Overview
The @lexical/html package provides utilities for converting between Lexical editor state and HTML, enabling import from HTML strings and export to HTML format.
Installation
npm install @lexical/html
Core Functions
$generateNodesFromDOM
Converts DOM nodes to Lexical nodes.
function $generateNodesFromDOM(
editor: LexicalEditor,
dom: Document | ParentNode
): Array<LexicalNode>
The editor instance (used for node type registration)
dom
Document | ParentNode
required
The DOM document or element to convert from
Returns: Array of Lexical nodes
Example:
import { $generateNodesFromDOM } from '@lexical/html';
// In browser
const parser = new DOMParser();
const dom = parser.parseFromString(htmlString, 'text/html');
editor.update(() => {
const nodes = $generateNodesFromDOM(editor, dom);
$getRoot().append(...nodes);
});
Headless Example (Node.js):
import { $generateNodesFromDOM } from '@lexical/html';
import { JSDOM } from 'jsdom';
const jsdom = new JSDOM(htmlString);
const dom = jsdom.window.document;
editor.update(() => {
const nodes = $generateNodesFromDOM(editor, dom);
$getRoot().append(...nodes);
});
$generateHtmlFromNodes
Converts Lexical nodes to HTML string.
function $generateHtmlFromNodes(
editor: LexicalEditor,
selection?: BaseSelection | null
): string
Optional selection to export only selected nodes
Returns: HTML string
Example:
import { $generateHtmlFromNodes } from '@lexical/html';
// Export entire editor
editor.getEditorState().read(() => {
const html = $generateHtmlFromNodes(editor);
console.log(html);
});
// Export only selection
editor.getEditorState().read(() => {
const selection = $getSelection();
const html = $generateHtmlFromNodes(editor, selection);
console.log(html);
});
How It Works
Import (HTML → Lexical)
- Parses HTML string into DOM using DOMParser (browser) or JSDOM (Node.js)
- Traverses DOM nodes and calls
importDOM() on registered Lexical node classes
- Creates corresponding Lexical nodes based on conversion functions
- Maintains hierarchy and attributes during conversion
Supported Conversions:
The conversion depends on the importDOM() method of registered nodes:
<p> → ParagraphNode
<h1>-<h6> → HeadingNode (from @lexical/rich-text)
<blockquote> → QuoteNode (from @lexical/rich-text)
<ul>, <ol> → ListNode (from @lexical/list)
<li> → ListItemNode (from @lexical/list)
<a> → LinkNode (from @lexical/link)
<code>, <pre> → CodeNode (from @lexical/code)
<table>, <tr>, <td> → TableNode, TableRowNode, TableCellNode (from @lexical/table)
- Text nodes → TextNode with formatting
<br> → LineBreakNode
<b>, <strong> → Bold format
<i>, <em> → Italic format
<u> → Underline format
<s>, <strike>, <del> → Strikethrough format
Export (Lexical → HTML)
- Traverses Lexical node tree from root
- Calls
exportDOM() on each node to get HTML element
- Appends child HTML elements recursively
- Returns final HTML string
Conversion Examples:
- ParagraphNode →
<p>
- HeadingNode →
<h1>-<h6>
- QuoteNode →
<blockquote>
- ListNode →
<ul> or <ol>
- ListItemNode →
<li>
- LinkNode →
<a href="...">
- TextNode → Text with
<b>, <i>, <u>, etc. for formatting
- LineBreakNode →
<br>
DOM Conversion Customization
Nodes can customize HTML import/export by implementing:
Custom Import
static importDOM(): DOMConversionMap | null {
return {
div: (node: Node) => ({
conversion: convertDivElement,
priority: 0
})
};
}
Custom Export
exportDOM(editor: LexicalEditor): DOMExportOutput {
const element = document.createElement('div');
element.setAttribute('data-custom', 'value');
return { element };
}
Complete Example
import { createEditor } from 'lexical';
import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
import { CodeNode } from '@lexical/code';
import { LinkNode } from '@lexical/link';
import { ListNode, ListItemNode } from '@lexical/list';
const editor = createEditor({
nodes: [
HeadingNode,
QuoteNode,
CodeNode,
LinkNode,
ListNode,
ListItemNode
]
});
editor.setRootElement(document.getElementById('editor'));
// Import HTML
const htmlString = `
<h1>Hello World</h1>
<p>This is <b>bold</b> and <i>italic</i> text.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<a href="https://lexical.dev">Lexical</a>
`;
const parser = new DOMParser();
const dom = parser.parseFromString(htmlString, 'text/html');
editor.update(() => {
const nodes = $generateNodesFromDOM(editor, dom);
$getRoot().select();
$getRoot().clear();
$getRoot().append(...nodes);
});
// Export HTML
function exportHtml() {
return editor.getEditorState().read(() => {
return $generateHtmlFromNodes(editor);
});
}
const html = exportHtml();
console.log(html);
Headless (Server-Side) Example
import { createHeadlessEditor } from '@lexical/headless';
import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
import { JSDOM } from 'jsdom';
// Set up JSDOM
const jsdom = new JSDOM('<!DOCTYPE html>');
global.document = jsdom.window.document;
global.window = jsdom.window as any;
const editor = createHeadlessEditor({
nodes: [/* ... */]
});
// Import HTML
const htmlInput = '<h1>Server-side HTML</h1><p>Processing...</p>';
const dom = new JSDOM(htmlInput).window.document;
editor.update(() => {
const nodes = $generateNodesFromDOM(editor, dom);
$getRoot().append(...nodes);
});
// Process and export
const htmlOutput = editor.getEditorState().read(() => {
return $generateHtmlFromNodes(editor);
});
console.log(htmlOutput);
Common Use Cases
Paste HTML from Clipboard
import { $generateNodesFromDOM } from '@lexical/html';
editor.registerCommand(
PASTE_COMMAND,
(event: ClipboardEvent) => {
const html = event.clipboardData?.getData('text/html');
if (html) {
const parser = new DOMParser();
const dom = parser.parseFromString(html, 'text/html');
editor.update(() => {
const nodes = $generateNodesFromDOM(editor, dom);
const selection = $getSelection();
if ($isRangeSelection(selection)) {
selection.insertNodes(nodes);
}
});
return true;
}
return false;
},
COMMAND_PRIORITY_HIGH
);
Export Selection as HTML
import { $generateHtmlFromNodes } from '@lexical/html';
function copySelectionAsHtml() {
editor.getEditorState().read(() => {
const selection = $getSelection();
const html = $generateHtmlFromNodes(editor, selection);
// Copy to clipboard
navigator.clipboard.writeText(html);
});
}
Load Initial Content from HTML
import { $generateNodesFromDOM } from '@lexical/html';
function loadInitialContent(htmlString: string) {
const parser = new DOMParser();
const dom = parser.parseFromString(htmlString, 'text/html');
editor.update(() => {
const nodes = $generateNodesFromDOM(editor, dom);
$getRoot().clear().append(...nodes);
});
}