unified, remark, and rehype form a composable ecosystem for parsing, transforming, and generating text using abstract syntax trees (ASTs). unified is the core engine that manages plugins and data flow between parsers and compilers. remark extends unified to specifically handle Markdown, converting it to and from an AST. rehype extends unified to handle HTML, operating on the HTML AST (HAST). Together, they allow developers to build powerful tools like static site generators, linters, formatters, and content management systems by chaining small, focused plugins.
When building tools that process text—whether it's documentation, blog posts, or user comments—you often need more than simple string replacement. The unified ecosystem provides a robust way to parse text into structured data (Abstract Syntax Trees), modify that data safely, and compile it back into text. While unified, remark, and rehype are often used together, they solve distinct problems. Let's break down their roles and how they fit into a modern frontend architecture.
unified is the backbone. It doesn't know about Markdown or HTML on its own. Instead, it provides the interface to attach a parser (turns text into a tree) and a compiler (turns a tree back into text). You use unified directly when you need to bridge different formats or build a highly custom processor.
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
// Creating a custom Markdown processor using the core engine
const processor = unified()
.use(remarkParse) // Attach Markdown parser
.use(remarkStringify); // Attach Markdown compiler
const file = await processor.process('# Hello World');
console.log(String(file)); // Output: '# Hello World\n'
If you only need to work with Markdown or HTML, you usually won't call unified directly. Instead, you'll use the pre-configured wrappers (remark or rehype) which already include the necessary parsers and compilers.
remark is unified pre-configured for Markdown. It parses Markdown into a mdast (Markdown AST) and compiles it back to Markdown or other formats. This is your go-to tool for authoring workflows, documentation sites, and content pipelines where the source of truth is a .md file.
Use Case: Automatically adding a Table of Contents or converting specific syntax.
import remark from 'remark';
import remarkToc from 'remark-toc';
const file = await remark()
.use(remarkToc) // Plugin to inject Table of Contents
.process('# My Article\n\nSome content...');
console.log(String(file));
// Output includes a generated [Table of Contents] section
Because remark operates on the Markdown AST, it understands the semantic structure of your document (headings, lists, code blocks) rather than just the raw characters. This makes transformations safer and more reliable than regex-based approaches.
rehype is unified pre-configured for HTML. It parses HTML into a hast (HTML AST) and compiles it back to HTML. Crucially, rehype runs after Markdown has been converted to HTML. This is the right place to handle browser-specific concerns, security sanitization, or injecting external resources.
Use Case: Sanitizing user input or adding target="_blank" to external links.
import rehype from 'rehype';
import rehypeExternalLinks from 'rehype-external-links';
import rehypeSanitize from 'rehype-sanitize';
const htmlString = '<p>Check <a href="https://example.com">this link</a></p>';
const file = await rehype()
.use(rehypeSanitize) // Remove dangerous tags/scripts
.use(rehypeExternalLinks) // Add target="_blank" to external links
.process(htmlString);
console.log(String(file));
// Output: <p>Check <a href="https://example.com" target="_blank" rel="noopener">this link</a></p>
Since rehype works with the HTML AST, it treats your content like a DOM tree. This allows you to manipulate attributes, wrap elements, or reorder nodes with the same precision you'd have using document.querySelector in the browser, but running on the server or during a build step.
In real-world applications, you often need to chain these tools. A common pattern in static site generators is: Markdown (remark) → HTML (rehype) → Final Output.
You can pass the output of a remark processor directly into a rehype processor because remark can compile to HTML (using remark-rehype), and rehype expects HTML input.
import remark from 'remark';
import remarkRehype from 'remark-rehype';
import rehype from 'rehype';
import rehypeSlug from 'rehype-slug';
// 1. Parse Markdown and convert to HTML AST
// 2. Pass HTML AST to Rehype for further processing
const file = await remark()
.use(remarkRehype) // Converts mdast -> hast
.use(rehypeSlug) // Adds ID attributes to headings for anchor links
.process('# Getting Started\n\nWelcome to the guide.');
console.log(String(file));
// Output: <h1 id="getting-started">Getting Started</h1>...
This separation of concerns is powerful. remark handles the authoring experience (Markdown features), while rehype handles the delivery experience (HTML optimization and security).
| Feature | unified | remark | rehype |
|---|---|---|---|
| Primary Input | Text (Generic) | Markdown | HTML |
| AST Type | None (Engine) | mdast (Markdown) | hast (HTML) |
| Typical Use | Custom parsers, multi-format bridges | Docs, blogs, content linting | Sanitization, analytics, DOM manipulation |
| Pre-configured? | No (Bring your own parser/compiler) | Yes (Markdown parser/compiler included) | Yes (HTML parser/compiler included) |
remark when:> [!NOTE] into specific div structures).rehype when:unified directly when:remark plugins must run before rehype plugins. Once you convert Markdown to HTML, you lose Markdown-specific information (like the difference between *emphasis* and _emphasis_), so you can't go back.Think of unified as the factory assembly line, remark as the station that shapes raw materials (Markdown) into components, and rehype as the station that paints and packages those components (HTML) for delivery.
For most frontend developers building content sites, you will spend 80% of your time configuring remark plugins to handle content authoring needs and 20% configuring rehype plugins to handle security and performance optimizations. Understanding where the boundary lies between Markdown processing and HTML manipulation is the key to building maintainable, scalable content pipelines.
Choose rehype if you need to manipulate HTML content after it has been generated. It is ideal for tasks like sanitizing user-generated HTML, injecting analytics scripts, optimizing images, or modifying the DOM structure of your final output before serving it to the browser.
Choose remark if your primary workflow involves parsing, linting, or transforming Markdown content. It is the standard choice for converting Markdown to HTML, checking for style guide compliance, or automating changes to documentation files before they are rendered.
Choose unified if you are building a custom processor from scratch, need to support multiple input formats (like Markdown and HTML) in one pipeline, or want to create your own parser/compiler pair. It is the foundational layer required when remark or rehype alone do not fit your specific data transformation needs.
unified processor to add support for parsing from HTML and serializing to HTML.
This package is a unified processor with support for parsing HTML as input
and serializing HTML as output by using unified with
rehype-parse and rehype-stringify.
See the monorepo readme for info on what the rehype ecosystem is.
You can use this package when you want to use unified, have HTML as input, and
want HTML as output.
This package is a shortcut for
unified().use(rehypeParse).use(rehypeStringify).
When the input isn’t HTML (meaning you don’t need rehype-parse) or the
output is not HTML (you don’t need rehype-stringify), it’s recommended to
use unified directly.
When you’re in a browser, trust your content, don’t need positional info on
nodes or formatting options, and value a smaller bundle size, you can use
rehype-dom instead.
When you want to inspect and format HTML files in a project on the command
line, you can use rehype-cli.
This package is ESM only. In Node.js (version 16+), install with npm:
npm install rehype
In Deno with esm.sh:
import {rehype} from 'https://esm.sh/rehype@13'
In browsers with esm.sh:
<script type="module">
import {rehype} from 'https://esm.sh/rehype@13?bundle'
</script>
Say we have the following module example.js:
import {rehype} from 'rehype'
import rehypeFormat from 'rehype-format'
const file = await rehype().use(rehypeFormat).process(`<!doctype html>
<html lang=en>
<head>
<title>Hi!</title>
</head>
<body>
<h1>Hello!</h1>
</body></html>`)
console.error(String(file))
…running that with node example.js yields:
<!doctype html>
<html lang="en">
<head>
<title>Hi!</title>
</head>
<body>
<h1>Hello!</h1>
</body>
</html>
This package exports the identifier rehype.
There is no default export.
rehype()Create a new unified processor that already uses
rehype-parse and rehype-stringify.
You can add more plugins with use.
See unified for more information.
rehype-parse, rehype-stringifyWhen you use rehype-parse or rehype-stringify manually you can pass options
directly to them with use.
Because both plugins are already used in rehype, that’s not possible.
To define options for them, you can instead pass options to data:
import {rehype} from 'rehype'
import {reporter} from 'vfile-reporter'
const file = await rehype()
.data('settings', {
emitParseErrors: true,
fragment: true,
preferUnquoted: true
})
.process('<div title="a" title="b"></div>')
console.error(reporter(file))
console.log(String(file))
…yields:
1:21-1:21 warning Unexpected duplicate attribute duplicate-attribute hast-util-from-html
⚠ 1 warning
<div title=a></div>
HTML is parsed and serialized according to WHATWG HTML (the living standard), which is also followed by all browsers.
The syntax tree format used in rehype is hast.
This package is fully typed with TypeScript. It exports no additional types.
Projects maintained by the unified collective are compatible with maintained versions of Node.js.
When we cut a new major release, we drop support for unmaintained versions of
Node.
This means we try to keep the current release line, rehype@^13, compatible
with Node.js 16.
As rehype works on HTML, and improper use of HTML can open you up to a
cross-site scripting (XSS) attack, use of rehype can also be unsafe.
Use rehype-sanitize to make the tree safe.
Use of rehype plugins could also open you up to other attacks. Carefully assess each plugin and the risks involved in using them.
For info on how to submit a report, see our security policy.
See contributing.md in rehypejs/.github for ways
to get started.
See support.md for ways to get help.
This project has a code of conduct. By interacting with this repository, organization, or community you agree to abide by its terms.
Support this effort and give back by sponsoring on OpenCollective!
|
Vercel |
Motif |
HashiCorp |
GitBook |
Gatsby | |||||
Netlify
|
Coinbase |
ThemeIsle |
Expo |
Boost Note
|
Markdown Space
|
Holloway | |||
|
You? | |||||||||