codex-team/editor.js · error · Error
Shortcut ${shortcut.name} is already registered for ${shortc
Error message
Shortcut ${shortcut.name} is already registered for ${shortcut.on}. Please remove it before add a new handler. What it means
Editor.js's Shortcuts registry (used for keyboard shortcut handling on editor blocks) throws this when you attempt to register a shortcut with a name that is already registered for the same event target (the on field, e.g. 'keydown'). The API is add/remove based, so re-registering requires an explicit removal first.
Source
Thrown at src/components/utils/shortcuts.ts:57
*/
class Shortcuts {
/**
* All registered shortcuts
*
* @type {Map<Element, Shortcut[]>}
*/
private registeredShortcuts: Map<Element, Shortcut[]> = new Map();
/**
* Register shortcut
*
* @param shortcut - shortcut options
*/
public add(shortcut: ShortcutData): void {
const foundShortcut = this.findShortcut(shortcut.on, shortcut.name);
if (foundShortcut) {
throw Error(
`Shortcut ${shortcut.name} is already registered for ${shortcut.on}. Please remove it before add a new handler.`
);
}
const newShortcut = new Shortcut({
name: shortcut.name,
on: shortcut.on,
callback: shortcut.handler,
});
const shortcuts = this.registeredShortcuts.get(shortcut.on) || [];
this.registeredShortcuts.set(shortcut.on, [...shortcuts, newShortcut]);
}
/**
* Remove shortcut
*
* @param element - Element shortcut is set forView on GitHub (pinned to 5f45dabbe5)
Solutions
- Guard the registration: call findShortcut(on, name) (or track registered names yourself) and only add when not already present.
- Call shortcuts.remove(name, on) before shortcuts.add(...) when re-registering intentionally.
- In framework components, register shortcuts once (empty deps in React useEffect) and clean up with shortcuts.remove on unmount to avoid duplicate registrations on remount.
- If the error comes from a third-party tool's lifecycle, ensure the editor/tool is destroyed properly (editor.destroy()) before re-initializing instead of stacking instances.
Example fix
// before
shortcuts.add({ name: 'CMD+B', on: 'keydown', handler: this.handleB });
// after
if (!shortcuts.findShortcut('keydown', 'CMD+B')) {
shortcuts.add({ name: 'CMD+B', on: 'keydown', handler: this.handleB });
} else {
shortcuts.remove('CMD+B', 'keydown');
shortcuts.add({ name: 'CMD+B', on: 'keydown', handler: this.handleB });
} Defensive patterns
Strategy: validation
Validate before calling
function registerShortcut(shortcuts, data) {
if (shortcuts.findShortcut(data.on, data.name)) {
shortcuts.remove(data.name, data.on);
}
shortcuts.add(data);
}
// usage: registerShortcut(this.shortcuts, { name: 'CMD+B', on: 'keydown', handler }); Type guard
function isShortcutRegistered(shortcuts, data) {
return Boolean(shortcuts.findShortcut(data.on, data.name));
} Try / catch
try {
shortcuts.add(shortcutData);
} catch (e) {
if (e instanceof Error && e.message.includes('is already registered')) {
shortcuts.remove(shortcutData.name, shortcutData.on);
shortcuts.add(shortcutData);
} else {
throw e;
}
} Prevention
- Register shortcuts only once per editor instance; avoid doing it in per-block or per-keystroke handlers.
- In React, use useEffect with [] deps (or proper cleanup calling shortcuts.remove) to survive remounts.
- Wrap registration in a findShortcut/remove guard, especially in HMR or dev hot-reload environments.
When it happens
Trigger: Calling shortcuts.add({ name: 'CMD+B', on: 'keydown', handler }) twice with the same name and on value, e.g. when a plugin's prepare() runs again, when a custom tool is re-initialized, or when module code subscribes in a lifecycle hook that fires multiple times (mount/unmount cycles in React).
Common situations: React/Vue components that create Editor.js instances or register shortcuts in useEffect/componentDidMount without cleanup, hot-module reloading re-running registration code, or plugin code that adds shortcuts on every block focus/change event. Typically the handler registration code is idempotent-assuming but the library is not.
Related errors
- Unable to move Block down since it is already the last
- Unable to move Block up since it is already the first
- Incorrect data passed to the render() method
- Block with id "${id}" not found
- Block Tool with type "${newType}" not found
AI-assisted analysis of codex-team/editor.js@5f45dabbe5 (2026-08-27).
Data as JSON: /api/errors/b0493857c62574cb.
Report an issue: GitHub.