codex-team/editor.js · error · Error

Tool «${toolName}» must be a constructor function or an obje

Error message

Tool «${toolName}» must be a constructor function or an object with function in the «class» property

What it means

During prepare, Editor.js validates every entry in config.tools. Each entry must be either a class/constructor function (the tool implementation) or a plain settings object containing a class property that is a function. If neither condition holds, this error is thrown naming the offending tool key.

Source

Thrown at src/components/modules/tools.ts:392

  }

  /**
   * Validate Tools configuration objects and throw Error for user if it is invalid
   */
  private validateTools(): void {
    /**
     * Check Tools for a class containing
     */
    for (const toolName in this.config.tools) {
      if (Object.prototype.hasOwnProperty.call(this.config.tools, toolName)) {
        if (toolName in this.internalTools) {
          return;
        }

        const tool = this.config.tools[toolName];

        if (!_.isFunction(tool) && !_.isFunction((tool as ToolSettings).class)) {
          throw Error(
            `Tool «${toolName}» must be a constructor function or an object with function in the «class» property`
          );
        }
      }
    }
  }

  /**
   * Unify tools config
   */
  private prepareConfig(): {[name: string]: ToolSettings} {
    const config: {[name: string]: ToolSettings} = {};

    /**
     * Save Tools settings to a map
     */
    for (const toolName in this.config.tools) {
      /**

View on GitHub (pinned to 5f45dabbe5)

Solutions

  1. Check the import style for the offending tool: most Editor.js tools use a default export — use import Header from '@editorjs/header'; verify with the tool's package README.
  2. If using a settings object, include the class key pointing at the constructor: { header: { class: Header, inlineToolbar: true } }.
  3. Log the value (console.log(typeof Header, Header)) before initialization to spot undefined/string values caused by bad imports or circular dependencies.
  4. If lazy-loading tools, ensure the module has fully evaluated (await import(...)) before passing it into tools.

Example fix

// before
import { Header } from '@editorjs/header'; // wrong: named import is undefined
const editor = new EditorJS({
  holder: 'editor-js',
  tools: { header: { class: Header, inlineToolbar: true } }
});

// after
import Header from '@editorjs/header'; // default export is the constructor
const editor = new EditorJS({
  holder: 'editor-js',
  tools: { header: { class: Header, inlineToolbar: true } }
});
Defensive patterns

Strategy: type-guard

Validate before calling

function assertToolsAreConstructors(tools) {
  for (const [name, entry] of Object.entries(tools)) {
    const candidate = typeof entry === 'object' && entry !== null ? entry.class : entry;
    if (typeof candidate !== 'function') {
      throw new Error(`Tool "${name}" is not a constructor (got ${String(candidate)}). Check its import.`);
    }
  }
}

assertToolsAreConstructors(config.tools); // before new EditorJS(...)

Type guard

function isToolConfig(entry) {
  if (typeof entry === 'function') return true;
  return typeof entry === 'object'
    && entry !== null
    && typeof entry.class === 'function';
}

// usage: Object.values(config.tools).every(isToolConfig)

Try / catch

try {
  const editor = new EditorJS(config);
} catch (e) {
  if (e instanceof Error && e.message.includes('must be a constructor function')) {
    console.error('Bad tool registration:', e.message);
    // fix imports / re-check config.tools entries
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Passing tools: { header: HeaderTool } where HeaderTool is undefined (bad import), a module namespace object, an instance instead of a class, or a string. Also passing a settings object like { header: { inlineToolbar: true } } without the required class property, or with class set to a non-function value.

Common situations: Very common with broken ES-module imports: named vs default export mismatches (import { Header } from '@editorjs/header' instead of import Header from '@editorjs/header'), circular imports that yield undefined at evaluation time, tree-shaking removing the class, or lazy-loaded tool bundles that haven't evaluated when the editor is constructed. Also occurs from typos: tools: { heaer: Header } works but the tool internals fail elsewhere, whereas tools: { header: 'Header' } (string) throws here.

Related errors


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