Frontend API Reference
Complete reference for the Plugin API object passed to your mount() function — context, events, and RPC calls.
When the host loads your plugin, it calls your mount function with two arguments: a container DOM element and the Plugin API object. This page documents everything available to your frontend code.
New to plugins? Start with the Getting Started tutorial first.
Module Exports
Your plugin entry file must export a mount function and may optionally export an unmount function.
mount(container, api)
Called when the user opens your plugin's tab.
| Parameter | Type | Description |
|---|---|---|
container | HTMLElement | A <div> element you can render into. The host manages its lifecycle. |
api | PluginAPI | The API object for context access, events, and server communication. |
export function mount(container, api) {
container.innerHTML = '<h1>Hello!</h1>';
}Rules:
- You own the contents of
container— add any DOM elements, styles, or event listeners you need - Do not remove or replace the
containerelement itself - If your
mountthrows an error, the host catches it and displays an error message in the tab mountis called each time the tab becomes active
unmount(container)
Called when your plugin tab is closed, the plugin is disabled, or the component unmounts.
| Parameter | Type | Description |
|---|---|---|
container | HTMLElement | The same element passed to mount. |
export function unmount(container) {
// Clean up event listeners, intervals, subscriptions
container.innerHTML = '';
}Rules:
- This export is optional — if you don't export it, the host skips cleanup
- Always unsubscribe from
onContextChangehere to prevent memory leaks - Clear any
setIntervalorsetTimeouthandles - Remove any global event listeners you added
The Plugin API Object
The api object provides three capabilities: reading context, subscribing to changes, and making server calls.
api.context
A read-only property that returns the current context snapshot.
type PluginContext = {
theme: 'dark' | 'light';
project: { name: string; path: string } | null;
session: { id: string; title: string } | null;
};Usage:
export function mount(container, api) {
const ctx = api.context;
console.log(ctx.theme); // 'dark' or 'light'
console.log(ctx.project?.name); // 'my-project' or undefined
console.log(ctx.project?.path); // '/home/user/my-project' or undefined
console.log(ctx.session?.id); // 'abc123' or undefined
console.log(ctx.session?.title); // 'Session 1' or undefined
}Context fields:
| Field | Type | Description |
|---|---|---|
theme | 'dark' | 'light' | Current UI theme. Updates when the user toggles dark mode. |
project | object | null | Currently selected project. null if no project is selected. |
project.name | string | Project display name. |
project.path | string | Absolute path to the project directory on the server. |
session | object | null | Currently active session. null if no session is active. |
session.id | string | Unique session identifier. |
session.title | string | Session display title. |
Note:
api.contextalways returns the latest values. Each access reads the current state — it's not a snapshot that goes stale.
api.onContextChange(callback)
Subscribe to context changes. Your callback is called whenever the theme, project, or session changes.
| Parameter | Type | Description |
|---|---|---|
callback | (ctx: PluginContext) => void | Called with the new context on every change. |
| Returns | () => void | An unsubscribe function. Call it to stop receiving updates. |
export function mount(container, api) {
const unsubscribe = api.onContextChange((ctx) => {
if (ctx.theme === 'dark') {
container.style.backgroundColor = '#1a1a1a';
container.style.color = '#fff';
} else {
container.style.backgroundColor = '#fff';
container.style.color = '#000';
}
if (ctx.project) {
loadProjectData(ctx.project.path);
}
});
// IMPORTANT: Store unsubscribe for cleanup
container._unsubscribe = unsubscribe;
}
export function unmount(container) {
if (container._unsubscribe) {
container._unsubscribe();
}
}Behavior:
- The callback is NOT called immediately on subscribe — only on subsequent changes
- To get the initial values, read
api.contextin yourmountfunction - You can register multiple callbacks — each gets its own unsubscribe function
- All callbacks are cleared when the component unmounts, but it's best practice to unsubscribe explicitly
api.rpc(method, path, body?)
Make an HTTP request to your plugin's backend server, proxied through the host.
| Parameter | Type | Description |
|---|---|---|
method | string | HTTP method: 'GET', 'POST', 'PUT', 'DELETE', etc. |
path | string | Request path on your server. Leading slashes are stripped automatically. |
body | unknown (optional) | Request body. Serialized as JSON. |
| Returns | Promise<unknown> | Parsed JSON response from your server. |
// GET request
const stats = await api.rpc('GET', '/stats?path=/home/user/project');
// GET with query params
const results = await api.rpc('GET', `/search?q=${encodeURIComponent(query)}`);
// POST with body
const result = await api.rpc('POST', '/analyze', {
files: ['src/index.ts', 'src/utils.ts'],
depth: 3
});
// DELETE
await api.rpc('DELETE', '/cache');How it works under the hood:
- Your call to
api.rpc('GET', '/stats?path=...')becomes a fetch to/api/plugins/your-plugin-name/rpc/stats?path=... - The host server authenticates the request using the user's session
- The host injects any configured secrets as
x-plugin-secret-*headers - The request is proxied to your plugin server on
127.0.0.1:<port> - The response is streamed back to the browser
Error handling:
try {
const data = await api.rpc('GET', '/data');
renderData(data);
} catch (err) {
// Common errors:
// - Plugin server not running (503)
// - Plugin server error (502)
// - Network error
showError(err.message);
}Important notes:
api.rpconly works if your plugin has aserverentry in the manifest- If your server isn't running, the host will attempt to lazy-start it
- The host handles authentication — your server doesn't need to validate tokens
- Query strings in the
pathparameter are preserved and forwarded
Patterns and Best Practices
Theme-Aware Rendering
export function mount(container, api) {
const render = (ctx) => {
const isDark = ctx.theme === 'dark';
container.innerHTML = `
<div style="
padding: 20px;
background: ${isDark ? '#1e1e1e' : '#ffffff'};
color: ${isDark ? '#d4d4d4' : '#1e1e1e'};
">
<h2>My Plugin</h2>
</div>
`;
};
render(api.context);
container._unsub = api.onContextChange(render);
}
export function unmount(container) {
container._unsub?.();
container.innerHTML = '';
}Project-Aware Data Loading
export function mount(container, api) {
let currentPath = null;
const loadData = async (projectPath) => {
if (projectPath === currentPath) return;
currentPath = projectPath;
container.querySelector('#content').textContent = 'Loading...';
try {
const data = await api.rpc('GET', `/analyze?path=${encodeURIComponent(projectPath)}`);
container.querySelector('#content').textContent = JSON.stringify(data, null, 2);
} catch (err) {
container.querySelector('#content').textContent = `Error: ${err.message}`;
}
};
container.innerHTML = '<pre id="content">Select a project...</pre>';
if (api.context.project) {
loadData(api.context.project.path);
}
container._unsub = api.onContextChange((ctx) => {
if (ctx.project) {
loadData(ctx.project.path);
} else {
currentPath = null;
container.querySelector('#content').textContent = 'Select a project...';
}
});
}For a complete real-world example of these patterns, see Example: Project Stats.
Loading Assets from Your Plugin Directory
You can load CSS, fonts, images, or other files from your plugin directory:
export function mount(container, api) {
// Load a stylesheet
if (!document.querySelector('#my-plugin-styles')) {
const link = document.createElement('link');
link.id = 'my-plugin-styles';
link.rel = 'stylesheet';
link.href = `/api/plugins/my-plugin/assets/styles.css`;
document.head.appendChild(link);
}
// Load an image
container.innerHTML = `
<img src="/api/plugins/my-plugin/assets/logo.png" alt="Logo" />
`;
}Next Steps
- Backend Servers — Add a server for filesystem access, external APIs, and more
- Manifest Reference — All configuration options
- Security Model — How your plugin is isolated