packages/lexical-website/docs/concepts/key-management.md
Keys are a fundamental concept in Lexical that enable efficient state management and node tracking. Understanding how keys work is crucial for building reliable editor implementations.
The __key property is a unique identifier assigned to each node in the Lexical editor. These keys are:
__key?Keys should ONLY be used in two specific situations:
class MyCustomNode extends ElementNode {
constructor(someData: string, key?: NodeKey) {
super(key); // Correctly passing key to parent constructor
this.__someData = someData;
}
}
class MyCustomNode extends ElementNode {
static clone(node: MyCustomNode): MyCustomNode {
return new MyCustomNode(node.__someData, node.__key);
}
}
Never use keys in these situations:
// ❌ Don't pass keys between different nodes
const newNode = new MyCustomNode(existingNode.__key);
// ❌ Don't manipulate keys directly
node.__key = 'custom-key';
The dotted outlines show nodes that are re-used in a zero-copy fashion from one EditorState to the next
graph TB
subgraph s0["Initial State"]
direction TB
m0["NodeMap (v0)"]
m0 -->|Key A| n0a["A (v0)"]
m0 -->|Key B| n0b["B (v0)"]
end
subgraph s1["Create Node C"]
direction TB
style n1a stroke-dasharray: 5 5
style n1b stroke-dasharray: 5 5
m1["NodeMap (v1)"]
m1 -->|Key A| n1a["A (v0)"]
m1 -->|Key B| n1b["B (v0)"]
m1 -->|Key C| n1c["C (v0)"]
end
subgraph s2["Update Node A"]
direction TB
style n2a stroke-dasharray: 5 0
style n2b stroke-dasharray: 5 5
style n2c stroke-dasharray: 5 5
m2["NodeMap (v1)"]
m2 -->|Key A| n2a["A (v1)"]
m2 -->|Key B| n2b["B (v0)"]
m2 -->|Key C| n2c["C (v0)"]
end
s0 -.-> s1 -.-> s2
The EditorState maintains a Map<NodeKey, LexicalNode> that tracks all nodes. Nodes refer to each other using keys in their internal pointers:
// Internal node structure (not for direct usage)
{
__prev: null | NodeKey,
__next: null | NodeKey,
__parent: null | NodeKey,
// __first, __last and __size are only for ElementNode to track its children
__first: null | NodeKey,
__last: null | NodeKey,
__size: number
}
These internal pointers maintain the tree structure and should never be manipulated directly.
// Get node by key
const node = editor.getElementByKey(key);
const node = $getNodeByKey(key);
// Get latest version of a node
const latest = node.getLatest();
// Get mutable version for updates
const mutable = node.getWritable();
NodeKeys are ephemeral and have several important characteristics:
Serialization
Uniqueness
Keys are used internally by Lexical to:
Key Reuse
// ❌ Never do this
function duplicateNode(node: LexicalNode) {
return new SameNodeType(data, node.__key);
}
Manual Key Assignment
// ❌ Never do this
node.__key = generateCustomKey();
Incorrect Constructor/Clone Implementation
// ❌ Never do this - missing key in constructor
class MyCustomNode extends ElementNode {
constructor(someData: string) {
super(); // Missing key parameter
this.__someData = someData;
}
}
// ✅ Correct implementation
class MyCustomNode extends ElementNode {
__someData: string;
constructor(someData: string, key?: NodeKey) {
super(key);
this.__someData = someData;
}
static clone(node: MyCustomNode): MyCustomNode {
return new MyCustomNode(node.__someData, node.__key);
}
afterCloneFrom(prevNode: this): void {
super.afterCloneFrom(prevNode);
this.__someData = prevNode.__someData;
}
}
Node Replacement
// ❌ Never re-use the key when changing the node class
const editorConfig = {
nodes: [
CustomNodeType,
{
replace: OriginalNodeType,
with: (node: OriginalNodeType) => new CustomNodeType(node.__key),
withKlass: CustomNodeType
}
]
};
// ✅ Correct: Use node replacement configuration
const editorConfig = {
nodes: [
CustomNodeType,
{
replace: OriginalNodeType,
with: (node: OriginalNodeType) => new CustomNodeType(),
withKlass: CustomNodeType
}
]
};
For proper node replacement, see the Node Replacement guide.
import {$applyNodeReplacement} from 'lexical';
// Create node helper function
export function $createMyCustomNode(data: string): MyCustomNode {
return $applyNodeReplacement(new MyCustomNode(data));
}
When writing tests involving node keys:
test('node creation', async () => {
await editor.update(() => {
// ✅ Correct: Create nodes normally
const node = new MyCustomNode("test");
// ✅ Correct: Keys are automatically handled
expect(node.__key).toBeDefined();
expect(node.__key).not.toBe('');
});
});
Understanding key management is crucial for performance:
Q: How do I reference a node later?
A: Store a reference to the node. Conventionally, all node methods will use getLatest() or getWritable() which will look up the latest version of that node before reading or writing its properties, which is equivalent to using the key but is type-safe (but may cause errors if you try to use a reference to a node that no longer exists). In some situations it may be preferable to use the key directly, which is also fine.
Q: How do I ensure unique nodes? A: Let Lexical handle key generation and management. Focus on node content and structure.