Plugin Developer Guide
Knotbook plugins are tiny JavaScript modules that run sandboxed inside the desktop app. This guide walks you from an empty folder to a published .knotplug file on the gallery.
1. Overview
A plugin is a folder with a manifest.json and a single JavaScript entry file (plus any extra assets you want to bundle). Knotbook copies the folder into its app data directory, then loads the entry inside a Web Worker on demand.
- No network at install time. Installing a plugin never calls home; everything is local to your machine.
- Manual updates. A plugin you installed will never auto-update or change behavior behind your back.
- Sharing is a single file. Export to
.knotplug— a JSON envelope you can publish on the Knotbook Plugins gallery, email, or commit to a repo.
2. Scaffold a plugin folder
Anywhere on disk, create a folder:
my-plugin/
├── manifest.json
└── main.js3. The manifest
manifest.json is a small JSON object that tells Knotbook what your plugin is called and how to load it.
{
"name": "Word Counter",
"version": "1.0.0",
"description": "Shows live word and character counts for the active note.",
"entry": "main.js",
"author": "Your Name",
"permissions": ["editor.read", "ui.statusBar"]
}Fields:
name— display name shown in the sidebar's Plugins section.version— your version string. Bump it on every release; Knotbook uses it to decide whether to overwrite an existing install when you re-import.description— optional one-liner.entry— path inside the folder to the JS entry file. Most plugins usemain.js.author— optional credit string.permissions— declared scopes (informational at install time; the runtime enforces what's actually allowed).
4. The runtime API
Knotbook injects a global knotbook object into your worker and calls two well-known exports:
// Knotbook loads this file inside a Web Worker. The 'knotbook' global is
// injected by the host and gives you a small, async API surface.
//
// Lifecycle: onActivate() runs once when the user enables the plugin.
// onDeactivate() runs when they disable it.
export async function onActivate(ctx) {
ctx.log("Word Counter activated");
ctx.onEditorChange(async ({ noteId, body }) => {
const words = body.trim().split(/\s+/).filter(Boolean).length;
const chars = body.length;
ctx.statusBar.set(`${words} words · ${chars} chars`);
});
}
export async function onDeactivate(ctx) {
ctx.statusBar.clear();
}The ctx object passed in is your scoped handle — keep a reference to it so cleanups in onDeactivate can undo what you did in onActivate. Available surface (today):
ctx.log(message)— write to Knotbook's plugin log.ctx.onEditorChange(callback)— fires when the active note's body changes (subject toeditor.read).ctx.statusBar.set(text)/ctx.statusBar.clear()— show a chip in the footer (requiresui.statusBar).ctx.commands.register(id, handler)— add a command-palette entry (requiresui.commandPalette).ctx.notes.list()/ctx.notes.read(id)— read-only note access (requiresworkspace.read).
All methods return Promises. Don't try to import npm packages — workers run plain ES modules with no bundler. If you need a library, paste it into your entry file or ship it as an extra file referenced via import from main.js.
5. Install and iterate locally
- Open Knotbook. The Plugins section lives in the left sidebar, just under Templates.
- Click the + next to Plugins and pick your
.knotplugfile (or callinstall_plugin_from_foldervia the CLI for a raw dev folder). - Click the plugin row to toggle it Enabled. Your plugin's
onActivatefires. - While iterating, edit files in your source folder and re-install — or click the row to toggle off/on — to reload. Knotbook never caches plugin source between activations.
6. Export to .knotplug
When you're happy with your plugin, right-click its row in the sidebar's Plugins section and choose Export as .knotplug…. Knotbook bundles your manifest, entry source, and any extra files into a single .knotplug JSON file you can share or upload.
7. Publish to the gallery
- Create a Knotbook account (or sign in).
- Go to /plugins and click Upload plugin.
- Drop in your
.knotplugfile, a preview image, a clear description, and up to ten tags. - Publish. Your plugin shows up on the gallery immediately. You can edit metadata or delete it any time from the same page.
8. Install someone else's plugin
- On /plugins, browse the gallery.
- Click Download .knotplug on the plugin you want.
- In Knotbook, open the sidebar's Plugins section and click the + — pick the file you just downloaded.
- The plugin is installed disabled and appears in the sidebar. Review what it declares in its manifest, then click the row to toggle it Enabled when you're comfortable.
9. The .knotplug file format
A .knotplug file is a JSON object with a stable shape. You don't usually need to touch it by hand — Knotbook writes it for you on export — but it's useful to know what's inside:
{
"kind": "knotbook.plugin",
"version": 1,
"manifest": {
"name": "Word Counter",
"version": "1.0.0",
"description": "Shows live word and character counts.",
"entry": "main.js",
"permissions": ["editor.read", "ui.statusBar"]
},
"entry_source": "export async function onActivate(ctx) { /* ... */ }",
"files": {
"icon.png": "iVBORw0KGgoAAAANSUhEUgAAA…(base64)"
},
"exported_at": 1747000000,
"exported_from": "Knotbook 1.4.0"
}kindmust equal"knotbook.plugin". The importer refuses unrelated JSON.versionis the envelope schema version (currently1). Newer-than-supported files are rejected.manifestis the contents ofmanifest.jsonverbatim.entry_sourceis the JS source of the file pointed at bymanifest.entry, stored as plain text so the archive remains human-readable.filesis an optional map of extra files (icons, CSS, second JS modules…) keyed by their path inside the plugin root. Values are base64-encoded so binary content survives the JSON round-trip.
10. Security model
- Plugin source runs inside a Web Worker with no DOM access — it can't reach your notes or local files except through the
knotbookAPI. - Newly imported plugins start disabled. You always opt in before any code runs.
- The upload pipeline caps each
.knotplugat 10 MB and refuses paths that try to escape the plugin root. - The manifest's
permissionsfield is advisory today; the worker enforces the actual scope. Don't rely on an absent permission as a security boundary in your own code.
11. FAQ
Can a plugin access the network?
Not by default. There's no fetch in the worker sandbox unless a future permission grants it.
Can I ship multiple JS files?
Yes — put them in your plugin folder and import them from main.js. Export to .knotplug and they ride along in files.
How do I update a published plugin?
Bump the version in your manifest, re-export, and upload a new entry from the gallery. The old entry stays so existing users keep a stable download URL until they update.
What if I find a bad plugin?
Email support@spyxpo.com with the plugin URL and admins can take it down.