Redactor uses a single in-editor event bus.
Event names use colons in docs (editor:ready). The legacy dot form (editor.ready) is equivalent — both map to the same listener.
Event pages format#
Each event is documented as:
- Short description — when the event fires.
Type—signalorhook(and whetherstop()can cancel).Payload— bullet list of fields the handler receives (not a raw object literal). Optional fields are marked.Example— practical handler code: what you can read from the payload and what to return or call.
Handlers always receive stop as well (see Event bus API); it is listed only for hooks where the emitter checks cancellation.
Quick start#
const app = Redactor('#entry');
app.on('editor:ready', () => {
console.log('Editor is ready');
});
app.on('blocks:change', () => {
console.log('Document changed');
});
Subscribe at init#
Pass an events map in editor options. Handlers are registered before the editor loads and are bound to the app instance, so this inside a plain function is the editor.
const app = Redactor('#entry', {
events: {
'editor:ready'() {
this.statusbar.add('status', 'Ready');
},
'blocks:change'() {
console.log('changed');
}
}
});
Legacy alias: subscribe (same shape as events).
Use this when integration code lives next to editor config and you do not need to unsubscribe later.
Subscribe after init#
Call app.on any time on the instance returned from Redactor(...).
const app = Redactor('#entry');
app.on('editor:change', ({ html }) => {
console.log(html);
});
Remove a specific handler with app.off(event, handler) when you no longer need it.
Subscribe in a plugin#
In start() (or the constructor for very early hooks), use this.app.on like application code.
export class TypographerPlugin {
constructor(app) {
this.app = app;
this._onKeyUp = (payload) => this._handleKeyUp(payload);
}
start() {
this.app.on('key:up', this._onKeyUp);
}
stop() {
this.app.off('key:up', this._onKeyUp);
}
}
Keep a reference to the handler if you unsubscribe in stop().
Emit from a plugin#
Publish on the shared bus with this.app.emit. Use a clear namespaced name — usually pluginKey:action.
start() {
const outline = this._buildOutline();
this.app.emit('minimap:outline', { outline });
}
Listeners receive one object: your payload fields plus stop (see Event bus API).
Listen to another plugin’s events#
Any code (app integration or another plugin) subscribes to the same global name. No imports or direct references between plugins are required.
// Plugin A
this.app.emit('reports:generated', { items });
// Plugin B — in start()
this.app.on('reports:generated', ({ items }) => {
this._renderSidebar(items);
});
Event bus API#
All methods accept a single event name or an array of names (same handler for each).
| Method | Role |
|---|---|
on(event, handler) |
Subscribe until off. Handlers run in registration order (after onFirst handlers). |
onFirst(event, handler) |
Same as on, but runs before other on handlers for that event. |
once(event, handler) |
Runs on the next emit only, then removed. |
off(event, handler) |
Unsubscribe one handler from on and once. |
emit(event, data?) |
Call all handlers synchronously. Returns the final payload. |
emitWithStop(event, data?) |
Like emit, but returns { payload, stopped } so the emitter can tell if a handler called stop(). |
emitAsync(event, data?) |
Like emit, but awaits each handler (use for async hooks). |
Payload and stop
Every handler is called as:
handler({ ...data, stop })
stop()— aborts the rest of the chain for this emit.- Return value — if a handler returns an object, it becomes the payload for the next handler (and the value returned from
emit).
once handlers are included in the same pass as on handlers and are cleared after that emit.
Examples
// Run once when the DOM is ready
app.once('dom:ready', () => initSidebar());
// Run before other listeners
app.onFirst('output:before:html', ({ html }) => ({ html: html.trim() }));
// Emitter checks cancellation
const { stopped } = app.emitWithStop('autosave:before:send', { data });
if (stopped) return;
// Async hook chain
const result = await app.emitAsync('upload:before:send', { file });
Hook events#
Many *:before:* events are hooks: you can change the payload or cancel the operation.
Transform — return an object; the emitter merges it into the next step (parser:before:html replaces html before loading content).
Cancel — call stop(); the emitter skips the default action (autosave:before:send aborts the HTTP request when stopped is true).
Real example — autosave:before:send (emitted in Autosave._send via emitWithStop):
const app = Redactor('#entry', {
autosave: {
url: '/api/save',
name: 'content'
}
});
app.on('autosave:before:send', (payload) => {
const { data, name, stop } = payload;
// Cancel: editor is read-only on this page — do not send
if (document.body.dataset.readonly === '1') {
stop();
return;
}
// Transform: add metadata to the request body
return {
data: {
...data,
meta: {
revision: getRevision(),
saved_at: Date.now()
}
}
};
});
stop()—Autosaveseesstopped: trueand returns without callingurlorhandler.- Return value — updated
datais what gets sent on the wire.