Frontend Plugin API
The frontend API is what a browser-side plugin receives in its activate()
call: a set of registrars for every UI surface, the event bus, and i18n.
Entry Point
A frontend plugin exports a definition with an activate(api) function.
The host calls it with the FrontendPluginApi object once the plugin is
consented and active:
import { definePlugin } from '@neotavern/plugin-sdk';
export default definePlugin({
activate(api) {
// Register surfaces here.
},
deactivate() {
// Optional explicit teardown.
},
});
Every registrar returns a cleanup function. The runtime collects these
automatically, so your plugin does not need to track them by hand — though
deactivate() can still tear down anything you manage yourself.
Registration Surfaces
The api.ui namespace groups the UI registrars:
- Pages —
api.ui.pages.register({ id, path, title, mount })adds a route under the plugin namespace.mountreceives a host-provided container and may return a teardown. - Settings panels —
api.ui.settingsPanels.register(...)adds a panel to the Settings screen. - Toolbar actions —
api.ui.toolbarActions.register({ id, title, icon, run }). The host renders the action as a standard button; you only provide semantics, never layout or breakpoints. - Message actions —
api.ui.messageActions.register({ id, title, icon, order, placement, run }). Theruncallback receives an immutable message snapshot plus anAbortSignalthat fires on teardown, re-invocation, or timeout. - Context menu items —
api.ui.contextMenuItems.register({ id, title, context, run })forcontext: 'message' | 'character'. - Message renderers —
api.ui.messageRenderers.register({ id, title, render }).renderreturns plain text with aplacementof'replace'or'after'— never HTML. - Character tabs —
api.ui.characterTabs.register({ id, title, mount }).mountreceives{ characterId }as context. - Sidebar panels —
api.ui.sidebarPanels.register({ id, title, slot, mount })withslot: 'left' | 'right'. - Dialogs —
api.ui.dialogs.register({ id, title, description, mount }). - Command palette actions —
api.ui.commands.register({ id, title, run }). - Hotkeys —
api.ui.hotkeys.register({ id, combo, run }), for examplecombo: 'mod+shift+k'.
Slash commands register separately through api.slash.register({ name, description, run }), and prompt interceptors through api.interceptors.
Prompt Interceptors
An interceptor runs on the assembled prompt before it is sent:
api.interceptors.register({
id: 'example.format',
priority: 100,
timeoutMs: 5000,
intercept(context) {
// context.messages is an array of { id, role, content, name }.
return context;
},
});
Lower priority runs earlier; a plugin that exceeds timeoutMs is skipped
without breaking the chain. Interceptors that only inspect the prompt need
prompt.inspect; those that change it need prompt.modify.
Events
The event bus is typed and shared with the host. api.events.on(event, handler) returns an unsubscribe function:
const off = api.events.on('chat.message.created', ({ chatId, messageId }) => {
console.log('new message', chatId, messageId);
});
Built-in events include chat.created, chat.opened,
chat.message.created, chat.message.updated, chat.message.deleted,
character.selected, generation.started, generation.delta,
generation.finished, generation.error, theme.changed, and
language.changed. Plugins may also emit and listen to custom events, with
names namespaced by convention, for example myplugin.foo.
Message Snapshots and Content Gating
Message actions receive an immutable MessageActionSnapshot with
messageId, chatId, branchId, role, content, name, meta, and
revision. The content field is null unless the plugin also holds
chat.read, so an action can render metadata without ever seeing message
text.
Notifications and i18n
api.notify({ title, description, variant, timeoutMs }) shows a notification
and returns a dismiss function. variant is info, success, warning, or
error.
api.i18n manages translation resources in an isolated plugin namespace:
api.i18n.addResources('ru', { greet: 'Привет' });
const label = api.i18n.t('greet');
addResources returns a cleanup function like every other registration.
Cleanup Guarantees
Because every registration returns a cleanup function and the runtime tracks them, disabling a plugin removes all of its handlers, timers, DOM nodes, subscriptions, and background requests. See Lifecycle for the full teardown contract, and the generated Plugin SDK reference for the precise types.