BookStackApp/BookStack · error · Error
Cannot call set() on a frozen Lexical node map
Error message
Cannot call set() on a frozen Lexical node map
What it means
In dev builds, Lexical freezes the pending editor state's _nodeMap by replacing its set/clear/delete methods with throwing stubs (handleDEVOnlyPendingUpdateGuarantees). Any code that mutates the committed/pending node map after freezing indicates code is holding and mutating a stale EditorState's map, which Lexical forbids, so it throws 'Cannot call set() on a frozen Lexical node map'.
Source
Thrown at resources/js/wysiwyg/lexical/core/LexicalUpdates.ts:465
try {
return callbackFn();
} finally {
activeEditorState = previousActiveEditorState;
isReadOnlyMode = previousReadOnlyMode;
activeEditor = previousActiveEditor;
}
}
function handleDEVOnlyPendingUpdateGuarantees(
pendingEditorState: EditorState,
): void {
// Given we can't Object.freeze the nodeMap as it's a Map,
// we instead replace its set, clear and delete methods.
const nodeMap = pendingEditorState._nodeMap;
nodeMap.set = () => {
throw new Error('Cannot call set() on a frozen Lexical node map');
};
nodeMap.clear = () => {
throw new Error('Cannot call clear() on a frozen Lexical node map');
};
nodeMap.delete = () => {
throw new Error('Cannot call delete() on a frozen Lexical node map');
};
}
export function $commitPendingUpdates(
editor: LexicalEditor,
recoveryEditorState?: EditorState,
): void {
const pendingEditorState = editor._pendingEditorState;
const rootElement = editor._rootElement;
const shouldSkipDOM = editor._headless || rootElement === null;View on GitHub (pinned to 18f8469a1c)
Solutions
- Stop mutating EditorState._nodeMap; treat editor states as immutable snapshots
- Clone/derive state via official APIs (editor.parseEditorState, $nodes helpers) instead of touching internals
- Remove/rework code storing references to old editor states and calling map methods
- Report the offending plugin/library code that reaches into Lexical internals
Example fix
// before
const map = editorState._nodeMap;
map.set(key, node);
// after
editor.update(() => {
const node = $getNodeByKey(key);
// mutate via public APIs
}); Defensive patterns
Strategy: type-guard
Validate before calling
// Don't touch internals; guard before mutating any captured state
function isFrozenNodeMap(map: unknown): boolean {
try { (map as Map<string, unknown>).set('__probe__', null); return false; }
catch { return true; }
} Type guard
function isLiveEditorState(state: EditorState, editor: LexicalEditor): boolean {
return editor.getEditorState() === state;
} Try / catch
try {
// code that may hold stale state references
} catch (e) {
if (e.message.includes('frozen Lexical node map')) {
console.error('Mutating a committed EditorState is forbidden');
}
throw e;
} Prevention
- Never call set/clear/delete on EditorState._nodeMap
- Use only public Lexical APIs to change editor state
- Treat all EditorState objects as immutable snapshots
- Run dev builds in CI to surface these invariants
When it happens
Trigger: parseEditorState or $commitPendingUpdates later triggering a .set() call on the frozen map — e.g. custom code or plugin reading editorState._nodeMap and calling set() on it, or misusing internal APIs.
Common situations: Plugins reaching into private _nodeMap internals; custom editor-state serialization/deserialization attempts; dev-only — never occurs in production builds.
Related errors
- Cannot call clear() on a frozen Lexical node map
- Cannot call delete() on a frozen Lexical node map
- ${error.toString()} Parent: ${dom.tagName}, new child: {tag:
- ${method} is not supported in headless mode
- To use $generateHtmlFromNodes in headless mode please initia
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/0c0fec27844e970c.
Report an issue: GitHub.