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:added | added |
kanban:card:moved | move |
theme:changed | change |
sticky-note:created | new |
Built-in Events
| Event | Payload | When |
|---|
board:ready | { boardEl } | Board initialized |
board:allPluginsLoaded | { total, enabled } | All plugins loaded |
board:restarted | — | Board restarted via api.restart() |
board:resize | { width, height } | Window resized |
plugin:loaded | { id, meta, container } | Single plugin loaded |
plugin:unload | pluginId | Plugin toggled off or deleted |
plugin:unloaded | pluginId | Plugin 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.