Creating Plugins
Build a plugin in JavaScript, package it as a zip, install it through the Manage Plugins modal. No build chain required for simple plugins; no restart needed at install time.
Plugin architecture
A Binderus plugin is a folder with a manifest.json and a main.js entry point. At install time the folder lives at:
<vault>/.binderus/plugins/<manifest.id>/
├── manifest.json
├── main.js
└── (any assets your plugin needs)
The plugin-loader reads main.js through a vault-aware Tauri command (read_plugin_file) so plugins work in both filesystem and encrypted-libsql storage modes.
manifest.json
{
"id": "my-plugin",
"name": "My Plugin",
"version": "0.1.0",
"minAppVersion": "0.9.0",
"description": "What my plugin does, in one sentence.",
"author": "Your Name"
}
| Field | Required | Notes |
|---|---|---|
id | yes | Lowercase, hyphenated, unique. Becomes the install folder name. |
name | yes | Human-readable name shown in Manage Plugins. |
version | yes | Semver. Bumping version on update lets the loader replace the running copy on hot-load. |
minAppVersion | yes | Binderus refuses to load plugins targeting a newer version than the installed app. |
description | recommended | One-line summary. Shown in the plugin row. |
author | recommended | Display only. No verification. |
main.js — lifecycle
main.js is a JavaScript module that exports a default object with lifecycle hooks. The plugin manager calls onLoad(ctx) when activating the plugin and onUnload() on deactivate or uninstall — this is where you wire up commands and tear them down cleanly.
// main.js
export default {
onLoad(ctx) {
// Register a command — appears in the command palette + bindable to a shortcut
const dispose = ctx.commands.register({
id: 'my-plugin.hello',
name: 'My Plugin: Say Hello',
run: () => ctx.editor.insertText('Hello from my plugin!')
});
// Persistent settings — keyed by plugin id under vault settings JSON
const settings = ctx.settings.get();
if (!settings.greeting) ctx.settings.set({ greeting: 'Hello' });
// Subscribe to setting changes (for live UI updates)
const unsubscribe = ctx.settings.onChange((s) => {
console.log('settings updated', s);
});
// Return a teardown function or store disposers — onUnload calls them
this._dispose = () => { dispose(); unsubscribe(); };
},
onUnload() {
this._dispose?.();
}
};
The PluginContext (ctx)
| Surface | Use for |
|---|---|
ctx.commands | Register commands that show up in Cmd P and can be bound to shortcuts. |
ctx.editor | Read selection, insert text, run editor commands. |
ctx.settings | Per-plugin settings store, persisted to vault settings JSON under plugins.<id>. get(), set(partial), replace(full), onChange(cb). |
ctx.panel | Mount a side-panel UI (HTML / DOM nodes). Hosts in the plugin panel area. |
ctx.statusBar | Add a status-bar entry — text + click handler. |
ctx.notify | Show a toast (info / success / error). |
Packaging
Zip up the folder. From your plugin's parent dir:
cd path/to/my-plugin
zip -r my-plugin.zip manifest.json main.js # add any other assets
The zip should contain manifest.json and main.js at the root, not inside a subfolder.
Install
In Binderus: Manage Plugins → Install (subtitle "from zip file") → pick the zip. Binderus calls the Rust install_plugin_from_zip command which:
- Validates the zip (size, entry count, manifest required)
- Sanitizes paths against zip-slip attacks
- Reads
manifest.jsonand validates theid - Wipes any prior install of the same id
- Unpacks into
<vault>/.binderus/plugins/<id>/ - Hot-loads the plugin without restarting the app
Uninstall is one-click per plugin row in the same modal.
Examples
The bindeck-mirror repo includes working example plugins under examples/plugins/:
- ollama-chat — chat with a local Ollama model using current editor / selection / files / folder as context
- ai-chat — same UX, generalized to any OpenAI-compatible endpoint (Ollama, LM Studio, OpenAI, DeepInfra, Groq, Together, OpenRouter)
Both ship as folders; the repo's tooling builds them into installable zips.
Security model
- Plugins run as JavaScript inside the Binderus renderer with a curated
ctxAPI — no raw filesystem, nofetch-anywhere by default - Network access goes through plugin-aware permissions (request, user prompt, allowlist)
- Plugin-scoped settings live under
plugins.<id>in the vault settings JSON — one plugin can't read another's settings - Disable / uninstall cleanly tears down every contribution; no leaked listeners, no dangling commands
Distribution
For now: share your .zip directly (Discord, GitHub Releases, Gumroad).