facebook/lexical · error

node name mismatch!

Error message

node name mismatch!

What it means

When mapping a Yjs XmlElement back to a Lexical node, SyncV2 checks that the Yjs element's nodeName equals the Lexical node's getType() (allowing the root special case). A mismatch means the shared document's structure disagrees with the Lexical node it is being bound to, which would corrupt the mapping, so it throws.

Source

Thrown at packages/lexical-yjs/src/SyncV2.ts:1003

  for (const [stateConfig, v] of known) {
    attrs[stateKeyToAttrKey(stateConfig.key)] = stateConfig.unparse(v);
  }
  return attrs;
};

export const $updateYFragment = (
  y: YDoc,
  yDomFragment: XmlElement,
  node: LexicalNode,
  binding: BindingV2,
  dirtyElements: Set<NodeKey>,
) => {
  if (
    yDomFragment instanceof XmlElement &&
    yDomFragment.nodeName !== node.getType() &&
    !(isRootElement(yDomFragment) && node.getType() === RootNode.getType())
  ) {
    throw new Error('node name mismatch!');
  }
  binding.mapping.set(yDomFragment, node);
  // update attributes
  if (yDomFragment instanceof XmlElement) {
    const yDomAttrs = yDomFragment.getAttributes();
    const lexicalAttrs = {
      ...propertiesToAttributes(node, binding),
      ...stateToAttributes(node),
    };
    for (const key in lexicalAttrs) {
      if (lexicalAttrs[key] != null) {
        const isEqual =
          yDomAttrs[key] === lexicalAttrs[key] ||
          // deep equality check so we don't sync properties/state with object values every update
          (isObject(yDomAttrs[key]) &&
            isObject(lexicalAttrs[key]) &&
            equalAttrs(yDomAttrs[key], lexicalAttrs[key]));
        if (!isEqual && key !== 'ychange') {

View on GitHub (pinned to 76a22dcba9)

Solutions

  1. Align node getType() strings with the Yjs element names present in the shared document.
  2. Migrate the Yjs document's element names if the Lexical type was renamed.
  3. Ensure all collaborators use identical node definitions/versions (lock package versions across clients).
  4. Verify you are passing the matching Lexical node for each XmlElement rather than an arbitrary node.

Example fix

// before
class H1Node { getType() { return 'h1'; } } // Yjs doc has nodeName 'heading'
// after
class H1Node { getType() { return 'heading'; } } // matches Yjs nodeName
Defensive patterns

Strategy: validation

Validate before calling

if (yDomFragment instanceof XmlElement &&
    yDomFragment.nodeName !== node.getType() &&
    !(isRootElement(yDomFragment) && node.getType() === RootNode.getType())) {
  throw new Error(`Schema mismatch: yjs=${yDomFragment.nodeName} lexical=${node.getType()}`);
}

Type guard

function yjsNameMatches(yEl: unknown, node: LexicalNode): boolean {
  return yEl instanceof XmlElement && yEl.nodeName === node.getType();
}

Try / catch

try {
  bindNode(binding, yDomFragment, node);
} catch (e) {
  if (e instanceof Error && e.message === 'node name mismatch!') {
    console.error('Yjs/Lexical schema mismatch', {
      yjs: (yDomFragment as XmlElement).nodeName,
    });
  } else { throw e; }
}

Prevention

When it happens

Trigger: $bindNodeToYjs (the binding function around line 1003) receives a yDomFragment that is an XmlElement whose nodeName differs from node.getType() and is not the root element mapped to RootNode.

Common situations: Two clients with different node schemas collaborating: one writes <heading> where the other expects 'paragraph'; custom nodes renamed via getType() after documents were created; manually constructing Yjs XML with names that don't match Lexical types.

Related errors


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