rehype vs remark vs unified
Building Custom Text Processing Pipelines with Unified, Remark, and Rehype
rehyperemarkunifiedSimilar Packages:

Building Custom Text Processing Pipelines with Unified, Remark, and Rehype

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
rehype02,24213.3 kB12 years agoMIT
remark08,97615.7 kB113 years agoMIT
unified05,018146 kB12 years agoMIT

Unified vs Remark vs Rehype: Architecting Text Transformation Pipelines

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.

🏗️ The Core Engine: Unified

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.

📝 Markdown Transformation: Remark

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.

🌐 HTML Manipulation: Rehype

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.

🔗 Connecting the Dots: The Full Pipeline

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).

⚖️ Key Differences at a Glance

Featureunifiedremarkrehype
Primary InputText (Generic)MarkdownHTML
AST TypeNone (Engine)mdast (Markdown)hast (HTML)
Typical UseCustom parsers, multi-format bridgesDocs, blogs, content lintingSanitization, analytics, DOM manipulation
Pre-configured?No (Bring your own parser/compiler)Yes (Markdown parser/compiler included)Yes (HTML parser/compiler included)

🛠️ When to Use Which?

Choose remark when:

  • Your source files are Markdown.
  • You need to enforce style guides on documentation (e.g., "no dead links", "heading capitalization").
  • You want to transform Markdown syntax before it becomes HTML (e.g., converting custom alerts > [!NOTE] into specific div structures).

Choose rehype when:

  • You already have HTML (perhaps from a CMS or a previous build step).
  • You need to sanitize content to prevent XSS attacks.
  • You want to inject scripts, modify meta tags, or optimize images in the final HTML output.
  • You need to manipulate the DOM structure (wrapping images in figures, adding icons to headings).

Choose unified directly when:

  • You are building a plugin that needs to work with both Markdown and HTML.
  • You are creating a parser for a custom format (like LaTeX or CSV) and want to leverage the plugin ecosystem.
  • You need fine-grained control over the data flow between parsing and compiling stages that the pre-built presets don't offer.

💡 Architectural Best Practices

  1. Keep Plugins Small: The ecosystem thrives on small, single-purpose plugins. Instead of writing one massive transformation function, chain several small plugins. This makes debugging easier and allows you to reuse logic across projects.
  2. Respect the AST: Never manipulate the raw string inside a plugin unless absolutely necessary. Always traverse and modify the AST (mdast or hast). This ensures your changes don't break the structure or introduce syntax errors.
  3. Order Matters: In a combined pipeline, 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.

🎯 Final Thoughts

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.

How to Choose: rehype vs remark vs unified

  • rehype:

    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.

  • remark:

    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.

  • unified:

    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.

README for rehype

rehype

Build Coverage Downloads Size Sponsors Backers Chat

unified processor to add support for parsing from HTML and serializing to HTML.

Contents

What is this?

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.

When should I use this?

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.

Install

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>

Use

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>

API

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.

Examples

Example: passing options to rehype-parse, rehype-stringify

When 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>

Syntax

HTML is parsed and serialized according to WHATWG HTML (the living standard), which is also followed by all browsers.

Syntax tree

The syntax tree format used in rehype is hast.

Types

This package is fully typed with TypeScript. It exports no additional types.

Compatibility

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.

Security

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.

Contribute

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.

Sponsor

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?

License

MIT © Titus Wormer