Skip to main content

Overview

The Event Bus (api.bus) is a simple pub/sub system that lets plugins communicate without importing each other.
// Plugin A emits an event
api.bus.emit('todo:added', { text: 'Buy milk', done: false });

// Plugin B listens for it
api.bus.on('todo:added', (data) => {
  console.log('New todo:', data.text);
});

API

bus.on(event, callback)

Subscribe to an event.
api.bus.on('theme:changed', (data) => {
  document.body.style.background = data.color;
});

bus.off(event, callback)

Unsubscribe. Must pass the same function reference.
function handler(data) { /* ... */ }

api.bus.on('my-event', handler);
// later:
api.bus.off('my-event', handler);

bus.once(event, callback)

Subscribe to an event, but auto-remove the listener after the first emission.
api.bus.once('board:ready', (data) => {
  console.log('Ready! (only fires once)');
});

bus.emit(event, data)

Publish an event to all subscribers. Errors in listeners are caught and logged without interrupting other listeners.
api.bus.emit('theme:changed', { color: '#1a1a2e' });

bus.removeAll([event])

Remove all listeners for a specific event, or all events if no name is given.
api.bus.removeAll('todo:added');  // Clear all listeners for this event
api.bus.removeAll();              // Nuclear option: clear everything

Naming Convention

Use namespace:action to avoid collisions between plugins:
✅ Good❌ Bad
todo:addedadded
kanban:card:movedmove
theme:changedchange
sticky-note:creatednew

Built-in Events

EventPayloadWhen
board:ready{ boardEl }Board initialized
board:allPluginsLoaded{ total, enabled }All plugins loaded
board:restartedBoard restarted via api.restart()
board:resize{ width, height }Window resized
plugin:loaded{ id, meta, container }Single plugin loaded
plugin:unloadpluginIdPlugin toggled off or deleted
plugin:unloadedpluginIdPlugin fully torn down
plugin:docked{ pluginId, el, target }Plugin docked
plugin:undocked{ pluginId, el }Plugin undocked
plugin:updated{ pluginId, el }Plugin container modified
plugin:dragstart{ el }Drag started
plugin:dragend{ el }Drag ended
contextmenu:open{ x, y, menu }Right-click menu opened
storage:change{ key, value, oldValue, pluginId? }Storage value changed
See Built-in Events for full details.

Example: Cross-Plugin Communication

// === todo-list/plugin.js ===
export function setup(api) {
  const todos = [];

  function addTodo(text) {
    todos.push({ text, done: false });
    api.bus.emit('todo:added', { text, count: todos.length });
  }
}

// === stats/plugin.js ===
export function setup(api) {
  let count = 0;

  api.bus.on('todo:added', (data) => {
    count = data.count;
    renderStats();
  });
}
Plugins should never directly import or reference each other. Always use the event bus for communication.