BookStackApp/BookStack · error · Error
To use $generateHtmlFromNodes in headless mode please initia
Error message
To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.
What it means
$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.
Source
Thrown at resources/js/wysiwyg/lexical/html/index.ts:74
lexicalNodes = lexicalNodes.concat(lexicalNode);
}
}
}
$unwrapArtificalNodes(allArtificialNodes);
return lexicalNodes;
}
export function $generateHtmlFromNodes(
editor: LexicalEditor,
selection?: BaseSelection | null,
): string {
if (
typeof document === 'undefined' ||
(typeof window === 'undefined' && typeof global.window === 'undefined')
) {
throw new Error(
'To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.',
);
}
const container = document.createElement('div');
const root = $getRoot();
const topLevelChildren = root.getChildren();
for (let i = 0; i < topLevelChildren.length; i++) {
const topLevelNode = topLevelChildren[i];
$appendNodesToHTML(editor, topLevelNode, container, selection);
}
const nodeCode = [];
for (const node of container.childNodes) {
if ("outerHTML" in node) {
nodeCode.push(node.outerHTML)
} else {View on GitHub (pinned to 18f8469a1c)
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.
Example fix
// before
import { $generateHtmlFromNodes } from './html';
const html = $generateHtmlFromNodes(editor, null); // throws in node
// after
import { JSDOM } from 'jsdom';
const dom = new JSDOM('<!DOCTYPE html>');
global.window = dom.window;
global.document = dom.window.document;
const html = $generateHtmlFromNodes(editor, null); Defensive patterns
Strategy: validation
Validate before calling
function hasBrowserDom(): boolean {
return typeof document !== 'undefined' &&
(typeof window !== 'undefined' || typeof (globalThis as any).window !== 'undefined');
}
if (!hasBrowserDom()) throw new Error('Set up JSDom before HTML generation'); Try / catch
try {
html = $generateHtmlFromNodes(editor, null);
} catch (e) {
if (e instanceof Error && e.message.includes('headless mode')) {
setupJSDomGlobals();
html = $generateHtmlFromNodes(editor, null);
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ${method} is not supported in headless mode
- Table Element Not Found
- Expected to find TableElement in DOM
- errors.ldap_extension_not_installed
- ${error.toString()} Parent: ${dom.tagName}, new child: {tag:
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/2f7befea322dd7bd.
Report an issue: GitHub.