PluginsEarly
plugin.toml
The plugin manifest. Its root keys, what a plugin can add, default keys, settings and limits.
Every plugin folder holds a plugin.toml. It names the plugin, says which
version of the interface it is written for, declares what it may do, and lists
everything it adds to the app. gr plugin check reads it and names any problem
at its line.
Write the root keys first, before any table. A single bracket, [allow], is a
table; a double bracket, [[commands]], adds one more of a kind. Paths name
files inside the plugin’s folder.
A complete manifest
A small panel that reads the workspace, with a command, a key for it and a setting:
name = "notes"
version = "0.1.0"
api = "0.3"
description = "A panel of workspace notes."
main = "plugin.tsx"
events = ["files.changed"]
[allow]
fs = ["read:workspace"]
clipboard = "write"
[[commands]]
name = "notes.copy"
title = "Copy the selected note"
[[panels]]
id = "notes.list"
title = "Notes"
dock = "right"
[panels.keys]
y = "notes.copy"
[keys.normal]
"<leader>nn" = "notes.list"
[settings.max_notes]
kind = "int"
default = 100
doc = "The most notes to show."
Root keys
| Key | What it says |
|---|---|
name |
Required. 2 to 40 lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit. Not a reserved name, and not another installed plugin’s. |
version |
Required. A semantic version, such as 0.1.0. |
api |
Required. The interface version the plugin is written for: 0.1, 0.2 or 0.3. Using a name that arrived in a later version is an api.newer error. A plugin written for an older version keeps working. |
description |
Required. 1 to 300 characters on what it does and how to use it. Agents search these words. |
main |
The script, ending in .ts, .tsx, .js or .jsx. Leave it out for a plugin that only holds data, or whose panels are static. |
events |
The background events the script wants, such as files.changed. load, unload and settings need no declaring, and each thing a plugin adds brings its own events with it. |
eager |
true starts the script with the window instead of when it is first needed. Off at first. |
eager_reason |
Why, when eager is on: every window pays the cost of starting it. |
[allow]
What the plugin may do. Nothing in it is granted until a person approves it, and nothing outside it is reachable at all:
[allow]
fs = ["read:workspace", "write:data"]
net = ["api.github.com"]
exec = ["rg"]
graph = "read"
agents = ["read", "write"]
What a plugin may do lists every request, what it allows, and the words a person reads before approving it.
What a plugin can add
Each id or name starts with the plugin’s name and a dot, such as
notes.list. Ids are unique across everything one plugin adds.
| Table | Adds |
|---|---|
[[commands]] |
A command in the palette, which keys can run. name and title are required. |
[[panels]] |
A view docked left, right (the default) or bottom, floating (float), or as a tab in the middle (tab, since 0.2). id and title are required; the id is also the command that shows and hides it. [panels.keys] gives keys that apply while the panel has the focus. |
[[status]] |
A segment of the statusline, set with $.status.set. |
[[pickers]] |
A searchable list the plugin fills, opened by its id. live = true asks again as the search changes. |
[[badges]] |
Badges on the map’s cards, by node, set with $.badges.set. legend says what they mean. |
[documents] |
Lets the plugin follow the files open in the app, for the languages it names. Needed for document events and $.docs. |
[[decorations]] |
Marks inside open code: signs, underlines, tints and words at the end of a line. Needs [documents]. |
[[queries]] |
Marks drawn by a tree-sitter query file, with no script at all. |
[[layers]] |
A short-lived key mode, entered by one of the plugin’s commands. |
[[menus]] |
A row in one of the app’s right-click menus, which runs one of the plugin’s commands. |
[[tools]] |
A tool your coding agents can call through Graphide’s MCP server. Needs agents = ["tools"]. |
[[mcp]] |
A whole MCP server for your agents, run in the sandbox. |
[[agents]] |
A new kind of agent the app can start, spoken to over ACP. |
[[mentions]] |
A source for @ in the agent composer. |
[[languages]] |
A language pack: a WebAssembly grammar, its highlighting queries, and a language server. |
[[completions]] |
Completions in the code view. |
[[snippets]] |
Snippets for a language, with tab stops. No script and nothing to approve. |
[[agents]], a language’s server and terminals run as you, outside the
sandbox, and a person’s approval says so. gr plugin api prints the full
rules for each table.
A plugin can also carry data: colour themes as themes/<name>.toml, icons
as SVG or PNG files in icons/, and editor presets, such as a Neovim
distribution’s keys, as presets/<name>.json. Its themes join your theme
choices once the plugin is approved and on.
Default keys
[keys.normal]
"<leader>nn" = "notes.list"
[keys.insert]
"<C-k>" = { command = "notes.copy", when = "focus == 'code'" }
[keys.normal], [keys.visual] and [keys.insert] bind keys, in Vim’s
notation, to the plugin’s own commands, panels and pickers. A plugin’s keys are
defaults: they are bound only where the person’s own keys leave the key free.
In normal and visual a default cannot be a single unmodified letter, and in
insert it must start with a modified key. A when clause limits a key to
where its context keys say.
Settings
[settings.max_notes]
kind = "int"
default = 100
doc = "The most notes to show."
[settings.token]
kind = "secret"
doc = "Your API token."
host = "api.example.com"
header = "Authorization"
format = "Bearer {}"
A setting’s key is lowercase letters, digits and underscores. kind is
string, bool, int, enum (with its values) or secret. Every kind but
a secret needs a default of that kind. The script reads settings with
$.settings.get, and a person sets them in their config.toml under
[plugins.<name>.settings] or with gr plugin config.
A secret is entered by the person with gr plugin secret set and kept in the
system keychain. The script only learns whether it is set. Graphide adds it to
requests to the one host the setting names, in the header it names, written
as format says.
Limits
[limits]
handler_ms = 1000
memory_mb = 512
A plugin may raise its own time and memory limits, up to a cap:
| Key | Default | Cap | For |
|---|---|---|---|
load_ms |
1000 | 2000 | Loading the script |
handler_ms |
500 | 2000 | A command, an act or a pick |
view_ms |
250 | 1000 | Drawing a view |
rows_ms |
2000 | 5000 | Rows of a long list, and picker entries |
background_ms |
500 | 2000 | Events, timers and promises, each slice |
memory_mb |
256 | 1024 | The plugin’s whole process |
The rest are fixed, or a person’s own. Limits has them all.
Size
A plugin folder holds at most 2 MiB in 200 files; a language pack, 16 MiB in 400. A symbolic link, a hard link to a file outside the folder, or a special file is refused.
The script imports only its own files, by a relative path such as
./lib.ts: .ts, .tsx, .js, .jsx and .json. There are no packages to
install; everything the plugin runs is in its folder.
These docs describe Graphide 0.2.16, the latest release. Something wrong or missing? Tell us onDiscord or at hello@graphide.com.