Files

5.2 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 View plugins menu 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', {
      icon: 'ST',
      label: 'Standup',
      run: (actionContext) => api.messages.send('Standup: yesterday, today, blocked', actionContext.textChannel?.id)
    })
  );
}

The action appears as a tile in the server side panel's View plugins menu and runs with source: 'toolbarAction'.

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.