Plugin structure
A plugin is a directory with a root plugin.json file. The manifest declares which files to load and which features to add to GrigDesk.
Package files
A simple panel can use this layout:
first-plugin/
├── plugin.json
├── assets/icon.png
├── ui/
│ ├── panel.js
│ ├── panel.css
│ └── locales/
│ ├── ru.json
│ └── en.json
└── docs/README.mdAdd a server file only when needed and declare it in entrypoints.server. The presence of a server.js beside the manifest does not load it automatically.
The v1 manifest
This is the complete manifest of the example plugin:
{
"id": "first-plugin",
"api": 1,
"name": "Счётчик проверок",
"version": "1.0.0",
"description": "Учебная панель со счётчиком проверенных пунктов",
"icon": "assets/icon.png",
"category": "dev",
"entrypoints": {
"web": {
"dir": "ui",
"entry": "panel.js",
"css": ["panel.css"],
"locales": { "ru": "locales/ru.json", "en": "locales/en.json" }
}
},
"contributes": {
"panels": [{ "title": "Счётчик проверок", "group": "tools", "order": 50 }]
},
"install": { "npm": [] }
}| Field | Purpose |
|---|---|
id | A unique package ID: lowercase Latin letters, digits, _ and -; the first character must be a letter or digit, up to 41 characters total. Must not match a system ID. |
api | Contract generation. Currently only 1; this is not the plugin or types package version. |
name, description | Name and description. A nonempty description is required, up to 4096 characters. |
version | A stable package version for Git installation, matching the tag without v. |
icon | Path to a PNG, JPEG or WebP up to 256 KiB. The same file is used in navigation and package details. |
entrypoints | A server entry and/or a UI module. Required object, which can be empty. |
contributes | A panel, supported slots, settings and other declarations. Required object, which can be empty. |
install.npm | Directories for preparing production dependencies. The example has none. |
Optional engines.grigdesk declares the minimum app version only as ">=X.Y.Z". category selects a catalog group: work, dev, knowledge or other. gallery.screenshots can contain up to six PNG/JPEG/WebP files, each up to 4 MiB. SVG is not accepted for icons or gallery images.
Paths are relative to the package. The module's dir is relative to the root; entry, css and locales are relative to dir. Absolute paths, URLs, parent traversal with .., hidden path components, node_modules and symlinks are not allowed.
Migrating an older package
Legacy top-level web, webModule, nav, settings, placements and minGrigDeskVersion fields are not adapted automatically. Use entrypoints, contributes and, for the minimum app version, engines.grigdesk. Older packages, including api: 2, remain visible with an incompatibility reason, but their code is not executed.
A panel inside GrigDesk
entrypoints.web points to a ready-to-run .js or .mjs ESM module with a Vue component as its default export. contributes.panels declares at most one panel: group selects rail, tools or dock, and order sets its position. GrigDesk generates the /ext/<id> address.
The group controls where the navigation entry appears. Opening behavior depends on the current screen and window width: from 900 px the panel is available in the dock alongside ongoing work; a narrow window opens it as a page. Switching between dock and page preserves the panel instance and its input.
Import Vue from vue and shared controls and dialogs from @grigdesk/ui. The application window provides both. Do not bundle your own Vue, create a separate Vue app, dialog hosts or EventSource. Emit CSS as a separate file and scope your selectors to the panel's root.
| Panel input | Purpose |
|---|---|
context.api | Shared get/post/put/patch/del client with the session, accepting only /api/… paths. |
context.locale, context.t | Reactive language and translation using the plugin's dictionaries. |
context.settings | Readonly effective setting values. |
context.signal | Cancellation when access to the instance is revoked. |
context.events, context.alerts | Own events over shared SSE and notifications from the plugin. |
context.askAgent, context.panel | A new chat with an unsent draft and control over the plugin's own panel. |
active, compact, presentation | Visibility and presentation of the panel. |
The module loads on the first request for its panel or a declared slot. An optional start(context) can return a cleanup function. A hidden panel keeps its input; disabling the plugin revokes access. Release timers and subscriptions and observe signal. After reconnection, context can change without running setup again: read the current prop in actions.
Embedding in an existing screen
In addition to its own panel, a plugin can declare a block in a supported core slot. For example, this manifest fragment adds runner data to the top-bar popover, below the limit bars:
{
"contributes": {
"slots": [
{
"slot": "provider.popover.after-bars",
"kind": "provider-usage",
"default": true
}
]
}
}This extends a complete manifest. The slot also requires a declared entrypoints.server providing JSON data for provider-usage. GrigDesk's own interface renders the data; this slot cannot mount an arbitrary Vue component. A placement can be disabled in settings while keeping the plugin's other features enabled.
Slots and contributes.panels can be used together. An open panel also has its own notifications through context.alerts, events through context.events and chat creation with an unsent draft through context.askAgent.
Server and data
entrypoints.server points to a .js, .mjs or .ts file whose default export is a Fastify plugin. Its handlers live under /api/ext/<id>/. Core services are available through options.core; check the methods you need before use. For actions restricted to the user interface, check request.authKind === 'session'.
Store persistent data in options.dataDir when provided. By default this is state/plugins/<id>/ within GrigDesk's data directory, separate from the installed code at plugins/<id>/. Code updates do not replace this directory. The server must release its resources on close. Long-running tasks can use lifecycle.prepareReplace to stop admitting new work and wait for existing work before package replacement.
Other declarations
contributes.settings describes fields on the shared settings page.
contributes.commands and contributes.themes currently retain id/title declarations only; they do not add command execution or theme switching.
Author documentation starts at docs/README.md. For enabled, compatible user plugins, GrigDesk gives the agent a path to this index. The agent reads it when a request concerns the plugin. Link other documents with relative paths. The plugin can still work without an index.
Next: create your first plugin.