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 for

View on GitHub (pinned to 5f45dabbe5)

Solutions

  1. Guard the registration: call findShortcut(on, name) (or track registered names yourself) and only add when not already present.
  2. Call shortcuts.remove(name, on) before shortcuts.add(...) when re-registering intentionally.
  3. 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.
  4. 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

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


AI-assisted analysis of codex-team/editor.js@5f45dabbe5 (2026-08-27). Data as JSON: /api/errors/b0493857c62574cb. Report an issue: GitHub.