{"record":{"id":"f1b979f0215695e9","repo":"ianstormtaylor/slate","slug":"could-not-completely-normalize-the-editor-after","errorCode":null,"errorMessage":"Could not completely normalize the editor after ${maxIterations} iterations! This is usually due to incorrect normalization logic that leaves a node in an invalid state.","messagePattern":"Could not completely normalize the editor after (.+?) iterations! This is usually due to incorrect normalization logic that leaves a node in an invalid state\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/slate/src/core/should-normalize.ts","lineNumber":11,"sourceCode":"import { WithEditorFirstArg } from '../utils/types'\nimport { Editor } from '../interfaces/editor'\n\nexport const shouldNormalize: WithEditorFirstArg<Editor['shouldNormalize']> = (\n  editor,\n  { iteration, initialDirtyPathsLength }\n) => {\n  const maxIterations = initialDirtyPathsLength * 42 // HACK: better way?\n\n  if (iteration > maxIterations) {\n    throw new Error(\n      `Could not completely normalize the editor after ${maxIterations} iterations! This is usually due to incorrect normalization logic that leaves a node in an invalid state.`\n    )\n  }\n\n  return true\n}\n","sourceCodeStart":1,"sourceCodeEnd":18,"githubUrl":"https://github.com/ianstormtaylor/slate/blob/72a37c701e5da4bb13a305d416d9c070d19d3a45/packages/slate/src/core/should-normalize.ts#L1-L18","documentation":"Slate normalizes the editor after every change by repeatedly running normalizeNode over 'dirty' paths until the document is valid. shouldNormalize caps this loop at initialDirtyPathsLength * 42 iterations as a safety hack; if normalization keeps marking nodes dirty (e.g. a normalizeNode override that never converges, always returns true for shouldNormalize-like hooks, or a normalize function that re-dirties the same path), the cap is hit and this error throws.","triggerScenarios":"A custom editor.normalizeNode override that fixes one thing but invalidates another (ping-pong normalization); withOverride/shouldNormalize plugins (e.g. slate-autoformat-style plugins) always returning true; a normalizeNode that calls editor.normalizeNode again or transforms nodes every pass; a normalize function that dirties the node it just normalized.","commonSituations":"Adding a normalization plugin whose fix triggers the next plugin's fix indefinitely; normalizations that insert nodes during normalization causing cascading dirty paths; upgrading Slate where normalizeNode chaining conventions changed (must call the previous normalizeNode for unmatched nodes).","solutions":["Audit every custom normalizeNode override: make each fix idempotent — only act when the invalid condition is present, and always fall through to the previous normalizeNode for non-matching nodes","Ensure normalize fixes don't call transforms that re-dirty the same path every pass (e.g. removing and re-adding the same property)","Log the dirty paths per iteration (wrap Editor.normalize or shouldNormalize) to find the ping-pong pair of normalizers","Reduce the problem: disable custom normalizers one by one until the loop stops to identify the culprit"],"exampleFix":"// before\nconst { normalizeNode } = editor\neditor.normalizeNode = entry => {\n  const [node] = entry\n  editor.normalizeNode(entry) // re-normalizes same entry every time -> infinite loop\n  if (node.type !== 'p') Transforms.setNodes(editor, { type: 'p' }, ...)\n}\n\n// after\nconst { normalizeNode } = editor\neditor.normalizeNode = entry => {\n  const [node, path] = entry\n  if (Element.isElement(node) && node.type !== 'p') {\n    Transforms.setNodes(editor, { type: 'p' }, { at: path })\n    return\n  }\n  normalizeNode(entry)\n}","handlingStrategy":"validation","validationCode":"const { normalizeNode } = editor\neditor.normalizeNode = entry => {\n  const [node, path] = entry\n  let handled = false\n  // each fix must only run when the invalid state exists\n  if (Element.isElement(node) && node.type == null) {\n    Transforms.setNodes(editor, { type: 'paragraph' }, { at: path })\n    handled = true\n  }\n  if (!handled) normalizeNode(entry)\n}","typeGuard":null,"tryCatchPattern":"try {\n  Editor.normalize(editor, { force: true })\n} catch (e) {\n  if (/Could not completely normalize/.test(e.message)) {\n    // log dirty paths, find ping-pong normalizers\n  } else throw e\n}","preventionTips":["Make every normalizeNode fix idempotent and conditional","Always delegate unmatched nodes to the previous normalizeNode","Never call transforms that re-dirty the same path during normalization","Test plugins against documents that trigger multiple normalizers at once"],"tags":["slate","normalization","infinite-loop","normalizenode","plugin"],"backgroundTag":"infinite-normalization-loop","analyzedSha":"72a37c701e5da4bb13a305d416d9c070d19d3a45","analyzedAt":"2026-08-27T23:04:51.145Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}