← Plugin docs

API Reference

Every method on the PluginAPI object passed to activate(api).

Your plugin's activate function receives one argument: a PluginAPI object. This page documents every method on it. The full TypeScript types live in the @projelli/plugin-api npm package, so your editor will show signatures, parameter names, and JSDoc as you type.

Methods that touch host data return Promises (the call round-trips across postMessage). Methods that just register UI return void.

Namespaces

Top-level fields

api.pluginId

readonly string. Your plugin's id, copied from manifest.json. Useful for namespacing log output and storage keys.

api.version

readonly string. Your plugin's version, copied from manifest.json.

commandsno permission

commands.register(id, handler): void

Register a command handler. The id should be namespaced to your plugin (e.g. my-plugin.do-thing). The handler can be sync or async; whatever it returns is the resolved value of commands.invoke.

api.commands.register('my-plugin.hello', () => {
  api.notify.info('Hello!');
  return 'done';
});

commands.invoke(id, payload?): Promise<unknown>

Invoke any registered command (yours or another plugin's, if you know its id). Returns whatever the handler returned.

const result = await api.commands.invoke('my-plugin.hello');

toolbarno permission

toolbar.addButton(spec): void

Add a button to the editor toolbar. Spec fields: id, icon (a Lucide icon name), tooltip, command (the command id to invoke on click).

api.toolbar.addButton({
  id: 'my-button',
  icon: 'sparkles',
  tooltip: 'Run my plugin',
  command: 'my-plugin.hello',
});

Icon names: anything in Lucide's icon set. Use lowercase, hyphenated names (chevron-down, not ChevronDown).

toolbar.removeButton(id): void

Remove a button you previously added. Does nothing if the id isn't found.

sidebar.addPanel(spec): void

Add (or replace) a panel in the right sidebar. Spec fields: id, title, html (inline HTML rendered inside a sandboxed iframe). Calling addPanel with an existing id replaces the panel's content.

api.sidebar.addPanel({
  id: 'my-panel',
  title: 'My Panel',
  html: '<div style="padding: 16px;">Hello sidebar</div>',
});

The iframe is sandboxed: scripts inside the panel cannot reach the worker or the main thread. To make a panel interactive, use a toolbar button or a registered command (the user clicks, the worker reacts, the panel re-renders). v2.1 may add panel-to-worker messaging.

sidebar.removePanel(id): void

Remove a panel. Does nothing if the id isn't found.

editor

editor.getSelection(): Promise<PluginEditorSelection | null>editor:selection

Get the current selection in the active editor. Returns null if there's no active editor or no selection.

const sel = await api.editor.getSelection();
if (sel) console.log(sel.text, sel.range);

The returned object has text: string and range: { startLine, startColumn, endLine, endColumn }. Lines and columns are 1-based.

editor.getContent(): Promise<string>editor:selection

Get the full text of the active document. Throws if there's no active editor.

const content = await api.editor.getContent();

editor.replaceSelection(text): Promise<void>editor:write

Replace the current selection with new text. If there's no selection, behaves like insertAtCursor.

await api.editor.replaceSelection('new text');

editor.insertAtCursor(text): Promise<void>editor:write

Insert text at the cursor position.

workspace

workspace.listFiles(path?): Promise<FileNode[]>workspace:read

List files and folders at the given path (defaults to workspace root). Returns an array of FileNode objects.

const files = await api.workspace.listFiles('notes/');

workspace.readFile(path): Promise<string>workspace:read

Read a file's text content. Path is relative to the workspace root.

const text = await api.workspace.readFile('notes/today.md');

workspace.writeFile(path, content): Promise<void>workspace:write

Write a file. Creates parent folders as needed. Overwrites any existing file at that path.

await api.workspace.writeFile('notes/draft.md', '# Draft\n');

ai

ai.invoke(opts): Promise<string>ai:invoke

Send a prompt to the user's configured AI provider. Returns the model's text response.

Options: prompt: string (required), system?: string (optional system prompt).

const reply = await api.ai.invoke({
  system: 'You are a precise translator.',
  prompt: 'Translate "hello" into French.',
});

The plugin never sees the user's API key. Calls are billed against the user's provider account.

storageno permission

Per-plugin key/value storage. Keys are scoped to your plugin id; you can't see other plugins' data.

storage.get(key): Promise<unknown>

Read a stored value. Returns undefined if not set.

storage.set(key, value): Promise<void>

Write a value. Must be JSON-serializable.

await api.storage.set('lastRun', Date.now());
const last = await api.storage.get('lastRun');

storage.remove(key): Promise<void>

Delete a stored value.

network

network.fetch(url, opts?): Promise<PluginNetworkResponse>network

Make an HTTP request from the worker. Accepts the same options as the standard fetch (method, headers, body). The body is always materialized; no streaming in v2.0.

const res = await api.network.fetch('https://api.example.com/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ q: 'hello' }),
});
if (res.ok) console.log(res.body);

Response shape: { status, statusText, headers, body, bodyEncoding, url, ok }. bodyEncoding is 'utf-8' for text responses or 'base64' for binary.

settingsno permission

settings.addPage(spec): void

Register a settings page in the Plugins panel. Spec: id, title, schema (a record of field id to field spec). Each field has type ('string' | 'number' | 'boolean' | 'select'), default, label, and choices (required when type is 'select').

api.settings.addPage({
  id: 'my-plugin-settings',
  title: 'My Plugin',
  schema: {
    targetLanguage: {
      type: 'select',
      default: 'Spanish',
      label: 'Target language',
      choices: ['Spanish', 'French', 'German'],
    },
  },
});

settings.get(key): Promise<unknown>

Read a setting value. Falls back to the schema default if the user hasn't changed it.

settings.set(key, value): Promise<void>

Write a setting value. Useful for plugins that update their own settings programmatically.

notifyno permission

Show a toast notification.

notify.info(msg): void

Blue toast.

notify.warn(msg): void

Yellow toast.

notify.error(msg): void

Red toast.

api.notify.info('Done!');
api.notify.warn('Selection is empty.');
api.notify.error('Network request failed.');

Module shape

Your src/index.ts must default-export an object that matches the PluginModule interface:

export default {
  async activate(api: PluginAPI) {
    // setup
  },
  async deactivate() {
    // optional cleanup, called before disable / uninstall / update
  },
};

activate runs once per worker lifetime. deactivate is optional. The bundle must be an ES module (the scaffolder configures this for you).

Next: Publishing →