facebook/lexical · error
$createTextNodesFromYText: Node ${type} is not a TextNode
Error message
$createTextNodesFromYText: Node ${type} is not a TextNode What it means
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.
Source
Thrown at packages/lexical-yjs/src/SyncV2.ts:404
const nodeTypes: string[] = deltas.map(
delta => delta.attributes.t ?? TextNode.getType(),
);
const canReuseNodes =
nodes.length === nodeTypes.length &&
nodes.every((node, i) => node.getType() === nodeTypes[i]);
if (!canReuseNodes) {
const registeredNodes = binding.editor._nodes;
nodes = nodeTypes.map(type => {
const nodeInfo = registeredNodes.get(type);
if (nodeInfo === undefined) {
throw new Error(
`$createTextNodesFromYText: Node ${type} is not registered`,
);
}
const node = new nodeInfo.klass();
if (!$isTextNode(node)) {
throw new Error(
`$createTextNodesFromYText: Node ${type} is not a TextNode`,
);
}
return node;
});
}
// Sync text, properties and state to the text nodes.
for (let i = 0; i < deltas.length; i++) {
const node = nodes[i];
const delta = deltas[i];
const {attributes, insert} = delta;
if (node.__text !== insert) {
node.setTextContent(insert);
}
const properties = {
...getDefaultNodeProperties(node, binding),
...attributes.p,View on GitHub (pinned to 76a22dcba9)
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.
Example fix
// before
class MentionNode extends ElementNode { getType() { return 'mention'; } }
// after
class MentionNode extends TextNode { getType() { return 'mention'; } } Defensive patterns
Strategy: type-guard
Validate before calling
const nodeInfo = editor._nodes.get(type);
if (!nodeInfo) throw new Error(`Type ${type} not registered`);
const probe = new nodeInfo.klass();
if (!$isTextNode(probe)) throw new Error(`${type} is not a TextNode`); Type guard
function isTextSubclass(klass: Klass<LexicalNode>): boolean {
return klass.prototype instanceof TextNode || klass === TextNode;
} Try / catch
try {
syncYTextToLexical(binding, yText);
} catch (e) {
if (e instanceof Error && e.message.includes('is not a TextNode')) {
console.error(`Node type registered with wrong base class: ${e.message}`);
} else { throw e; }
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Unexpected delta format
- $createOrUpdateNodeFromYElement: Node ${type} is not registe
- $createTextNodesFromYText: Node ${type} is not registered
- Invalid state key: ${key}
- node name mismatch!
AI-assisted analysis of facebook/lexical@76a22dcba9 (2026-08-31).
Data as JSON: /api/errors/0cf60d3ee47cd08a.
Report an issue: GitHub.