-
Notifications
You must be signed in to change notification settings - Fork 0
plugin api reference
All plugin endpoints are under /api/plugins. Endpoints that modify state require authentication.
GET /api/plugins
Returns an array of all discovered plugins and their current state.
Response
[
{
"id": "themes",
"name": "Themes",
"version": "1.2.0",
"type": "provider",
"state": "active",
"bundled": true,
"nav": { "label": "Themes", "icon": "swatch", "order": 80 }
}
]state is one of: discovered, validating, loading, setting_up, active, tearing_down, disabled, errored.
GET /api/plugins/:id
Returns detailed information for a single plugin including full manifest and current state.
GET /api/plugins/:id/file/*path
Serves a static file from the plugin's directory. No authentication required — used by the frontend to load plugin scripts and Vue components.
POST /api/plugins/:id/enable
Marks the plugin as enabled in disabled-plugins.json. Takes effect on the next server restart.
Response 200 OK
POST /api/plugins/:id/disable
Marks the plugin as disabled. Takes effect on the next server restart.
Response 200 OK
GET /api/plugins/providers
Returns all registered provider implementations grouped by type.
Response
{
"storage": [
{ "name": "local", "pluginId": "built-in-storage", "active": true },
{ "name": "s3", "pluginId": "storage-s3", "active": false }
],
"metadata": [
{ "name": "musicbrainz", "pluginId": "metadata-mb", "active": true }
]
}PUT /api/plugins/providers/:type/active
Changes the active provider for a given type. Requires the settings permission.
Body
{ "name": "s3" }Response 200 OK
GET /api/plugins/:id/settings
Returns the raw HTML defined in the plugin manifest's settings field. Rendered inside an <iframe> in the settings UI.
Each plugin's own HTTP and WebSocket routes are mounted under:
HTTP: /api/plugins/{pluginId}/**
WebSocket: /ws/plugins/{pluginId}/**
For example, a plugin with ID my-plugin that calls ctx.routes.register('GET', '/data', handler) in its setup() will be reachable at:
GET /api/plugins/my-plugin/data
Authentication is applied automatically. Custom permission checks are optional per route.
The ctx object passed to plugin.setup(ctx).
ctx.hooks.on(event: HookEvent, handler: HookHandler, opts?: HookOptions): void
ctx.hooks.once(event: HookEvent, handler: HookHandler): void
ctx.hooks.off(event: HookEvent, handler: HookHandler): voidHookOptions:
{
phase?: 'before' | 'after' // default 'before'
priority?: number // default 100, lower runs first
}ctx.routes.register(
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
path: string,
handler: RouteHandler,
opts?: { permission?: string }
): void
ctx.routes.ws(path: string, handler: WsHandler): voidpath is relative to /api/plugins/{pluginId}/.
ctx.providers.register<T>(type: ProviderType, name: string, provider: T): void
ctx.providers.get<T>(type: ProviderType): T | null // active provider
ctx.providers.getByName<T>(type: ProviderType, name: string): T | nullctx.db.get(key: string): Promise<unknown>
ctx.db.set(key: string, value: unknown): Promise<void>
ctx.db.delete(key: string): Promise<void>
ctx.db.list(prefix: string): Promise<Array<{ key: string; value: unknown }>>All keys are scoped to the plugin. Keys from different plugins never collide.
ctx.permissions.define(name: string, description: string): voidDefined permissions appear in the admin panel and can be assigned to users.
ctx.logger.info(message: string, meta?: object): void
ctx.logger.warn(message: string, meta?: object): void
ctx.logger.error(message: string, meta?: object): voidOutput is tagged with the plugin ID automatically.
Passed to setup(ctx) exported from a Vue SFC or script plugin.
ctx.events.on(event: string, handler: (detail: unknown) => void): () => void
ctx.events.once(event: string, handler: (detail: unknown) => void): () => void
ctx.events.emit(event: string, detail?: unknown): voidon returns an unsubscribe function.
ctx.slots.register(
slot: SlotName,
component: Component,
opts?: { order?: number }
): voidPreconfigured fetch wrapper scoped to /api/plugins/{pluginId}/:
ctx.api.get(path: string): Promise<unknown>
ctx.api.post(path: string, body?: unknown): Promise<unknown>
ctx.api.patch(path: string, body?: unknown): Promise<unknown>
ctx.api.delete(path: string): Promise<unknown>Scripts that use the legacy window.slopsmith surface:
window.slopsmith.on(event, handler)
window.slopsmith.once(event, handler)
window.slopsmith.emit(event, detail)This maps directly to PluginEventBus under the hood. New plugins should use the FrontendPluginContext API instead.