Backend Plugin API
The backend API is what a server-side plugin receives in its activate()
call: restricted abstractions for routes, storage, events, logging,
network access, providers, and files — and nothing else.
Entry Point
A backend plugin exports a definition with an activate(api) function that
receives the ServerPluginApi object:
import { definePlugin } from '@neotavern/plugin-sdk';
export default definePlugin({
activate(api) {
const off = api.routes.get('/hello', async (request) => ({
status: 200,
body: { hello: 'world' },
}));
},
});
The backend entry runs as a separate Node.js process. The plugin never receives the Fastify root instance, the SQLite connection, internal tables, absolute paths, the full environment, or other providers' API keys.
Routes
api.routes is a scoped router mounted under
/api/plugins/{pluginId}/. Each method takes a path and a handler and
returns a cleanup function:
api.routes.get(path, handler)api.routes.post(path, handler)api.routes.put(path, handler)api.routes.delete(path, handler)
A PluginRequest carries params, query, headers, a parsed JSON body,
and an AbortSignal. A PluginResponse is { status, body, headers }.
Handlers may return a value directly or a promise; the host enforces timeouts
and cancels work through the signal.
Storage
api.storage is a namespaced key/value store isolated per plugin:
await api.storage.set('state', { count: 1 });
const state = await api.storage.get('state');
await api.storage.delete('state');
const keys = await api.storage.keys();
Data is scoped to your plugin id, so two plugins can never collide.
Events and Logging
api.events is the same typed event bus the frontend uses. Subscribing
returns an unsubscribe function, and all subscriptions are removed
automatically on disable, crash, or shutdown. Emitting is restricted to your
own namespace ({pluginId}.event), payloads must be JSON-safe, and the host
caps payload size and the number of event names per runtime.
api.logger provides debug, info, warn, and error methods, each
taking a message and optional metadata. Logs never include secrets.
Permission-Checked Fetch
api.fetch is fetch guarded by the plugin's network:<host> permissions:
const response = await api.fetch('https://api.example.com/data', {
method: 'GET',
headers: { Accept: 'application/json' },
signal,
});
Requests to hosts that are not granted are rejected before any network
activity. Secrets from other providers are never injected into your requests.
The response object exposes ok, status, text(), and json().
Providers and Context Strategies
api.providers lets a plugin extend generation:
api.providers.register(kind, factory, options)registers a new provider adapter kind (requiresproviders.register). Registration returns a cleanup function.api.providers.registerTokenizer(profile)registers a local model-specific tokenizer. A profile declaresid,approximate,matches(model), andcount(text). Exact tokenizers can be built from tiktoken, SentencePiece, or Hugging Face tokenizer JSON; until one is registered for a model, the host falls back to a script-aware heuristic and marks counts as approximate. Registration is removed automatically on deactivate.
api.contextStrategies.register(strategy) adds a context-shifting strategy.
The host verifies that system, pinned, and current-user blocks survive, and
applies the final token budget itself — the fitsBudget value a strategy
returns is not trusted.
api.postProcessors.register(processor) adds a post-generation hook. It runs
after the stream completes and before the message is saved; returning a new
string replaces the assistant reply. It requires prompt.modify.
Virtual Filesystem
api.files is a sandboxed virtual filesystem rooted at the plugin's own data
directory:
await api.files.write('notes.txt', 'content');
const content = await api.files.read('notes.txt');
const entries = await api.files.list('.');
await api.files.delete('notes.txt');
Paths cannot escape the plugin root, so a plugin can only ever touch its own data.
What a Backend Plugin Cannot Do
The API surface is deliberately small. There is no way to reach the host
database, other plugins' storage, arbitrary filesystem paths, or unvetted
network hosts. If the SDK does not expose it, it is not accessible. The
generated Plugin SDK reference lists the full
ServerPluginApi surface, and Providers explains
how provider plugins fit into the model.