html-dom-parser
html-dom-parser converts an HTML string into domhandler node objects on both Node and browsers. On the server, version 8 wraps htmlparser2 and removes the synthetic root parent. In a browser bundle, it parses through native DOM APIs and converts the result back into matching Element, Text, Comment, and ProcessingInstruction objects. The output preserves parent, previous-sibling, next-sibling, children, tag-name, and attribute links, making it a low-level bridge for tools that want one traversable tree shape in both environments.
A solid compatibility layer when domhandler nodes are the required interchange format across Node and browsers. Use htmlparser2 directly for server-only work, and add an explicit sanitizer before any untrusted tree reaches rendered DOM.
Use it if
- You need domhandler-compatible nodes from HTML fragments in both Node and browser bundles
- You are building or maintaining tooling around html-react-parser, htmlparser2, or another package that already expects domhandler nodes
- Server code needs htmlparser2 options such as xmlMode, decodeEntities, or case-control flags
- A browser with Trusted Types enforcement needs a caller-supplied policy before the parser assigns HTML to a template or document
- You need sanitization: the parser preserves elements and attributes and the Trusted Types example delegates cleaning to DOMPurify, so untrusted HTML is not made safe merely by parsing it
- You need identical parsing knobs in every runtime: the README says browser parsing uses native DOM behavior and ignores server parser options apart from trustedTypePolicy
- You need a standards-focused whole-document parser with source locations and serialization: this package strips the server root parent and does not expose a serializer
- You only need server parsing: htmlparser2 already provides parseDOM, and the README describes the server implementation as a wrapper around it
- You need ES5 or Internet Explorer support: v8 changed build output to ES6, while v2 had already removed IE11 support
Setup reality
npm install html-dom-parser installs two pinned runtime dependencies: domhandler 6.0.1 and htmlparser2 12.0.0. There are no peer dependencies, native builds, credentials, or project config. Version 8 publishes ESM, CommonJS, browser-conditional, and React Native mappings plus TypeScript declarations. ESM uses a default import, but CommonJS must use require('html-dom-parser').default; v5 introduced that .default requirement and old examples without it fail with a not-a-function error. Runtime selection is done by package conditions and browser mappings. Node receives the htmlparser2 implementation and can use parser and handler options. Browser bundlers receive the client implementation, which uses template.innerHTML, createHTMLDocument, or DOMParser and accepts only trustedTypePolicy as a meaningful option. That means malformed markup, implied elements, tag casing, and document wrappers can differ between server and browser native parsing, so hydration-sensitive output needs cross-runtime tests. Parsing an empty string returns an empty array, while a non-string throws TypeError. Results are cyclic: child.parent points upward and siblings link through prev and next, so JSON.stringify on a tree throws unless you strip links or use a cycle-aware serializer. Parsing is not sanitizing. A Trusted Types policy only changes what gets assigned to innerHTML; its createHTML callback must apply a real sanitizer if input is untrusted. Server options such as xmlMode and decodeEntities can also change tree meaning, and the browser path cannot mirror them. Version 8 targets ES6, so legacy browser transpilation is the application's responsibility.
Patterns
Parse an HTML fragmentparse-html-fragment
import parse from 'html-dom-parser';
const nodes = parse('<p class="lead">Hello <em>there</em></p>');
const paragraph = nodes[0];
console.log(paragraph.type, paragraph.name, paragraph.attribs.class);The function returns an array because a fragment can contain multiple root nodes, including text and comments.
Load version 8 from CommonJSrequire-commonjs
const parse = require('html-dom-parser').default;
const nodes = parse('<strong>Important</strong>');CommonJS callers need .default. The migration guide says this changed in v5.
Handle an empty fragmenthandle-empty-input
const nodes = parse('');
console.log(nodes.length); // 0An empty string returns an empty array. A non-string value throws TypeError instead of returning an empty result.
Recursively walk elements and textwalk-node-tree
function walk(node) {
if (node.type === 'text') console.log(node.data);
if ('children' in node) {
for (const child of node.children) walk(child);
}
}
for (const node of parse('<p>Hello <em>there</em></p>')) walk(node);domhandler nodes also contain parent, prev, and next links. Use children for a downward traversal and avoid revisiting parent links.
Read parsed attributesread-element-attributes
const [link] = parse('<a href="/docs" data-kind="local">Docs</a>');
if (link.type === 'tag') {
console.log(link.attribs.href, link.attribs['data-kind']);
}Attributes are plain strings in an attribs object. Parsing does not validate URLs or remove event-handler attributes.
Parse XML-style markup on the serverparse-xml-mode
const nodes = parse('<feed><entry /></feed>', {
xmlMode: true,
recognizeSelfClosing: true,
});These htmlparser2 options apply to the Node server parser. Browser parsing uses native DOM behavior and cannot mirror the option set.
Keep encoded entities on the serverpreserve-entity-text
const [text] = parse('Tom & Jerry', {
decodeEntities: false,
});
console.log(text.data); // Tom & JerrydecodeEntities is a server option. Browser DOM parsing decodes according to the browser and ignores this setting.
Preserve server-side XML tag casecontrol-tag-case
const [node] = parse('<LinearGradient />', {
xmlMode: true,
lowerCaseTags: false,
});
console.log(node.type === 'tag' && node.name); // LinearGradientHTML mode normally lowercases tags. Browser parsing follows HTML DOM rules and has its own SVG case-name mapping.
Sanitize through a browser Trusted Types policyuse-trusted-types-policy
import DOMPurify from 'dompurify';
import parse from 'html-dom-parser';
const policy = window.trustedTypes?.createPolicy('parsed-html', {
createHTML(input) {
return DOMPurify.sanitize(input);
},
});
const nodes = parse(untrustedHTML, { trustedTypePolicy: policy });DOMPurify is a separate dependency. A Trusted Types policy that simply returns input would satisfy the API but would not sanitize anything.
Validate the input boundaryreject-non-string-input
function parseMarkup(value) {
if (typeof value !== 'string') {
throw new TypeError('markup must be a string');
}
return parse(value);
}Version 8 throws TypeError when the first argument is not a string on both server and browser paths.
Project nodes into JSON-safe datastrip-cycles-for-json
function toJSONSafe(node) {
if (node.type === 'text' || node.type === 'comment') {
return { type: node.type, data: node.data };
}
return {
type: node.type,
name: 'name' in node ? node.name : undefined,
attributes: 'attribs' in node ? node.attribs : undefined,
children: 'children' in node ? node.children.map(toJSONSafe) : [],
};
}
const safe = parse('<p>Hello</p>').map(toJSONSafe);Direct JSON.stringify fails on normal element trees because child.parent points back to its parent. Project only the fields you need.
Collect matching elements without a selector enginefind-elements-by-name
function findAll(nodes, name, found = []) {
for (const node of nodes) {
if (node.type === 'tag' && node.name === name) found.push(node);
if ('children' in node) findAll(node.children, name, found);
}
return found;
}
const links = findAll(parse(html), 'a');html-dom-parser supplies a tree, not CSS selectors. Use Cheerio if selection and mutation are the main job.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| htmlparser2 | npm | Node-only parsing where you want the underlying streaming parser, parseDOM API, and direct option control |
| parse5 | npm | You need specification-oriented HTML document parsing, serialization, or source location information |
| linkedom | npm | Server code needs browser-like Document and DOM APIs rather than domhandler nodes |
| cheerio | npm | You want jQuery-style selection and mutation on server-parsed markup instead of manual tree traversal |