Plugin folders live in the runtime plugins directory reported by Settings -> Plugins.
Bundled plugins ship with the app from the bundled plugins directory and can be enabled/disabled the same way.
my-plugin/
plugin.json
tab.html
widget.html
Pluginfile is the portable single-file distribution format for Vellium plugins.
It bundles the manifest and referenced asset files into one JSON document:
{
"format": "vellium-pluginfile@1",
"manifest": {
"id": "my-plugin",
"name": "My Plugin",
"version": "0.1.0"
},
"files": {
"tab.html": "<!doctype html>..."
}
}Use Settings -> Plugins -> Install Pluginfile to install one, or export an existing
plugin back into a .pluginfile.json package.
{
"id": "my-plugin",
"name": "My Plugin",
"version": "0.1.0",
"description": "Custom tab and widget",
"defaultEnabled": true,
"permissions": ["api.read", "pluginSettings.read", "pluginSettings.write", "host.resize"],
"tabs": [
{
"id": "dashboard",
"label": "My Tab",
"path": "tab.html",
"order": 100
}
],
"slots": [
{
"id": "chat-widget",
"slot": "chat.inspector.bottom",
"title": "My Widget",
"path": "widget.html",
"order": 100,
"height": 240
}
],
"actions": [
{
"id": "composer-tool",
"location": "chat.composer",
"label": "My Action",
"title": "Plugin Action",
"path": "widget.html",
"order": 100,
"width": 720,
"height": 420,
"variant": "ghost"
},
{
"id": "quick-sync",
"location": "app.toolbar",
"label": "Quick Sync",
"title": "Quick Sync",
"mode": "inline",
"request": {
"method": "POST",
"path": "/api/plugins/{{pluginId}}/settings",
"body": { "lastAction": "{{activeTab}}" }
},
"successMessage": "Plugin settings updated",
"variant": "accent",
"reloadPlugins": false
}
]
}chat.sidebar.bottomchat.inspector.bottomchat.composer.bottomchat.message.bottomwriting.sidebar.bottomwriting.editor.bottomsettings.bottom
app.toolbarchat.composerchat.messagewriting.toolbarwriting.editor
Load the host bridge from your plugin page:
<script src="/api/plugins/sdk.js"></script>Then use it from page scripts:
const ctx = await window.VelliumPlugin.host.getContext();
const settings = await window.VelliumPlugin.api.get('/api/settings');
const pluginSettings = await window.VelliumPlugin.settings.get();
window.VelliumPlugin.host.resize(320);
window.VelliumPlugin.ui.ensureStyles();sdk.js now injects a shared UI layer automatically.
It provides:
- theme sync with the current app theme
- app-aligned color tokens
- ready-to-use utility classes
Available helpers:
window.VelliumPlugin.ui.ensureStyles();
window.VelliumPlugin.ui.applyTheme('dark');
window.VelliumPlugin.ui.classes;Plugins do not need to talk to raw backend routes directly anymore. Use the stable
window.VelliumPlugin.vellium namespace instead.
const { vellium } = window.VelliumPlugin;
const chats = await vellium.chats.list();
const created = await vellium.chats.create({ title: "Plugin chat" });
await vellium.chats.send(created.id, { content: "Hello from plugin" });
const blankCharacter = await vellium.characters.createBlank({
name: "Plugin Character",
description: "Created from plugin"
});
const lorebooks = await vellium.lorebooks.list();
const providers = await vellium.providers.list();Use vellium.generate() when your plugin needs model output without caring whether the
active backend is OpenAI-compatible, KoboldCpp, or a custom adapter.
const result = await window.VelliumPlugin.vellium.generate({
systemPrompt: "You are a concise assistant.",
userPrompt: "Summarize this scene in one paragraph."
});
console.log(result.content);
console.log(result.reasoning);
console.log(result.providerType);You can also pass explicit messages/provider/model:
await window.VelliumPlugin.vellium.generate({
providerId: "local-provider",
modelId: "my-model",
messages: [
{ role: "system", content: "Stay concise." },
{ role: "user", content: "Write a short recap." }
]
});Plugins can manage custom endpoint adapters through the unified API:
const adapters = await window.VelliumPlugin.vellium.extensions.adapters.list();
await window.VelliumPlugin.vellium.extensions.adapters.upsert({
id: "my-backend",
name: "My Backend",
description: "Plugin-owned adapter",
enabled: true,
authMode: "bearer",
authHeader: "X-API-Key",
chat: {
enabled: true,
method: "POST",
path: "/generate",
resultPath: "output.text",
bodyTemplate: {
prompt: "{{userPrompt}}"
}
}
});Useful classes:
vp-rootvp-herovp-cardvp-gridvp-stackvp-rowvp-actionsvp-titlevp-subtitlevp-labelvp-statvp-mutedvp-buttonvp-button--accentvp-button--dangervp-codevp-pill
Example:
<script src="/api/plugins/sdk.js"></script>
<div class="vp-root">
<section class="vp-hero">
<h1 class="vp-title">My Plugin</h1>
<p class="vp-subtitle">Uses the shared Vellium plugin UI kit.</p>
<div class="vp-actions">
<button class="vp-button vp-button--accent">Run</button>
<button class="vp-button">Cancel</button>
</div>
</section>
</div>ctx.payload contains slot-specific host data when the plugin is mounted inside chat or writing surfaces.
The bridge is intentionally limited to /api/* requests. This is the current safe base layer.
inline actions run directly through the host bridge with template substitution from the current context payload, so simple operations do not need to open an iframe modal.