Files
Toju/docs-site/docs/plugin-development/examples.md
2026-04-29 17:15:01 +02:00

4.9 KiB

sidebar_position
sidebar_position
5

Examples

Toolbar Message Plugin

toju-plugin.json

{
  "schemaVersion": 1,
  "id": "example.toolbar-message",
  "title": "Toolbar Message",
  "description": "Adds a toolbar action that sends a reusable message.",
  "version": "1.0.0",
  "kind": "client",
  "scope": "client",
  "apiVersion": "1.0.0",
  "compatibility": {
    "minimumTojuVersion": "1.0.0",
    "verifiedTojuVersion": "1.0.0"
  },
  "entrypoint": "./main.js",
  "capabilities": ["messages.send", "ui.pages"]
}

main.js

export function activate(context) {
  const { api } = context;

  context.subscriptions.push(api.ui.registerToolbarAction('standup-message', {
    label: 'Standup',
    run: () => api.messages.send('Standup: yesterday, today, blocked')
  }));
}

Settings Page Plugin

{
  "schemaVersion": 1,
  "id": "example.settings-page",
  "title": "Settings Page Example",
  "description": "Adds a plugin settings page and stores a local preference.",
  "version": "1.0.0",
  "kind": "client",
  "apiVersion": "1.0.0",
  "compatibility": { "minimumTojuVersion": "1.0.0" },
  "entrypoint": "./main.js",
  "capabilities": ["ui.settings", "storage.local"],
  "settings": {
    "type": "object",
    "properties": {
      "enabled": { "type": "boolean", "default": true }
    }
  }
}
export function activate(context) {
  const { api } = context;

  context.subscriptions.push(api.ui.registerSettingsPage('preferences', {
    label: 'Example Preferences',
    render: () => {
      const root = document.createElement('section');
      const button = document.createElement('button');

      button.type = 'button';
      button.textContent = 'Remember preference';
      button.onclick = () => api.storage.set('enabled', true);
      root.append(button);
      return root;
    }
  }));
}

Server-Scoped Soundboard

A server-scoped plugin can be installed as a server requirement and auto-installed for server members when marked required.

{
  "schemaVersion": 1,
  "id": "example.soundboard",
  "title": "Server Soundboard",
  "description": "Adds a soundboard side panel and announces played sounds.",
  "version": "1.0.0",
  "kind": "client",
  "scope": "server",
  "apiVersion": "1.0.0",
  "compatibility": { "minimumTojuVersion": "1.0.0" },
  "entrypoint": "./main.js",
  "capabilities": [
    "server.read",
    "users.manage",
    "ui.sidePanel",
    "media.playAudio",
    "messages.send"
  ],
  "pluginUser": {
    "displayName": "Soundboard",
    "label": "Audio helper"
  }
}
export function activate(context) {
  const { api } = context;
  const botId = api.server.registerPluginUser({
    id: 'soundboard-bot',
    displayName: 'Soundboard'
  });

  context.subscriptions.push(api.ui.registerSidePanel('sounds', {
    label: 'Soundboard',
    render: () => {
      const panel = document.createElement('div');
      const button = document.createElement('button');

      button.type = 'button';
      button.textContent = 'Play chime';
      button.onclick = async () => {
        await api.media.playAudioClip({ url: './chime.wav', volume: 0.7 });
        api.messages.sendAsPluginUser({ pluginUserId: botId, content: 'Played chime' });
      };

      panel.append(button);
      return panel;
    }
  }));
}

Message Bus Plugin

{
  "schemaVersion": 1,
  "id": "example.poll-bus",
  "title": "Poll Bus",
  "description": "Uses the plugin message bus for lightweight P2P poll votes.",
  "version": "1.0.0",
  "kind": "client",
  "apiVersion": "1.0.0",
  "compatibility": { "minimumTojuVersion": "1.0.0" },
  "entrypoint": "./main.js",
  "capabilities": ["events.p2p.publish", "events.p2p.subscribe", "messages.read"]
}
export function activate(context) {
  const { api } = context;

  context.subscriptions.push(api.messageBus.subscribe({
    topic: 'poll:votes',
    replayLatest: true,
    latestMessageLimit: 20,
    handler: (event) => api.logger.info('Vote received', event.payload)
  }));

  api.messageBus.publish({
    topic: 'poll:votes',
    payload: { option: 'A' },
    includeLatestMessages: true,
    includeSelf: true,
    latestMessageLimit: 20
  });
}

Custom DOM Mount

Use ui.dom sparingly and cleanly. The runtime tags mounted elements with plugin ownership metadata and removes remaining mounted elements when the plugin unloads.

export function activate(context) {
  const badge = document.createElement('div');

  badge.textContent = 'Plugin active';
  badge.style.position = 'absolute';
  badge.style.right = '1rem';
  badge.style.bottom = '1rem';

  context.subscriptions.push(context.api.ui.mountElement('active-badge', {
    target: 'body',
    element: badge
  }));
}

All-API Fixture

The repo includes an E2E fixture at toju-app/public/plugins/e2e-all-api/. It intentionally calls every public plugin API surface so Playwright coverage can validate the runtime. Use it as a compatibility reference, not as the minimal style for production plugins.