This guide helps you move an existing Redactor 4 integration to Redactor 5. It focuses on settings, the public API, and events — the surfaces most integrations and plugins use.
Redactor 5 keeps the same entry point (Redactor('#entry', options) → app), but several option names, method paths, and event names changed. Prefer the 5.x names in new code; where noted, older forms still work as aliases.
Related references:
What changed at a glance#
| Area | Redactor 4 | Redactor 5 |
|---|---|---|
| Content API | Nested under app.editor.* |
Methods live directly on app |
| Undo stack | app.state |
app.history |
| Low-level helpers | app.create('selection') etc. |
app.getService('selection') etc. |
| Menu lists | popups.* |
dropdown.* |
| Events config | subscribe: { … } |
Prefer events: { … } (subscribe still works) |
| Event names | Dotted (editor.load) |
Colon form in docs (editor:ready); dots remain a notation alias for the same segments |
| Plugins | Object-literal plugins | Class plugins (constructor / start / stop) |
| Premium add-ons | Separate plugins | Suites + expanded premium surface (Assistant, Review, Versions, …) |
Settings#
Update constructor options first. Most options keep the same meaning; several were renamed, nested, or replaced.
Renamed or reshaped
| Redactor 4 | Redactor 5 | Notes |
|---|---|---|
clicktoedit |
clickToEdit |
CamelCase. Works on <div> hosts; ignored on <textarea>. |
popups.format |
dropdown.format |
Format menu item list |
popups.inline |
dropdown.inline |
Inline menu item list |
popups.addbar |
dropdown.addbar |
“+” menu order |
sync: false |
sync: { enabled: false } |
Boolean → object |
syncDelay |
sync.debounce |
Same idea (ms) |
state.limit |
history.limit |
Undo stack size |
addPosition: 'top'\|'bottom' |
contentInsert: 'start'\|'end' |
Values renamed |
clean.enterinline |
clean.enterInline |
Re-test behavior — docs/default differ from 4.x |
link.target: '_blank' |
link.newTab: true |
UI / create-form default |
link.size |
link.truncate |
Display truncation (default 30 → 24) |
image.wrapWithStyle |
wrapWithStyle |
Top-level; may be true or an object |
paste.stripAttr |
paste.sanitize |
Richer sanitize object |
colorpicker |
colors |
Palette config (UI sizing knobs removed from docs) |
autosave.interval (as primary delay) |
autosave.debounce |
Optional interval still exists for periodic saves |
command: false |
— | Use app.triggers.unregister('/') after init |
// Redactor 4
Redactor('#entry', {
clicktoedit: true,
sync: false,
syncDelay: 100,
state: { limit: 100 },
addPosition: 'bottom',
popups: {
format: ['text', 'h1', 'h2', 'h3']
}
});
// Redactor 5
Redactor('#entry', {
clickToEdit: true,
sync: { enabled: false, debounce: 100 },
history: { limit: 100 },
contentInsert: 'end',
dropdown: {
format: ['text', 'h1', 'h2', 'h3']
}
});
Removed from settings docs (verify in your build)
These were documented in 4.x and no longer have a dedicated settings page in 5.x. Do not assume runtime parity — replace or drop them deliberately:
classes, reorder, structure, grammarly, scrollOverflow, block.outline, paragraphizer, global drop (use image.drop), image.states, embed.script.
New options worth knowing
| Option | Role |
|---|---|
ai |
AI Commands / backend integration |
hostTabs |
Host panels next to the editor |
themeStore |
Persist theme in localStorage |
visualCaret |
Custom caret behavior |
blockUid / trackedRefs |
Stable block ids / ref tracking |
activeState |
Toolbar active-state rules |
buttons.toolbar |
Explicit main toolbar button list |
addbarItems |
Custom addbar entries at init |
Plugins accept a string array as before, or a map with per-plugin options:
Redactor('#entry', {
plugins: {
counter: { plugin: CounterPlugin, options: { /* … */ } }
}
});
See Plugins settings and AI settings.
Public API#
Calling patterns
| Concern | Redactor 4 | Redactor 5 |
|---|---|---|
| Content / focus | app.editor.setContent(…) |
app.setContent(…) |
| Undo / redo | app.state.undo() |
app.history.undo() |
| Services | app.create('selection') |
app.getService('selection') |
| Insertion helper | app.create('insertion') |
app.getService('inserter') |
| Options (plugins) | this.opts.get('x') |
this.app.getOption('x') |
| Lang (plugins) | this.lang.get('x') |
this.app.getLang('x') |
| Custom events | this.app.broadcast('name', data) |
this.app.emit('name', data) |
| Containers | app.container.get('toolbar') |
app.host.getToolbar() (and related getters) |
Modules such as toolbar, block, and addbar still hang off app (app.toolbar.add(…)). You can also use app.getModule('toolbar') or app.invoke('toolbar.add', …).
Content and editor methods
| Redactor 4 | Redactor 5 |
|---|---|
app.editor.setContent({ html }) |
app.setContent(html) |
app.editor.setJson(…) |
app.setJson(…) |
app.editor.setEmpty() |
app.setEmpty() |
app.editor.getContent() |
app.getHtml() or app.getContent('html') |
app.editor.getJson() |
app.getJson() |
app.editor.insertContent({ html }) |
app.insertHtml(html, options) |
app.editor.insertContent({ instance }) |
app.insertBlock(block, options) |
app.editor.setFocus(…) |
app.focusEditor(…) |
app.editor.setBlur() |
app.blurEditor() |
app.editor.hasFocus() |
app.hasFocus() |
app.editor.isEmpty() |
app.isEditorEmpty() |
app.editor.isSelectAll() |
app.isAllSelected() |
app.editor.save() / restore() |
app.saveSelection() / restoreSelection() |
app.disable() / enable() |
app.setEditorDisabled(true\|false) |
app.readonly() / editable() |
app.setEditorReadonly(true\|false) |
// Redactor 4
app.editor.setContent({ html: '<p>Hello</p>' });
const html = app.editor.getContent();
app.editor.insertContent({ html: '<p>More</p>' });
app.editor.setFocus('end');
// Redactor 5
app.setContent('<p>Hello</p>');
const html = app.getHtml();
app.insertHtml('<p>More</p>', { caret: 'end' });
app.focusEditor({ position: 'end' });
Modules and services
| Redactor 4 | Redactor 5 |
|---|---|
app.state.undo() / redo() |
app.history.undo() / redo() |
app.source.open() / close() |
app.source.show() / hide() |
app.source.is() |
app.source.isVisible() |
app.source.getElement() |
app.source.getTextarea() |
app.placeholder.set(…) |
app.placeholder.setEditorPlaceholder(…) |
app.toolbar.unsetActive(…) |
app.toolbar.clearActive(…) |
app.toolbar.disable / enable (by name) |
app.toolbar.setDisabled / clearDisabled |
block.getBlock() (element) |
block.getEl() |
app.create('selection') |
app.getService('selection') |
app.create('insertion') |
app.getService('inserter') |
app.create('caret') |
app.getService('caret') |
app.create('offset') |
app.getService('offset') |
app.create('utils') |
app.utils |
Block focus APIs moved toward explicit focus methods:
// Redactor 4 (typical)
app.block.set(el, 'end');
app.block.unset();
// Redactor 5
app.block.focus(el, { caret: 'end' });
app.block.clearFocus();
The Dom wrapper (app.$doc, jQuery-like chain) is no longer part of the public API docs — work with native elements and services.
Full module and service lists: API overview.
Events#
Notation and config
Docs use colons: editor:ready. The legacy dotted form (editor.ready) is equivalent when the segments are the same — editor.ready ↔ editor:ready.
That is only a notation alias. It does not rename events that changed identity. For example, editor.load is not the same as editor:ready.
| Redactor 4 | Redactor 5 |
|---|---|
subscribe: { 'editor.load': … } |
Prefer events: { 'editor:ready': … } |
this.app.broadcast('x', data) |
this.app.emit('x', data) |
event.get('key') / event.set(…) |
Destructured payload; return values / stop() for hooks |
// Redactor 4
Redactor('#entry', {
subscribe: {
'editor.load': function() {
// …
},
'editor.keydown': function(event) {
// …
}
}
});
// Redactor 5
Redactor('#entry', {
events: {
'editor:ready'() {
// …
},
'key:down'({ event }) {
// …
}
}
});
You can also subscribe after init with app.on / app.off — see Events overview.
Same name, new punctuation
These keep the same segments — switch dots to colons (or keep the dotted form as notation).
| Redactor 4 | Redactor 5 |
|---|---|
app.before.start |
app:before:start |
app.start |
app:start |
app.stop |
app:stop |
editor.focus |
editor:focus |
editor.blur |
editor:blur |
editor.change |
editor:change |
editor.empty |
editor:empty |
editor.mousedown |
editor:mousedown |
editor.mouseup |
editor:mouseup |
block.before.remove |
block:before:remove |
block.remove |
block:remove |
image.upload |
image:upload |
image.insert |
image:insert |
image.change |
image:change |
image.before.change |
image:before:change |
image.upload.error |
image:upload:error |
embed.insert |
embed:insert |
link.change |
link:change |
link.remove |
link:remove |
upload.before.send |
upload:before:send |
upload.error |
upload:error |
autosave.before.send |
autosave:before:send |
autosave.send |
autosave:send |
autosave.error |
autosave:error |
dropdown.before.open |
dropdown:before:open |
dropdown.open |
dropdown:open |
dropdown.close |
dropdown:close |
form.before.create |
form:before:create |
form.create |
form:create |
ai.create |
ai:create |
ai.before.send |
ai:before:send |
ai.complete |
ai:complete |
ai.before.insert |
ai:before:insert |
ai.insert |
ai:insert |
ai.discard |
ai:discard |
ai.stop |
ai:stop |
ai.error |
ai:error |
Renamed events (update handlers)
| Redactor 4 | Redactor 5 | Notes |
|---|---|---|
editor.load |
editor:ready |
Editor started and ready |
editor.insert |
inserter:insert |
Content inserted |
editor.keydown |
key:down |
|
editor.keyup |
key:up |
|
editor.click |
click |
|
editor.dragstart |
dnd:start |
|
editor.dragover |
dnd:over |
|
editor.dragleave |
dnd:leave |
|
editor.drop |
dnd:drop |
|
editor.before.copy |
clipboard:before:copy |
|
editor.copy |
clipboard:copy (also clipboard:after:copy) |
|
editor.before.cut |
clipboard:before:cut |
|
editor.cut |
clipboard:cut (also clipboard:after:cut) |
|
editor.before.paste / editor.paste |
clipboard:before:paste / clipboard:paste / clipboard:after:paste (+ cleaner:*:paste) |
Prefer clipboard + cleaner events |
block.set |
block:focus |
Block selected / focused |
block.unset |
block:focus:clear |
|
block.change |
blocks:change |
Document-level change signal |
inline.set |
inline:format |
|
format.set |
blocks:format or inline:format |
Split by concern |
link.add |
link:set |
|
image.width |
image:dimensions |
|
image.link / image.unlink |
link:set / link:remove (often with image scope) |
|
upload.complete |
upload:success |
|
source.open / source.close |
source:show / source:hide |
|
toolbar.sticky / toolbar.static |
toolbar:sticky:start / toolbar:sticky:end |
|
state.undo / state.redo |
history:restore |
Single restore hook (no separate undo/redo events) |
For “document changed” listeners, prefer blocks:change in new code (you can still listen to editor:change).
Parse / output pipeline
HTML parse and unparse hooks were split into cleaner, parser, and output events. Map carefully and re-test:
| Redactor 4 | Start here in 5.x |
|---|---|
editor.before.parse / editor.parse |
cleaner:before:input / cleaner:after:input, parser:before:html, parser:loaded |
editor.before.unparse / editor.unparse |
output:prepare:html, output:before:html |
editor.before.load |
Often parser:before:html or wait for editor:ready |
Events without a direct replacement
No dedicated 5.x counterpart is documented for these 4.x events:
editor.mouseoverblock.duplicatelist.itemembed.changesource.before.opensource.before.closesource.changeapp.before.stoplink.clickimage.click
For click-related cases, use the generic click event or focus events and filter by target or block type.
Plugins#
Rewrite object-literal plugins as classes with constructor(app, options), start(), and optional load() / stop().
// Redactor 4 (concept)
Redactor.add('plugin', 'hello', {
start() {
this.app.toolbar.add('hello', { title: 'Hi', command: 'hello.run' });
},
run() { /* … */ }
});
// Redactor 5
class HelloPlugin {
constructor(app, options = {}) {
this.app = app;
this.options = options;
}
start() {
this.app.toolbar.add('hello', {
title: 'Hi',
command: 'hello.run'
});
}
run() {
this.app.insertHtml('<p>Hello</p>', { caret: 'end' });
}
}
Redactor('#entry', {
plugins: ['hello'],
// or: plugins: { hello: { plugin: HelloPlugin } }
});
Inside plugins, replace this.opts / this.lang / this.app.create(…) / broadcast with the 5.x patterns above. Full walkthrough: Create a plugin.
Suggested migration steps#
- Swap the Redactor 5 package (CSS/JS) and open the page — fix init errors first.
- Update settings renames (
clickToEdit,dropdown,sync,history, …). - Replace
app.editor.*andapp.state.*call sites. - Replace
app.create(…)withgetService/app.utils. - Move
subscribe→events, update renamed event names (especiallyeditor.load, keys, clipboard, DnD, block focus). - Port custom plugins to classes; re-test toolbar commands and services.
- Re-check paste/clean/output hooks if you transform HTML on the way in or out.
- Optionally adopt new surfaces: Host tabs, JSON content, Premium features.
If something still fails after these steps, compare the old call with the matching page under Settings, API, or Events — method and payload shapes may have changed even when the feature still exists.