facebook/lexical · error

Internal Lexical error: invariant() called without a message

Error message

Internal Lexical error: invariant() called without a message

What it means

Lexical's invariant(cond, message) helper throws a plain Error when its condition is falsy; when it is called without a message argument the error text falls back to the generic 'Internal Lexical error: invariant() called without a message'. This signals an assertion failure inside Lexical's internal code paths (e.g. $getHtmlContent, exportNodeToJSON, $handleTab callers listed) where an assumed precondition about the node tree or selection did not hold. It usually indicates a corrupted or unexpected editor state rather than an API misuse with a documented remedy.

Source

Thrown at packages/lexical-internal/src/invariant.ts:28

// if "condition" is false will throw an error. This function is special-cased
// in flow itself, so we can't name it anything else.
//
// In a production build the `transformErrorMessages` Babel plugin replaces
// every call site with a hoisted `if (!cond)` check plus a
// `formatProdErrorMessage(code, ...args)` call, so this body is only reached
// when the source is consumed without that transform (the `source` export
// condition or an untransformed dev build). It must therefore stand on its
// own: interpolate `%s` placeholders against args and throw.
export default function invariant(
  cond?: boolean,
  message = 'Internal Lexical error: invariant() called without a message',
  ...args: string[]
): asserts cond {
  if (cond) {
    return;
  }

  throw new Error(
    args.reduce((msg, arg) => msg.replace('%s', String(arg)), message),
  );
}

View on GitHub (pinned to 76a22dcba9)

Solutions

  1. Check whether a custom node's methods (createDOM, updateDOM, exportJSON, etc.) violate their contracts and fix the implementation
  2. Ensure all node classes used in the document are registered on the editor
  3. Align all @lexical/* package versions (stale/mismatched packages cause inconsistent invariants)
  4. Reproduce with a minimal editor state and file an issue with the stack trace if it appears to be a Lexical bug

Example fix

// before
exportJSON(): SerializedNode {return {type: 'my-node', ...};} // missing 'version', breaks exportNodeToJSON invariant

// after
exportJSON(): SerializedMyNode {return {...super.exportJSON(), type: 'my-node', version: 1};}
Defensive patterns

Strategy: try-catch

Validate before calling

// validate node tree invariants before serialization
editor.read(() => {
  $dfs().forEach(({node}) => {
    if (!node.getType()) throw new Error('Node missing type: ' + node.getKey());
  });
});

Type guard

function isSerializableNode(node: LexicalNode): node is LexicalNode & {exportJSON(): SerializedLexicalNode} {
  return typeof (node as {exportJSON?: unknown}).exportJSON === 'function';
}

Try / catch

try {
  const html = $getHtmlContent();
} catch (e) {
  if (String(e).includes('Internal Lexical error')) {
    console.error('Lexical invariant violated — check custom nodes and package versions', e);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Any of the calling APIs ($getHtmlContent, $getLexicalContent, exportNodeToJSON, $appendNodesToJSON, $getCodeLines, $handleTab) reaching an internal state that violates its assumptions — e.g. serializing a tree containing a node type that fails an internal assertion, or tab handling on a selection the code did not expect.

Common situations: Custom ElementNode/DecoratorNode implementations that break internal contracts (wrong return types from methods); operating on nodes not registered on the editor; version mismatches between @lexical packages after an upgrade; feeding hand-crafted JSON into $parseSerializedNode.

Related errors


AI-assisted analysis of facebook/lexical@76a22dcba9 (2026-08-31). Data as JSON: /api/errors/58f52b0fb40b9e6c. Report an issue: GitHub.