Skip to content

Plugins overview

A Kryton plugin is a folder containing a manifest.json plus a client entry, a server entry, or both. Plugins extend the editor (UI slots, code-fence renderers, commands, editor extensions) and the server (HTTP routes, event hooks, scoped storage).

The minimum is a manifest plus at least one entry:

my-plugin/
manifest.json
client/
index.js # optional — runs in the browser
server/
index.js # optional — runs in Node, loaded by the server
__tests__/
*.test.js # optional — vitest

This mirrors the layout in every plugin under kryton-plugins/plugins/ — client-only (recent-files), server-only (sample-wordcount), or both (kanban, git-backup).

Required fields (validated by kryton-plugins/scripts/validate-registry.js and the server’s PluginManifest type in packages/server/src/modules/plugins/services/types.ts):

FieldTypeNotes
idstringMust equal the directory name.
namestringHuman-readable label.
versionstringSemver.
descriptionstringOne-line summary.
authorstring
minKrytonVersionstringEarliest Kryton release this plugin supports.

Optional:

  • server — path to the server entry file, relative to the plugin dir.
  • client — path to the client entry file, relative to the plugin dir.
  • settings[] — declarative settings rendered by the host. Each entry has key, type (string | boolean | number), default, label, and perUser (when true, each user gets their own value).

Example (kryton-plugins/plugins/recent-files/manifest.json):

{
"id": "recent-files",
"name": "Recent Files",
"version": "1.0.0",
"description": "Display recently opened files for quick navigation",
"author": "Kryton",
"minKrytonVersion": "2.0.0",
"tags": ["navigation", "productivity"],
"icon": "clock",
"client": "client/index.js",
"settings": [
{
"key": "maxItems",
"type": "number",
"default": 20,
"label": "Maximum recent files to show",
"perUser": true
}
]
}

Server-side, every plugin moves through these states (PluginManager.loadPlugin in services/manager.ts):

  1. installed — manifest read from disk.
  2. loaded — server entry require()d.
  3. active — module’s exported activate(api) ran successfully (with a timeout). Client-only plugins skip straight from installed to active.
  4. error — load or activation threw. The error message is persisted on the installedPlugin row.

The enabled flag (also on installedPlugin) is independent of state: disabling a plugin from the admin panel deactivates it, persisting enabled = false, and the next boot skips loading it.

Client-side, activate(api) runs after the host injects window.__krytonPluginDeps (React + ReactDOM). Registrations made during activate — sidebar panels, fence renderers, commands — persist for the session. deactivate(), if exported, runs on plugin disable.

Two install paths, both handled by PluginManager:

  • Registry — the official catalogue lives at kryton-plugins/registry.json, regenerated from the manifests by kryton-plugins/scripts/generate-registry.js. The admin panel’s Plugin Manager browses this list and installs by id.
  • Local — drop a folder into the configured plugins directory and the manager picks it up on boot (or via the Reload plugins action). Use this for development.

Both end up as rows in installedPlugin; the lifecycle is the same from there.

  • Quickstart — a 30-line sidebar plugin you can clone and edit.
  • Client API — autogenerated TypeDoc reference for everything on ClientPluginAPI.
  • Server API — autogenerated TypeDoc reference for the Node-side PluginAPI.
  • UI slots — every api.ui.register* method with a real-world example.
  • Code-fence renderers — how the Kanban plugin replaces a kanban ``` block with an interactive board.
  • Testing and publishing — vitest, manifest validation, and how to land a plugin in the registry.