-
-
Notifications
You must be signed in to change notification settings - Fork 6
plugins
MultiVibe plugins are versioned JavaScript modules that participate in the inference lifecycle. A plugin can inspect or replace a request or response and can stop processing by returning its own HTTP response. Plugins execute inside the MultiVibe process: they are an extension mechanism, not a security sandbox.
This guide covers installation and operations, the v1 manifest and hook API, and the minimum structure needed to publish a plugin.
Only install repositories whose code and maintainers you trust. A plugin has the same filesystem, network, environment, and process access as MultiVibe. It can read provider credentials and request content. The installer performs structural checks, but these do not make third-party code safe:
- the URL must identify a public GitHub repository over HTTPS;
- credentials, query strings, fragments, submodules, and non-GitHub hosts are rejected;
- the repository is capped at 256 MiB and an individual file at 128 MiB;
- symlinks may not escape the checkout;
- the entrypoint must stay inside the repository; and
- the manifest must target the supported module API version.
MultiVibe clones the repository's current default-branch commit and records the
exact SHA in /data/modules/modules-lock.json. It never updates a plugin
automatically. Review upstream changes before selecting Update.
Open http://localhost:1455/?tab=plugins (replace the origin for a remote
deployment), then:
- Paste a public
https://github.com/owner/repositoryURL. - Select Install and pin. A new third-party plugin starts disabled.
- Restart MultiVibe when the card displays Restart required.
- Select Enable to use the plugin for new requests.
The plugin cards expose their origin, pinned commit, version, declared hooks, health, and load state.
The Marketplace view is designed for larger catalogs: search matches names, descriptions, authors, IDs, categories, and tags, while category chips narrow the result grid. Select Install on a catalog card to clone and pin that plugin without re-entering its URL. The separate Installed view retains the runtime controls and health information.
Select Submit a plugin, enter the public GitHub repository URL, then choose Validate and submit. MultiVibe clones a temporary snapshot, validates the same safety and compatibility rules used during installation, reads its marketplace metadata, and adds or refreshes the entry in this deployment's catalog. Submission does not install or execute the plugin.
Marketplace submissions are stored in /data/modules/marketplace.json. This
is an instance-local registry: operators can curate their own catalog and move
it with their /data backup. The submission API is protected by the same admin
authentication as the rest of the dashboard.
| Action | Effect |
|---|---|
| Enable | Makes a loaded plugin participate in new requests. |
| Disable | Stops invoking the plugin for new requests immediately. |
| Update | Fetches and pins the current upstream commit; review it first and restart afterward. |
| Remove | Deletes a disabled third-party plugin and its lock entry. Bundled plugins cannot be removed. |
Plugin state and external checkouts live below MODULES_PATH, which defaults
to /data/modules with the supplied Compose configuration. Back up this path
with the rest of /data. Do not edit modules-lock.json while MultiVibe is
running.
The bundled Security plugin is enabled by default. It provides reversible,
session-scoped pseudonymization before prompt content leaves MultiVibe. It is
maintained in the
multivibe-security-module
repository and shipped at a pinned submodule commit.
The dashboard uses the authenticated /admin/modules API. An admin login
session, x-admin-token, or a Bearer admin token is required when
ADMIN_TOKEN is configured.
export MULTIVIBE_URL=http://localhost:1455
export ADMIN_TOKEN='replace-with-your-admin-token'
# List plugins
curl -fsS -H "x-admin-token: $ADMIN_TOKEN" \
"$MULTIVIBE_URL/admin/modules"
# Install and pin a public GitHub repository
curl -fsS -X POST -H "x-admin-token: $ADMIN_TOKEN" \
-H 'content-type: application/json' \
-d '{"url":"https://github.com/owner/repository"}' \
"$MULTIVIBE_URL/admin/modules/install"
# Enable or disable
curl -fsS -X PATCH -H "x-admin-token: $ADMIN_TOKEN" \
-H 'content-type: application/json' \
-d '{"enabled":true}' \
"$MULTIVIBE_URL/admin/modules/com.example.plugin"
# Replace the complete settings object
curl -fsS -X PATCH -H "x-admin-token: $ADMIN_TOKEN" \
-H 'content-type: application/json' \
-d '{"settings":{"mode":"audit"}}' \
"$MULTIVIBE_URL/admin/modules/com.example.plugin"
# Fetch and pin the latest upstream commit
curl -fsS -X POST -H "x-admin-token: $ADMIN_TOKEN" \
"$MULTIVIBE_URL/admin/modules/com.example.plugin/update"
# Disable before removing a third-party plugin
curl -fsS -X PATCH -H "x-admin-token: $ADMIN_TOKEN" \
-H 'content-type: application/json' -d '{"enabled":false}' \
"$MULTIVIBE_URL/admin/modules/com.example.plugin"
curl -fsS -X DELETE -H "x-admin-token: $ADMIN_TOKEN" \
"$MULTIVIBE_URL/admin/modules/com.example.plugin"Unset the shell variables when finished. Settings updates replace the complete settings object; they are not a partial merge. MultiVibe validates top-level property names, primitive types, enums, and numeric bounds from the manifest's schema.
A plugin repository needs a multivibe.module.json file at its root and a
prebuilt ECMAScript-module entrypoint. MultiVibe does not install dependencies
or run a build during installation, so commit every runtime file the
entrypoint needs. Avoid dependencies on MultiVibe's private source tree.
example-plugin/
├── multivibe.module.json
├── package.json
└── dist/
└── index.js
Mark JavaScript files as ECMAScript modules:
{
"type": "module"
}Example manifest:
{
"id": "com.example.plugin",
"name": "Example plugin",
"version": "1.0.0",
"apiVersion": 1,
"description": "Adds a marker before an upstream request.",
"entrypoint": "dist/index.js",
"hooks": ["request.beforeUpstream"],
"priority": 100,
"timeoutMs": 5000,
"failurePolicy": "open",
"repository": "https://github.com/owner/example-plugin.git",
"categories": ["Productivity", "Automation"],
"tags": ["metadata", "routing"],
"author": "Example Labs",
"homepage": "https://github.com/owner/example-plugin",
"settingsSchema": {
"type": "object",
"properties": { "marker": { "type": "string" } },
"additionalProperties": false
},
"defaultSettings": { "marker": "example" }
}| Field | Required | Meaning |
|---|---|---|
id |
Yes | Stable lowercase identifier, 3–128 characters, using letters, digits, dots, and hyphens. |
name, version, description
|
Yes | Operator-facing plugin metadata. |
apiVersion |
Yes | Must be 1. |
entrypoint |
Yes | Repository-relative path to a committed ESM JavaScript file. |
hooks |
Yes | Hook names the plugin implements. |
repository |
Yes | Canonical public GitHub URL; it must match the installation URL after normalization. |
categories |
No | Up to 8 category names used for marketplace grouping; uncategorized plugins appear under Other. |
tags |
No | Up to 16 searchable tags. |
author |
No | Author or publisher displayed by marketplace cards. |
homepage |
No | Public HTTPS details URL shown on the card. |
priority |
No | Execution order; lower values run first, default 100, then IDs break ties. |
timeoutMs |
No | Per-hook timeout, default 5,000 ms and clamped to 10–60,000 ms. |
failurePolicy |
No |
open (default) continues the request after failure; closed fails it. |
settingsSchema |
No | JSON-Schema-like contract for operator settings. |
defaultSettings |
No | Initial settings copied into the lock entry at installation. |
Example entrypoint:
export const module = {
async "request.beforeUpstream"(value, context) {
context.log.info(`processing ${context.requestId}`);
return {
action: "replace",
value: { ...value, metadata: { ...value.metadata, marker: context.settings.marker } }
};
}
};
export default module;The entrypoint may export the plugin object as either default or module.
Each handler receives a structured clone of the current value and a context
object. Return one of:
{ action: "continue" }
{ action: "replace", value: replacement }
{ action: "respond", response: { status: 403, headers: {}, body: { error: "blocked" } } }respond short-circuits the remaining plugins at that hook. Replacements flow
to the next plugin in priority order.
| Hook | Current invocation point | Typical use |
|---|---|---|
request.received |
After authentication, before routing | Validate, reject, or normalize the client body. |
request.beforeUpstream |
After provider/model selection and payload normalization | Redact or adapt the provider-bound payload. |
response.received |
On buffered upstream response content | Restore, inspect, or transform upstream data. |
response.beforeClient |
Before a buffered Responses object is returned | Apply a final structured transformation or return a custom response. |
stream.open |
Reserved in API v1; not currently dispatched | Future stream initialization handling. |
request.error |
Reserved in API v1; not currently dispatched | Future error observation or transformation. |
The context can contain:
{
requestId: string;
sessionId?: string;
application?: string;
route: string;
transport: "http" | "sse" | "websocket";
provider?: string;
model?: string;
signal: AbortSignal;
settings: Readonly<Record<string, unknown>>;
log: {
info(message: string): void;
warn(message: string): void;
error(message: string): void;
};
}Optional fields depend on how far the request has progressed. Do not retain or
mutate the supplied value or settings object. Treat signal as the request's
cancellation signal and keep hook work bounded even though MultiVibe also
enforces the manifest timeout.
If a hook throws or times out, MultiVibe marks that plugin unhealthy for the
rest of the process lifetime. With failurePolicy: "open", processing
continues without it. With "closed", the hook failure is propagated; at the
initial request hook this becomes a 500 module_failed response.
- Build the JavaScript entrypoint and commit all runtime artifacts.
- Test every declared hook with representative request and response shapes.
- Keep logs free of prompts, responses, credentials, and other secrets.
- Verify
repository,id,apiVersion, timeout, priority, and failure policy. - Tag the release, then install or update it from the Plugins page.
- Confirm the pinned commit and healthy state after restart before enabling it in production.
For the canonical TypeScript contracts and loader behavior, see
src/module-sdk.ts and
src/module-manager.ts.
Migrated from docs/plugins.md on 2026-09-07.