{"record":{"id":"3bfc507396cc091f","repo":"codex-team/editor.js","slug":"tool-toolname-must-be-a-constructor-function","errorCode":null,"errorMessage":"Tool «${toolName}» must be a constructor function or an object with function in the «class» property","messagePattern":"Tool «(.+?)» must be a constructor function or an object with function in the «class» property","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/components/modules/tools.ts","lineNumber":392,"sourceCode":"  }\n\n  /**\n   * Validate Tools configuration objects and throw Error for user if it is invalid\n   */\n  private validateTools(): void {\n    /**\n     * Check Tools for a class containing\n     */\n    for (const toolName in this.config.tools) {\n      if (Object.prototype.hasOwnProperty.call(this.config.tools, toolName)) {\n        if (toolName in this.internalTools) {\n          return;\n        }\n\n        const tool = this.config.tools[toolName];\n\n        if (!_.isFunction(tool) && !_.isFunction((tool as ToolSettings).class)) {\n          throw Error(\n            `Tool «${toolName}» must be a constructor function or an object with function in the «class» property`\n          );\n        }\n      }\n    }\n  }\n\n  /**\n   * Unify tools config\n   */\n  private prepareConfig(): {[name: string]: ToolSettings} {\n    const config: {[name: string]: ToolSettings} = {};\n\n    /**\n     * Save Tools settings to a map\n     */\n    for (const toolName in this.config.tools) {\n      /**","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/codex-team/editor.js/blob/5f45dabbe5690b7033b2f9047d3985add5045fdd/src/components/modules/tools.ts#L374-L410","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","If using a settings object, include the class key pointing at the constructor: { header: { class: Header, inlineToolbar: true } }.","Log the value (console.log(typeof Header, Header)) before initialization to spot undefined/string values caused by bad imports or circular dependencies.","If lazy-loading tools, ensure the module has fully evaluated (await import(...)) before passing it into tools."],"exampleFix":"// before\nimport { Header } from '@editorjs/header'; // wrong: named import is undefined\nconst editor = new EditorJS({\n  holder: 'editor-js',\n  tools: { header: { class: Header, inlineToolbar: true } }\n});\n\n// after\nimport Header from '@editorjs/header'; // default export is the constructor\nconst editor = new EditorJS({\n  holder: 'editor-js',\n  tools: { header: { class: Header, inlineToolbar: true } }\n});","handlingStrategy":"type-guard","validationCode":"function assertToolsAreConstructors(tools) {\n  for (const [name, entry] of Object.entries(tools)) {\n    const candidate = typeof entry === 'object' && entry !== null ? entry.class : entry;\n    if (typeof candidate !== 'function') {\n      throw new Error(`Tool \"${name}\" is not a constructor (got ${String(candidate)}). Check its import.`);\n    }\n  }\n}\n\nassertToolsAreConstructors(config.tools); // before new EditorJS(...)","typeGuard":"function isToolConfig(entry) {\n  if (typeof entry === 'function') return true;\n  return typeof entry === 'object'\n    && entry !== null\n    && typeof entry.class === 'function';\n}\n\n// usage: Object.values(config.tools).every(isToolConfig)","tryCatchPattern":"try {\n  const editor = new EditorJS(config);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('must be a constructor function')) {\n    console.error('Bad tool registration:', e.message);\n    // fix imports / re-check config.tools entries\n  } else {\n    throw e;\n  }\n}","preventionTips":["Verify default vs named import style for every tool package before wiring it in.","Log typeof for each tool entry during development to catch undefined imports early.","Run a loop over config.tools with a type guard before initialization."],"tags":["editorjs","tools","validation","imports","es-modules"],"backgroundTag":"invalid-plugin-registration","analyzedSha":"5f45dabbe5690b7033b2f9047d3985add5045fdd","analyzedAt":"2026-08-27T22:50:23.124Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}