Skip to content
Docsplugin.toml

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.