Plugin Development Guide
Komari supports extending the server with JavaScript plugins. A plugin is a ZIP package containing a manifest (komari-plugin.json) and an entry script (default script.js). Plugins run inside the server process in their own sandboxed goja JS runtime, and can register HTTP routes, intercept HTTP requests/responses, call system RPC methods, register their own RPC methods, declare configuration items, and inject admin pages.
Security
Plugins run with admin privileges and may request sensitive capabilities such as filesystem access, child process execution, or port listening. Only install plugins you trust; review the declared permissions carefully before enabling third-party plugins.
What a plugin can do
| Capability | API | Required permission | Notes |
|---|---|---|---|
| Register RPC methods | server.registerRPC | always granted | Register plugin:xxx methods callable by the UI or other plugins |
| Call system RPC | server.call | allowSystemRPC | Invoke any registered RPC method with admin authority |
| Register HTTP routes | server.route | allowRoutes | Register METHOD /path on the host engine; supports streaming |
| Intercept HTTP requests/responses | server.hook | allowHooks | Modify every HTTP request/response entering or leaving the server |
| Read plugin configuration | server.getConfig | always granted | Read saved configuration merged with manifest defaults |
| Declare configuration items | manifest configuration | no permission | Admin UI generates a config form automatically |
| Inject admin pages | manifest pages | no permission | Show iframe / redirect pages in the admin sidebar |
| File access | fs / require | inside plugin dir: always granted | Sandboxed to the plugin directory; escaping requires allowAllFileAccess |
| Node compatibility modules | node modules | node | events/fs/path/os/process/net/http/crypto, etc. |
| Child processes | child_process | allowExec | Execute external commands |
| Port listening | net/http Server | allowListen | Bind local ports (default 127.0.0.1) |
Quick Start
1. Create the plugin directory
hello/
├── komari-plugin.json
└── script.js2. Write the manifest komari-plugin.json
{
"name": "Hello World",
"short": "hello",
"description": "An example plugin",
"author": "Your Name",
"version": "1.0.0",
"komari": ">=1.0.0",
"entry": "script.js",
"permissions": {
"node": true,
"allowSystemRPC": true,
"allowRoutes": true
}
}See Manifest Reference for all fields.
3. Write the entry script script.js
const server = require("server");
function load() {
console.log("hello plugin loaded");
// Register an HTTP route: GET /hello
server.route("GET", "/hello", async (req, res) => {
const nodes = await server.call("common:getNodes");
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({
greeting: "Hello, Komari!",
nodeCount: Object.keys(nodes).length
}));
});
}
function unload() {
console.log("hello plugin unloaded");
}- The top-level script runs immediately when the plugin loads, but it is recommended to put logic in the global
load()function. load()runs every time the plugin is enabled/started;unload()runs on disable, uninstall, or server shutdown.
4. Package and install
Put komari-plugin.json and script.js directly at the ZIP's root (no wrapping folder), then upload it on the admin "Plugins" page, or install via the admin API:
curl -X POST -H "Cookie: session_token=<your session>" --data-binary "@hello.zip" http://localhost:25774/api/admin/plugin/install5. Enable the plugin
After installation the plugin is disabled by default and must be enabled manually.
Because hello declares allowSystemRPC and allowRoutes, enabling it triggers the permission approval flow: the admin must confirm the permissions in a dialog before the plugin can be enabled (see "Permissions & Approval" below).
Once enabled, visit GET /hello to see the plugin's JSON response.
Installation limits
ZIP packages: up to 10,000 files, each file ≤ 128 MiB, total extracted ≤ 512 MiB, manifest ≤ 1 MiB. Any path-traversal entry (../, absolute paths) rejects the entire package.
Lifecycle
Load load()
- The server reads and validates
komari-plugin.jsonand checks thekomariversion constraint. - A dedicated JS runtime is created (sandbox root:
data/plugin/<short>), and the entry script is executed immediately. - If the script defines a global
load()function, it is invoked. - A top-level error or a
load()error → the plugin is auto-disabled and the error is persisted inlast_error.
Unload unload()
Triggered on disable, delete, or server shutdown:
- The plugin is removed from the instance registry first (so
server.callrejects with "not loaded" from insideunload()). - The global
unload()is called if defined (errors are recorded, not blocking). - Registered RPC methods are unregistered, handlers and hooks cleared, and the runtime closed.
Route slots persist
Gin route slots registered by a plugin remain after unload: requests to those routes receive 404 until the plugin is loaded again. Reinstalling a running plugin unloads it first and restores its persisted enabled state.
Startup recovery
On server startup (LoadAll), every enabled and approved plugin is loaded automatically; plugins that fail to load are auto-disabled with last_error persisted (this does not stop the server from starting).
Permissions & Approval
Permission model
- Always granted (no declaration needed, no approval):
server.registerRPC,server.getConfig, file access inside the plugin directory. - Granted by declaration (runtime settings, no approval):
permissions.node,maxHTTPBodyBytes,maxChildOutputBytes,timeout. - Require admin approval (6 sensitive capabilities; any of them being
truetriggers the flow):allowSystemRPC,allowRoutes,allowHooks,allowExec,allowListen,allowAllFileAccess.
Approval flow
When admin:setPluginEnabled enables a plugin, the server compares the declared capability set with the hash saved at the last approval:
- Matches → enabled directly.
- Differs and the plugin is not yet approved → returns
{ requires_approval: true }; the admin UI shows a permission dialog, and retries withapproved: trueafter confirmation. - Changing sensitive capabilities after approval → requires re-approval (changes to
node/timeout/size limits do not re-trigger approval).
Capability hash
The approval hash covers only the 6 sensitive capabilities (a sha256:-prefixed JSON hash); node, maxHTTPBodyBytes, maxChildOutputBytes, and timeout are excluded.
Behavior when a permission is missing
| API | Behavior without permission |
|---|---|
server.route / server.hook | Throws TypeError at load time; plugin load fails (auto-disabled) |
server.call | The returned Promise is rejected (load not blocked) |
require("child_process") | Throws (no allowExec) |
net/http Server listen() | Throws (no allowListen) |
fs / require outside plugin dir | Rejected by the sandbox (no allowAllFileAccess) |
Debugging
Each plugin has a dedicated 64 KiB ring log buffer; console.* output and lifecycle/hook errors are written to it (reset on every load). Read it via the RPC2 method admin:getPluginLogs (param {short}):
POST /api/rpc2
{"jsonrpc":"2.0","id":1,"method":"admin:getPluginLogs","params":{"short":"hello"}}TIP
When a plugin fails to load, the admin "Plugins" list shows last_error. Combined with the plugin logs, this diagnoses most issues (e.g. missing permissions, unsupported runtime APIs).
Security & Limitations
- The plugin sandbox root is
data/plugin/<short>:fsandrequireare confined to it, and path traversal / symlink escapes are rejected at the OS level (os.Root). server.callruns with admin authority — a plugin callingadmin:*methods is equivalent to the admin doing it themselves.- The JS runtime is not a browser and not full Node.js: no DOM, WebSocket, ESM,
for await...of, or completefs. Read the JS Runtime Reference before relying on any API. - Each JS turn is bounded by
timeout(default 30 s): script init,load(), route handlers, hooks, RPC handlers, and fetch all obey it. A route handler that never callsend()produces 504. - There are no per-plugin CPU/memory/network quotas;
process.memoryUsage()etc. report the whole Komari process.
Continue Reading
| Document | Contents |
|---|---|
| Manifest Reference | All komari-plugin.json fields, permissions, configuration, pages |
| server Module | server.route / server.hook / server.call / server.registerRPC / server.getConfig and lifecycle hooks |
| JS Runtime | Every JavaScript API available in the sandbox and its compatibility |
| RPC Methods | All system RPC methods callable via server.call |
| Publishing to the Plugin Market | Publish your plugin to the official plugin market |