{"record":{"id":"0cf60d3ee47cd08a","repo":"facebook/lexical","slug":"createtextnodesfromytext-node-type-is-not-a-t","errorCode":null,"errorMessage":"$createTextNodesFromYText: Node ${type} is not a TextNode","messagePattern":"\\$createTextNodesFromYText: Node (.+?) is not a TextNode","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/lexical-yjs/src/SyncV2.ts","lineNumber":404,"sourceCode":"\n  const nodeTypes: string[] = deltas.map(\n    delta => delta.attributes.t ?? TextNode.getType(),\n  );\n  const canReuseNodes =\n    nodes.length === nodeTypes.length &&\n    nodes.every((node, i) => node.getType() === nodeTypes[i]);\n  if (!canReuseNodes) {\n    const registeredNodes = binding.editor._nodes;\n    nodes = nodeTypes.map(type => {\n      const nodeInfo = registeredNodes.get(type);\n      if (nodeInfo === undefined) {\n        throw new Error(\n          `$createTextNodesFromYText: Node ${type} is not registered`,\n        );\n      }\n      const node = new nodeInfo.klass();\n      if (!$isTextNode(node)) {\n        throw new Error(\n          `$createTextNodesFromYText: Node ${type} is not a TextNode`,\n        );\n      }\n      return node;\n    });\n  }\n\n  // Sync text, properties and state to the text nodes.\n  for (let i = 0; i < deltas.length; i++) {\n    const node = nodes[i];\n    const delta = deltas[i];\n    const {attributes, insert} = delta;\n    if (node.__text !== insert) {\n      node.setTextContent(insert);\n    }\n    const properties = {\n      ...getDefaultNodeProperties(node, binding),\n      ...attributes.p,","sourceCodeStart":386,"sourceCodeEnd":422,"githubUrl":"https://github.com/facebook/lexical/blob/76a22dcba981b46119fcec45f935270707e26a57/packages/lexical-yjs/src/SyncV2.ts#L386-L422","documentation":"SyncV2 maps Y.Text regions to Lexical TextNodes. After instantiating the class registered for a type, it asserts the result is actually a TextNode ($isTextNode). A registered class with a matching type string but a non-text base class (ElementNode, DecoratorNode, etc.) cannot hold inline Y.Text content, so the sync throws.","triggerScenarios":"nodeTypes map yields a type whose registered klass is not a TextNode subclass when `new nodeInfo.klass()` runs inside $createOrUpdateTextNodesFromYText — e.g. a custom node extending ElementNode but declared in the Y.Text's node-type list.","commonSituations":"Custom node registered with the same type string as a text-level node but implemented from the wrong base class; refactoring a TextNode subclass to ElementNode while old Yjs docs still reference the old type; copy-pasting a node class with the wrong extends clause.","solutions":["Make the registered class extend TextNode (or a TextNode subclass) so $isTextNode passes.","If the node is meant to be an element/decorator, remove its type from the Y.Text node-type data and remap the document content.","Check the class's $config/extends to confirm the inheritance chain: class MyNode extends TextNode.","Ensure serialized Yjs data was not produced by a version where the node was still a TextNode."],"exampleFix":"// before\nclass MentionNode extends ElementNode { getType() { return 'mention'; } }\n// after\nclass MentionNode extends TextNode { getType() { return 'mention'; } }","handlingStrategy":"type-guard","validationCode":"const nodeInfo = editor._nodes.get(type);\nif (!nodeInfo) throw new Error(`Type ${type} not registered`);\nconst probe = new nodeInfo.klass();\nif (!$isTextNode(probe)) throw new Error(`${type} is not a TextNode`);","typeGuard":"function isTextSubclass(klass: Klass<LexicalNode>): boolean {\n  return klass.prototype instanceof TextNode || klass === TextNode;\n}","tryCatchPattern":"try {\n  syncYTextToLexical(binding, yText);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('is not a TextNode')) {\n    console.error(`Node type registered with wrong base class: ${e.message}`);\n  } else { throw e; }\n}","preventionTips":["Extend TextNode for any node stored inside Y.Text regions.","Assert base class in unit tests for every custom node.","Avoid reusing type strings across different base classes.","Run type checks after refactors that change a node's base class."],"tags":["yjs","collaboration","textnode","class-hierarchy"],"backgroundTag":"wrong-node-base-class","analyzedSha":"76a22dcba981b46119fcec45f935270707e26a57","analyzedAt":"2026-08-31T21:45:54.792Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}