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.

ParameterTypeDescription
containerHTMLElementA <div> element you can render into. The host manages its lifecycle.
apiPluginAPIThe API object for context access, events, and server communication.
javascript
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 container element itself
  • If your mount throws an error, the host catches it and displays an error message in the tab
  • mount is called each time the tab becomes active

unmount(container)

Called when your plugin tab is closed, the plugin is disabled, or the component unmounts.

ParameterTypeDescription
containerHTMLElementThe same element passed to mount.
javascript
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 onContextChange here to prevent memory leaks
  • Clear any setInterval or setTimeout handles
  • 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.

typescript
type PluginContext = {
  theme: 'dark' | 'light';
  project: { name: string; path: string } | null;
  session: { id: string; title: string } | null;
};

Usage:

javascript
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:

FieldTypeDescription
theme'dark' | 'light'Current UI theme. Updates when the user toggles dark mode.
projectobject | nullCurrently selected project. null if no project is selected.
project.namestringProject display name.
project.pathstringAbsolute path to the project directory on the server.
sessionobject | nullCurrently active session. null if no session is active.
session.idstringUnique session identifier.
session.titlestringSession display title.

Note: api.context always 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.

ParameterTypeDescription
callback(ctx: PluginContext) => voidCalled with the new context on every change.
Returns() => voidAn unsubscribe function. Call it to stop receiving updates.
javascript
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.context in your mount function
  • 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.

ParameterTypeDescription
methodstringHTTP method: 'GET', 'POST', 'PUT', 'DELETE', etc.
pathstringRequest path on your server. Leading slashes are stripped automatically.
bodyunknown (optional)Request body. Serialized as JSON.
ReturnsPromise<unknown>Parsed JSON response from your server.
javascript
// 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:

  1. Your call to api.rpc('GET', '/stats?path=...') becomes a fetch to /api/plugins/your-plugin-name/rpc/stats?path=...
  2. The host server authenticates the request using the user's session
  3. The host injects any configured secrets as x-plugin-secret-* headers
  4. The request is proxied to your plugin server on 127.0.0.1:<port>
  5. The response is streamed back to the browser

Error handling:

javascript
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.rpc only works if your plugin has a server entry 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 path parameter are preserved and forwarded

Patterns and Best Practices

Theme-Aware Rendering

javascript
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

javascript
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:

javascript
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

Last updated March 9, 2026