codex-team/editor.js · critical · Error

Can't start without tools

Error message

Can't start without tools

What it means

Editor.js throws this during Editor initialization (prepare) when, after merging internal tools with user-provided config.tools, the resulting tools object is empty. The library refuses to start because an editor with no tools would be unusable. It also fires when the tools key is missing entirely from the config object.

Source

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

  public get internal(): ToolsCollection {
    return this.available.internalTools;
  }

  /**
   * Creates instances via passed or default configuration
   *
   * @returns {Promise<void>}
   */
  public async prepare(): Promise<void> {
    this.validateTools();

    /**
     * Assign internal tools
     */
    this.config.tools = _.deepMerge({}, this.internalTools, this.config.tools);

    if (!Object.prototype.hasOwnProperty.call(this.config, 'tools') || Object.keys(this.config.tools).length === 0) {
      throw Error('Can\'t start without tools');
    }

    const config = this.prepareConfig();

    this.factory = new ToolsFactory(config, this.config, this.Editor.API);

    /**
     * getting classes that has prepare method
     */
    const sequenceData = this.getListOfPrepareFunctions(config);

    /**
     * if sequence data contains nothing then resolve current chain and run other module prepare
     */
    if (sequenceData.length === 0) {
      return Promise.resolve();
    }

View on GitHub (pinned to 5f45dabbe5)

Solutions

  1. Add a non-empty tools object to the Editor.js config: tools: { paragraph: Paragraph } (import @editorjs/paragraph or another tool).
  2. If tools are loaded asynchronously, await them before constructing new EditorJS(...); construct the editor only after tools resolve.
  3. Debug-log Object.keys(config.tools || {}) right before initialization to confirm the object is populated.
  4. Never pass tools: {} or undefined; at minimum register the Paragraph tool, which Editor.js expects for block editing.

Example fix

// before
const editor = new EditorJS({ holder: 'editor-js' });

// after
import Paragraph from '@editorjs/paragraph';
const editor = new EditorJS({
  holder: 'editor-js',
  tools: {
    paragraph: Paragraph
  }
});
Defensive patterns

Strategy: validation

Validate before calling

import Paragraph from '@editorjs/paragraph';

function assertToolsReady(config) {
  if (!config.tools || Object.keys(config.tools).length === 0) {
    throw new Error('Editor.js config.tools is empty — register at least one tool (e.g. paragraph) before init.');
  }
}

// before new EditorJS(...):
assertToolsReady(config);

Type guard

function hasValidTools(config) {
  return Object.prototype.hasOwnProperty.call(config, 'tools')
    && config.tools !== null
    && typeof config.tools === 'object'
    && Object.keys(config.tools).length > 0;
}

Prevention

When it happens

Trigger: Calling new EditorJS({...}) without a tools property, or passing tools: {} (empty object). It can also occur if tools is passed as undefined/null or a non-plain object whose keys don't survive Object.keys(), since the merge of internalTools with user tools yields nothing usable.

Common situations: Most commonly a developer copies a minimal example and forgets the tools block, or builds config dynamically (e.g. tools: loadedTools where loadedTools is an empty object before an async fetch resolves). Also happens when config is spread from another object that omits tools, or after upgrading from examples that assumed default tools exist.

Related errors


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