{"record":{"id":"2f7befea322dd7bd","repo":"BookStackApp/BookStack","slug":"to-use-generatehtmlfromnodes-in-headless-mode-ple","errorCode":null,"errorMessage":"To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.","messagePattern":"To use \\$generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"resources/js/wysiwyg/lexical/html/index.ts","lineNumber":74,"sourceCode":"        lexicalNodes = lexicalNodes.concat(lexicalNode);\n      }\n    }\n  }\n\n  $unwrapArtificalNodes(allArtificialNodes);\n\n  return lexicalNodes;\n}\n\nexport function $generateHtmlFromNodes(\n  editor: LexicalEditor,\n  selection?: BaseSelection | null,\n): string {\n  if (\n    typeof document === 'undefined' ||\n    (typeof window === 'undefined' && typeof global.window === 'undefined')\n  ) {\n    throw new Error(\n      'To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.',\n    );\n  }\n\n  const container = document.createElement('div');\n  const root = $getRoot();\n  const topLevelChildren = root.getChildren();\n\n  for (let i = 0; i < topLevelChildren.length; i++) {\n    const topLevelNode = topLevelChildren[i];\n    $appendNodesToHTML(editor, topLevelNode, container, selection);\n  }\n\n  const nodeCode = [];\n  for (const node of container.childNodes) {\n    if (\"outerHTML\" in node) {\n      nodeCode.push(node.outerHTML)\n    } else {","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/resources/js/wysiwyg/lexical/html/index.ts#L56-L92","documentation":"$generateHtmlFromNodes serializes editor nodes to HTML using document.createElement, which requires a browser-like DOM. In headless mode (Node.js without a DOM implementation) `document`/`window` are undefined, so the function throws this descriptive error telling you to set up JSDom or similar first.","triggerScenarios":"Calling $generateHtmlFromNodes (directly or via $getHtmlContent) from an environment where `typeof document === 'undefined'` and no global window is installed — e.g. a Node test harness or server-side rendering without a DOM polyfill.","commonSituations":"Jest/Vitest tests running in the default node environment instead of jsdom; SSR/server code exporting editor content to HTML; tools generating static email or preview HTML from editor state.","solutions":["Install jsdom (npm i jsdom) and assign global.window and global.document from a new JSDom instance before calling the function.","Run tests with environment: 'jsdom' (Jest testEnvironment / vitest environment jsdom).","Perform HTML generation inside a DOM-capable environment (browser or happy-dom) rather than pure Node.","If only state inspection is needed, serialize with $convertToEditorStateJSON or walk nodes manually instead of generating HTML."],"exampleFix":"// before\nimport { $generateHtmlFromNodes } from './html';\nconst html = $generateHtmlFromNodes(editor, null); // throws in node\n\n// after\nimport { JSDOM } from 'jsdom';\nconst dom = new JSDOM('<!DOCTYPE html>');\nglobal.window = dom.window;\nglobal.document = dom.window.document;\nconst html = $generateHtmlFromNodes(editor, null);","handlingStrategy":"validation","validationCode":"function hasBrowserDom(): boolean {\n  return typeof document !== 'undefined' &&\n    (typeof window !== 'undefined' || typeof (globalThis as any).window !== 'undefined');\n}\nif (!hasBrowserDom()) throw new Error('Set up JSDom before HTML generation');","typeGuard":null,"tryCatchPattern":"try {\n  html = $generateHtmlFromNodes(editor, null);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('headless mode')) {\n    setupJSDomGlobals();\n    html = $generateHtmlFromNodes(editor, null);\n  } else throw e;\n}","preventionTips":["Run Node tests that touch Lexical HTML export in a jsdom environment","Install global.window/document from JSDOM at test bootstrap","Never call HTML serializers from pure Node scripts without a DOM polyfill"],"tags":["lexical","headless","jsdom","dom","environment"],"backgroundTag":"missing-dom-global","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}