{"record":{"id":"0c0fec27844e970c","repo":"BookStackApp/BookStack","slug":"cannot-call-set-on-a-frozen-lexical-node-map","errorCode":null,"errorMessage":"Cannot call set() on a frozen Lexical node map","messagePattern":"Cannot call set\\(\\) on a frozen Lexical node map","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"resources/js/wysiwyg/lexical/core/LexicalUpdates.ts","lineNumber":465,"sourceCode":"\n  try {\n    return callbackFn();\n  } finally {\n    activeEditorState = previousActiveEditorState;\n    isReadOnlyMode = previousReadOnlyMode;\n    activeEditor = previousActiveEditor;\n  }\n}\n\nfunction handleDEVOnlyPendingUpdateGuarantees(\n  pendingEditorState: EditorState,\n): void {\n  // Given we can't Object.freeze the nodeMap as it's a Map,\n  // we instead replace its set, clear and delete methods.\n  const nodeMap = pendingEditorState._nodeMap;\n\n  nodeMap.set = () => {\n    throw new Error('Cannot call set() on a frozen Lexical node map');\n  };\n\n  nodeMap.clear = () => {\n    throw new Error('Cannot call clear() on a frozen Lexical node map');\n  };\n\n  nodeMap.delete = () => {\n    throw new Error('Cannot call delete() on a frozen Lexical node map');\n  };\n}\n\nexport function $commitPendingUpdates(\n  editor: LexicalEditor,\n  recoveryEditorState?: EditorState,\n): void {\n  const pendingEditorState = editor._pendingEditorState;\n  const rootElement = editor._rootElement;\n  const shouldSkipDOM = editor._headless || rootElement === null;","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/resources/js/wysiwyg/lexical/core/LexicalUpdates.ts#L447-L483","documentation":"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'.","triggerScenarios":"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.","commonSituations":"Plugins reaching into private _nodeMap internals; custom editor-state serialization/deserialization attempts; dev-only — never occurs in production builds.","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"],"exampleFix":"// before\nconst map = editorState._nodeMap;\nmap.set(key, node);\n// after\neditor.update(() => {\n  const node = $getNodeByKey(key);\n  // mutate via public APIs\n});","handlingStrategy":"type-guard","validationCode":"// Don't touch internals; guard before mutating any captured state\nfunction isFrozenNodeMap(map: unknown): boolean {\n  try { (map as Map<string, unknown>).set('__probe__', null); return false; }\n  catch { return true; }\n}","typeGuard":"function isLiveEditorState(state: EditorState, editor: LexicalEditor): boolean {\n  return editor.getEditorState() === state;\n}","tryCatchPattern":"try {\n  // code that may hold stale state references\n} catch (e) {\n  if (e.message.includes('frozen Lexical node map')) {\n    console.error('Mutating a committed EditorState is forbidden');\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["javascript","lexical","rich-text","dev-only","immutability"],"backgroundTag":"leximmutability-violation","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}