unified vs remark vs retext
Processing Text Content: Markdown vs Natural Language vs Core Engine
unifiedremarkretextSimilar Packages:

Processing Text Content: Markdown vs Natural Language vs Core Engine

remark, retext, and unified are part of the same ecosystem designed for parsing, transforming, and serializing text content using syntax trees. unified is the core engine that provides the interface for plugins and processing pipelines. remark is a specialized tool built on unified for working with Markdown content. retext is also built on unified but focuses on natural language text for tasks like readability analysis or profanity filtering. Together, they allow developers to manipulate content programmatically with a shared plugin architecture.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
unified37,335,5355,042146 kB12 years agoMIT
remark4,161,9679,02415.7 kB103 years agoMIT
retext3,070,2262,43410.3 kB03 years agoMIT

remark vs retext vs unified: Architecture and Use Cases Compared

remark, retext, and unified are closely related tools often used together in the JavaScript ecosystem. They share the same underlying engine but serve different purposes depending on the type of content you need to process. Understanding their relationship helps you avoid unnecessary configuration while keeping your code flexible. Let's break down how they differ in setup, capabilities, and real-world application.

🛠️ Core Purpose: Markdown vs Natural Language vs Engine

remark is built specifically for Markdown.

  • It includes a parser and stringifier for Markdown syntax out of the box.
  • Best for converting Markdown to HTML or modifying Markdown files directly.
// remark: Ready for Markdown immediately
import remark from 'remark';
import html from 'remark-html';

const file = await remark().use(html).process('# Hello');
console.log(String(file)); // <h1>Hello</h1>

retext is built for natural language text.

  • It parses plain text into a linguistic structure rather than markup.
  • Best for readability checks, profanity filtering, or grammar analysis.
// retext: Ready for natural language
import retext from 'retext';
import english from 'retext-english';

const file = await retext().use(english).process('Hello world');
console.log(String(file)); // Hello world

unified is the core engine behind both.

  • It does not include a parser by default.
  • Best for custom pipelines or when you need to mix syntaxes.
// unified: Requires manual parser setup
import unified from 'unified';
import remarkParse from 'remark-parse';
import remarkHtml from 'remark-html';

const file = await unified()
  .use(remarkParse)
  .use(remarkHtml)
  .process('# Hello');
console.log(String(file)); // <h1>Hello</h1>

🔌 Plugin Ecosystem: Shared Architecture

All three tools use the same plugin system based on unist (Universal Syntax Tree). This means many plugins work across remark and retext if they operate on the tree structure rather than specific syntax.

remark plugins often target Markdown nodes.

  • You can add plugins to lint code blocks or extract headings.
// remark: Adding a plugin
import remark from 'remark';
import toc from 'remark-toc';

const file = await remark().use(toc).process('# Title\n\nContent');

retext plugins target natural language nodes.

  • You can add plugins to check sentence length or passive voice.
// retext: Adding a plugin
import retext from 'retext';
import readability from 'retext-readability';

const file = await retext().use(readability).process('Long sentence here.');

unified allows mixing plugins from both worlds.

  • You can create a pipeline that handles both Markdown and text analysis.
// unified: Mixing plugins
import unified from 'unified';
import remarkParse from 'remark-parse';
import retextEnglish from 'retext-english';

// Custom pipeline combining parsers
const processor = unified().use(remarkParse).use(retextEnglish);

⚙️ Configuration Flexibility: Pre-set vs Manual

The main trade-off is between convenience and control. remark and retext are essentially pre-configured instances of unified.

remark reduces boilerplate.

  • You do not need to import the Markdown parser separately.
  • Ideal for standard Markdown workflows where speed matters.
// remark: Minimal setup
import remark from 'remark';
// Parser is implicit

retext reduces boilerplate for text.

  • You do not need to import the natural language parser separately.
  • Ideal for standard text analysis workflows.
// retext: Minimal setup
import retext from 'retext';
// Parser is implicit

unified requires explicit setup.

  • You must choose and import every parser and stringifier.
  • Ideal when you need to swap parsers or support multiple formats.
// unified: Explicit setup
import unified from 'unified';
import parser from 'remark-parse'; // You choose this
import stringifier from 'remark-stringify'; // You choose this

const processor = unified().use(parser).use(stringifier);

🌐 Real-World Scenarios

Scenario 1: Blog Engine

You need to convert Markdown posts to HTML and generate a table of contents.

  • ✅ Best choice: remark
  • Why? It handles Markdown parsing and HTML conversion with minimal config.
// remark: Blog processing
import remark from 'remark';
import html from 'remark-html';
import toc from 'remark-toc';

await remark().use(toc).use(html).process(markdownContent);

Scenario 2: Comment Moderation

You need to scan user comments for profanity and readability issues.

  • ✅ Best choice: retext
  • Why? It is optimized for plain text analysis without Markdown overhead.
// retext: Moderation
import retext from 'retext';
import profanity from 'retext-profanities';

await retext().use(profanity).process(userComment);

Scenario 3: Custom CMS Pipeline

You need to process content that might be Markdown or plain text depending on the user.

  • ✅ Best choice: unified
  • Why? You can conditionally attach parsers based on input type.
// unified: Dynamic pipeline
import unified from 'unified';
import remarkParse from 'remark-parse';
import retextEnglish from 'retext-english';

const processor = unified().use(isMarkdown ? remarkParse : retextEnglish);

🤝 Similarities: Shared Ground Between remark, retext, and unified

While their focus differs, all three tools share the same foundational design. Here are key overlaps:

1. 🌲 Unified Syntax Tree (unist)

  • All three generate compatible syntax trees.
  • Plugins written for one often work on the others if they target generic nodes.
// Example: Visiting nodes works similarly
import { visit } from 'unist-util-visit';

// Works in remark, retext, and unified pipelines
function transformer(tree) {
  visit(tree, 'text', (node) => { /* ... */ });
}

2. ⚡ Async Processing Interface

  • All use .process() for async operations and .processSync() for sync.
  • Return a vfile object containing content and metadata.
// remark: Async
await remark().process('# Hi');

// retext: Async
await retext().process('Hi');

// unified: Async
await unified().use(plugin).process('# Hi');

3. 🔗 Plugin Middleware Pattern

  • All use .use() to chain plugins in order.
  • Plugins can modify the tree, add metadata, or change output.
// remark: Chain plugins
remark().use(pluginA).use(pluginB);

// retext: Chain plugins
retext().use(pluginA).use(pluginB);

// unified: Chain plugins
unified().use(pluginA).use(pluginB);

4. 📦 VFile Integration

  • All rely on vfile to manage file path, content, and messages.
  • Enables consistent error reporting and metadata handling.
// All produce vfile
const file = await remark().process('# Hi');
console.log(file.path); // undefined or set path
console.log(file.messages); // List of warnings/errors

5. 👥 Active Maintenance

  • All are maintained by the unifiedjs collective.
  • Regular updates ensure compatibility with modern Node.js and ESM.
// Example: ESM Imports work across all
import remark from 'remark';
import retext from 'retext';
import unified from 'unified';

📊 Summary: Key Similarities

FeatureShared by remark, retext, unified
Core Engine🔧 Built on unified interface
Syntax Tree🌲 Uses unist standard
Plugin System🔌 .use() middleware pattern
File Handling📦 Uses vfile for content/metadata
Processing⚡ .process() and .processSync()
Maintenance👥 unifiedjs collective

🆚 Summary: Key Differences

Featureremarkretextunified
Input Type📝 Markdown💬 Natural Language🛠️ Custom / Any
Default Parser✅ Included (Markdown)✅ Included (Natural Language)❌ None (Manual setup)
Setup Effort🚀 Low (Pre-configured)🚀 Low (Pre-configured)⚙️ High (Manual config)
Best ForBlogs, Docs, CMSModeration, AnalysisCustom Pipelines, Mixed Types
Flexibility🟢 High for Markdown🟢 High for Text🟣 Maximum for Everything

💡 The Big Picture

remark is like a specialized Markdown toolkit 📝—perfect for teams building documentation, blogs, or any system where Markdown is the source of truth. It removes boilerplate so you can focus on content transformation.

retext is like a text analysis kit 🔍—ideal for features that require understanding human language, such as comment moderation, readability scoring, or auto-correction. It handles the linguistic heavy lifting.

unified is like the engine block 🏗️—best for architects who need to build custom tools or combine multiple syntaxes. It gives you full control but requires more initial setup.

Final Thought: Since remark and retext are built on unified, you are never locked in. You can start with remark for simplicity and switch to unified later if you need deeper control. Choose based on your input format — Markdown, plain text, or something custom.

How to Choose: unified vs remark vs retext

  • unified:

    Choose unified if you need full control over the parsing and stringifying steps or are working with custom syntaxes not covered by remark or retext. It requires you to manually attach parsers and plugins, offering maximum flexibility for building custom processors or combining multiple syntax types in one pipeline.

  • remark:

    Choose remark if your primary goal is to process Markdown files, such as converting them to HTML, linting syntax, or extracting metadata. It comes pre-configured with a Markdown parser and stringifier, saving you from manual setup. It is the standard choice for blogs, documentation sites, and content management systems that rely on Markdown.

  • retext:

    Choose retext if you need to analyze or transform plain natural language text rather than structured markup. It is ideal for checking readability scores, filtering profanity, or performing sentiment analysis on user-generated content. Use this when your input is raw text without Markdown syntax.

README for unified

unified

Build Coverage Downloads Size Sponsors Backers Chat

unified lets you inspect and transform content with plugins.

Contents

What is this?

unified is two things:

  • unified is a collective of 500+ free and open source packages that work with content as structured data (ASTs)
  • unified (this project) is the core package, used in 1.3m+ projects on GH, to process content with plugins

Several ecosystems are built on unified around different kinds of content. Notably, remark (markdown), rehype (HTML), and retext (natural language). These ecosystems can be connected together.

When should I use this?

In some cases, you are already using unified. For example, it’s used in MDX, Gatsby, Docusaurus, etc. In those cases, you don’t need to add unified yourself but you can include plugins into those projects.

But the real fun (for some) is to get your hands dirty and work with syntax trees and build with it yourself. You can create those projects, or things like Prettier, or your own site generator. You can connect utilities together and make your own plugins that check for problems and transform from one thing to another.

When you are dealing with one type of content (such as markdown), you can use the main package of that ecosystem instead (so remark). When you are dealing with different kinds of content (such as markdown and HTML), it’s recommended to use unified itself, and pick and choose the plugins you need.

Install

This package is ESM only. In Node.js (version 16+), install with npm:

npm install unified

In Deno with esm.sh:

import {unified} from 'https://esm.sh/unified@11'

In browsers with esm.sh:

<script type="module">
  import {unified} from 'https://esm.sh/unified@11?bundle'
</script>

Use

import rehypeDocument from 'rehype-document'
import rehypeFormat from 'rehype-format'
import rehypeStringify from 'rehype-stringify'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import {unified} from 'unified'
import {reporter} from 'vfile-reporter'

const file = await unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeDocument, {title: '👋🌍'})
  .use(rehypeFormat)
  .use(rehypeStringify)
  .process('# Hello world!')

console.error(reporter(file))
console.log(String(file))

Yields:

no issues found
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>👋🌍</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <h1>Hello world!</h1>
  </body>
</html>

Overview

unified is an interface for processing content with syntax trees. Syntax trees are a representation of content understandable to programs. Those programs, called plugins, take these trees and inspect and modify them. To get to the syntax tree from text, there is a parser. To get from that back to text, there is a compiler. This is the process of a processor.

| ........................ process ........................... |
| .......... parse ... | ... run ... | ... stringify ..........|

          +--------+                     +----------+
Input ->- | Parser | ->- Syntax Tree ->- | Compiler | ->- Output
          +--------+          |          +----------+
                              X
                              |
                       +--------------+
                       | Transformers |
                       +--------------+
Processors

Processors process content. On its own, unified (the root processor) doesn’t work. It needs to be configured with plugins to work. For example:

const processor = unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeDocument, {title: '👋🌍'})
  .use(rehypeFormat)
  .use(rehypeStringify)

That processor can do different things. It can:

  • …parse markdown (parse)
  • …turn parsed markdown into HTML and format the HTML (run)
  • …compile HTML (stringify)
  • …do all of the above (process)

Every processor implements another processor. To create a processor, call another processor. The new processor is configured to work the same as its ancestor. But when the descendant processor is configured in the future it does not affect the ancestral processor.

When processors are exposed from a module (for example, unified itself) they should not be configured directly, as that would change their behavior for all module users. Those processors are frozen and they should be called to create a new processor before they are used.

File

When processing a document, metadata is gathered about that document. vfile is the file format that stores data, metadata, and messages about files for unified and plugins.

There are several utilities for working with these files.

Syntax tree

The syntax trees used in unified are unist nodes. A tree represents a whole document and each node is a plain JavaScript object with a type field. The semantics of nodes and the format of syntax trees is defined by other projects:

There are many utilities for working with trees listed in each aforementioned project and maintained in the syntax-tree organization. These utilities are a level lower than unified itself and are building blocks that can be used to make plugins.

Ecosystems

Around each syntax tree is an ecosystem that focusses on that particular kind of content. At their core, they parse text to a tree and compile that tree back to text. They also provide plugins that work with the syntax tree, without requiring that the end user has knowledge about that tree.

Plugins

Each aforementioned ecosystem comes with a large set of plugins that you can pick and choose from to do all kinds of things.

There are also a few plugins that work in any ecosystem:

Configuration

Processors are configured with plugins or with the data method. Most plugins also accept configuration through options. See each plugin’s readme for more info.

Integrations

unified can integrate with the file system through unified-engine. CLI apps can be created with unified-args, Gulp plugins with unified-engine-gulp, and language servers with unified-language-server. A streaming interface can be created with unified-stream.

Programming interface

The API provided by unified allows multiple files to be processed and gives access to metadata (such as lint messages):

import rehypeStringify from 'rehype-stringify'
import remarkParse from 'remark-parse'
import remarkPresetLintMarkdownStyleGuide from 'remark-preset-lint-markdown-style-guide'
import remarkRehype from 'remark-rehype'
import remarkRetext from 'remark-retext'
import retextEnglish from 'retext-english'
import retextEquality from 'retext-equality'
import {unified} from 'unified'
import {reporter} from 'vfile-reporter'

const file = await unified()
  .use(remarkParse)
  .use(remarkPresetLintMarkdownStyleGuide)
  .use(remarkRetext, unified().use(retextEnglish).use(retextEquality))
  .use(remarkRehype)
  .use(rehypeStringify)
  .process('*Emphasis* and _stress_, you guys!')

console.error(reporter(file))
console.log(String(file))

Yields:

1:16-1:24 warning Emphasis should use `*` as a marker                                 emphasis-marker remark-lint
1:30-1:34 warning `guys` may be insensitive, use `people`, `persons`, `folks` instead gals-man        retext-equality

⚠ 2 warnings
<p><em>Emphasis</em> and <em>stress</em>, you guys!</p>

Transforming between ecosystems

Ecosystems can be combined in two modes.

Bridge mode transforms the tree from one format (origin) to another (destination). A different processor runs on the destination tree. Afterwards, the original processor continues with the origin tree.

Mutate mode also transforms the syntax tree from one format to another. But the original processor continues transforming the destination tree.

In the previous example (“Programming interface”), remark-retext is used in bridge mode: the origin syntax tree is kept after retext is done; whereas remark-rehype is used in mutate mode: it sets a new syntax tree and discards the origin tree.

The following plugins lets you combine ecosystems:

API

This package exports the identifier unified (the root processor). There is no default export.

processor()

Create a new processor.

Returns

New unfrozen processor (processor).

This processor is configured to work the same as its ancestor. When the descendant processor is configured in the future it does not affect the ancestral processor.

Example

This example shows how a new processor can be created (from remark) and linked to stdin(4) and stdout(4).

import process from 'node:process'
import concatStream from 'concat-stream'
import {remark} from 'remark'

process.stdin.pipe(
  concatStream(function (buf) {
    process.stdout.write(String(remark().processSync(buf)))
  })
)

processor.compiler

Compiler to use (Compiler, optional).

processor.data([key[, value]])

Configure the processor with info available to all plugins. Information is stored in an object.

Typically, options can be given to a specific plugin, but sometimes it makes sense to have information shared with several plugins. For example, a list of HTML elements that are self-closing, which is needed during all phases.

👉 Note: setting information cannot occur on frozen processors. Call the processor first to create a new unfrozen processor.

👉 Note: to register custom data in TypeScript, augment the Data interface.

Signatures
  • processor = processor.data(key, value)
  • processor = processor.data(dataset)
  • value = processor.data(key)
  • dataset = processor.data()
Parameters
Returns

The current processor when setting (processor), the value at key when getting (Data[key]), or the entire dataset when getting without key (Data).

Example

This example show how to get and set info:

import {unified} from 'unified'

const processor = unified().data('alpha', 'bravo')

processor.data('alpha') // => 'bravo'

processor.data() // => {alpha: 'bravo'}

processor.data({charlie: 'delta'})

processor.data() // => {charlie: 'delta'}

processor.freeze()

Freeze a processor.

Frozen processors are meant to be extended and not to be configured directly.

When a processor is frozen it cannot be unfrozen. New processors working the same way can be created by calling the processor.

It’s possible to freeze processors explicitly by calling .freeze(). Processors freeze automatically when .parse(), .run(), .runSync(), .stringify(), .process(), or .processSync() are called.

Returns

The current processor (processor).

Example

This example, index.js, shows how rehype prevents extensions to itself:

import rehypeParse from 'rehype-parse'
import rehypeStringify from 'rehype-stringify'
import {unified} from 'unified'

export const rehype = unified().use(rehypeParse).use(rehypeStringify).freeze()

That processor can be used and configured like so:

import {rehype} from 'rehype'
import rehypeFormat from 'rehype-format'
// …

rehype()
  .use(rehypeFormat)
  // …

A similar looking example is broken as operates on the frozen interface. If this behavior was allowed it would result in unexpected behavior so an error is thrown. This is not valid:

import {rehype} from 'rehype'
import rehypeFormat from 'rehype-format'
// …

rehype
  .use(rehypeFormat)
  // …

Yields:

~/node_modules/unified/index.js:426
    throw new Error(
    ^

Error: Cannot call `use` on a frozen processor.
Create a new processor first, by calling it: use `processor()` instead of `processor`.
    at assertUnfrozen (~/node_modules/unified/index.js:426:11)
    at Function.use (~/node_modules/unified/index.js:165:5)
    …

processor.parse(file)

Parse text to a syntax tree.

👉 Note: parse freezes the processor if not already frozen.

👉 Note: parse performs the parse phase, not the run phase or other phases.

Parameters
  • file (Compatible) — file to parse; typically string or VFile; any value accepted as x in new VFile(x)
Returns

Syntax tree representing file (Node).

Example

This example shows how parse can be used to create a tree from a file.

import remarkParse from 'remark-parse'
import {unified} from 'unified'

const tree = unified().use(remarkParse).parse('# Hello world!')

console.log(tree)

Yields:

{
  type: 'root',
  children: [
    {type: 'heading', depth: 1, children: [Array], position: [Object]}
  ],
  position: {
    start: {line: 1, column: 1, offset: 0},
    end: {line: 1, column: 15, offset: 14}
  }
}

processor.parser

Parser to use (Parser, optional).

processor.process(file[, done])

Process the given file as configured on the processor.

👉 Note: process freezes the processor if not already frozen.

👉 Note: process performs the parse, run, and stringify phases.

Signatures
  • processor.process(file, done)
  • Promise<VFile> = processor.process(file?)
Parameters
  • file (Compatible, optional) — file; typically string or VFile; any value accepted as x in new VFile(x)
  • done (ProcessCallback, optional) — callback
Returns

Nothing if done is given (undefined). Otherwise a promise, rejected with a fatal error or resolved with the processed file (Promise<VFile>).

The parsed, transformed, and compiled value is available at file.value (see note).

👉 Note: unified typically compiles by serializing: most compilers return string (or Uint8Array). Some compilers, such as the one configured with rehype-react, return other values (in this case, a React tree). If you’re using a compiler that doesn’t serialize, expect different result values.

To register custom results in TypeScript, add them to CompileResultMap.

Example

This example shows how process can be used to process a file:

import rehypeDocument from 'rehype-document'
import rehypeFormat from 'rehype-format'
import rehypeStringify from 'rehype-stringify'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import {unified} from 'unified'

const file = await unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeDocument, {title: '👋🌍'})
  .use(rehypeFormat)
  .use(rehypeStringify)
  .process('# Hello world!')

console.log(String(file))

Yields:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>👋🌍</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <h1>Hello world!</h1>
  </body>
</html>

processor.processSync(file)

Process the given file as configured on the processor.

An error is thrown if asynchronous transforms are configured.

👉 Note: processSync freezes the processor if not already frozen.

👉 Note: processSync performs the parse, run, and stringify phases.

Parameters
  • file (Compatible, optional) — file; typically string or VFile; any value accepted as x in new VFile(x)
Returns

The processed file (VFile).

The parsed, transformed, and compiled value is available at file.value (see note).

👉 Note: unified typically compiles by serializing: most compilers return string (or Uint8Array). Some compilers, such as the one configured with rehype-react, return other values (in this case, a React tree). If you’re using a compiler that doesn’t serialize, expect different result values.

To register custom results in TypeScript, add them to CompileResultMap.

Example

This example shows how processSync can be used to process a file, if all transformers are synchronous.

import rehypeDocument from 'rehype-document'
import rehypeFormat from 'rehype-format'
import rehypeStringify from 'rehype-stringify'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import {unified} from 'unified'

const processor = unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeDocument, {title: '👋🌍'})
  .use(rehypeFormat)
  .use(rehypeStringify)

console.log(String(processor.processSync('# Hello world!')))

Yields:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>👋🌍</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <h1>Hello world!</h1>
  </body>
</html>

processor.run(tree[, file][, done])

Run transformers on a syntax tree.

👉 Note: run freezes the processor if not already frozen.

👉 Note: run performs the run phase, not other phases.

Signatures
  • processor.run(tree, done)
  • processor.run(tree, file, done)
  • Promise<Node> = processor.run(tree, file?)
Parameters
  • tree (Node) — tree to transform and inspect
  • file (Compatible, optional) — file associated with node; any value accepted as x in new VFile(x)
  • done (RunCallback, optional) — callback
Returns

Nothing if done is given (undefined). Otherwise, a promise rejected with a fatal error or resolved with the transformed tree (Promise<Node>).

Example

This example shows how run can be used to transform a tree:

import remarkReferenceLinks from 'remark-reference-links'
import {unified} from 'unified'
import {u} from 'unist-builder'

const tree = u('root', [
  u('paragraph', [
    u('link', {href: 'https://example.com'}, [u('text', 'Example Domain')])
  ])
])

const changedTree = await unified().use(remarkReferenceLinks).run(tree)

console.log(changedTree)

Yields:

{
  type: 'root',
  children: [
    {type: 'paragraph', children: [Array]},
    {type: 'definition', identifier: '1', title: '', url: undefined}
  ]
}

processor.runSync(tree[, file])

Run transformers on a syntax tree.

An error is thrown if asynchronous transforms are configured.

👉 Note: runSync freezes the processor if not already frozen.

👉 Note: runSync performs the run phase, not other phases.

Parameters
  • tree (Node) — tree to transform and inspect
  • file (Compatible, optional) — file associated with node; any value accepted as x in new VFile(x)
Returns

Transformed tree (Node).

processor.stringify(tree[, file])

Compile a syntax tree.

👉 Note: stringify freezes the processor if not already frozen.

👉 Note: stringify performs the stringify phase, not the run phase or other phases.

Parameters
  • tree (Node) — tree to compile
  • file (Compatible, optional) — file associated with node; any value accepted as x in new VFile(x)
Returns

Textual representation of the tree (Uint8Array or string, see note).

👉 Note: unified typically compiles by serializing: most compilers return string (or Uint8Array). Some compilers, such as the one configured with rehype-react, return other values (in this case, a React tree). If you’re using a compiler that doesn’t serialize, expect different result values.

To register custom results in TypeScript, add them to CompileResultMap.

Example

This example shows how stringify can be used to serialize a syntax tree:

import {h} from 'hastscript'
import rehypeStringify from 'rehype-stringify'
import {unified} from 'unified'

const tree = h('h1', 'Hello world!')

const document = unified().use(rehypeStringify).stringify(tree)

console.log(document)

Yields:

<h1>Hello world!</h1>

processor.use(plugin[, options])

Configure the processor to use a plugin, a list of usable values, or a preset.

If the processor is already using a plugin, the previous plugin configuration is changed based on the options that are passed in. In other words, the plugin is not added a second time.

👉 Note: use cannot be called on frozen processors. Call the processor first to create a new unfrozen processor.

Signatures
  • processor.use(preset?)
  • processor.use(list)
  • processor.use(plugin[, ...parameters])
Parameters
  • preset (Preset) — plugins and settings
  • list (PluggableList) — list of usable things
  • plugin (Plugin) — plugin
  • parameters (Array<unknown>) — configuration for plugin, typically a single options object
Returns

Current processor (processor).

Example

There are many ways to pass plugins to .use(). This example gives an overview:

import {unified} from 'unified'

unified()
  // Plugin with options:
  .use(pluginA, {x: true, y: true})
  // Passing the same plugin again merges configuration (to `{x: true, y: false, z: true}`):
  .use(pluginA, {y: false, z: true})
  // Plugins:
  .use([pluginB, pluginC])
  // Two plugins, the second with options:
  .use([pluginD, [pluginE, {}]])
  // Preset with plugins and settings:
  .use({plugins: [pluginF, [pluginG, {}]], settings: {position: false}})
  // Settings only:
  .use({settings: {position: false}})

CompileResultMap

Interface of known results from compilers (TypeScript type).

Normally, compilers result in text (Value of vfile). When you compile to something else, such as a React node (as in, rehype-react), you can augment this interface to include that type.

import type {ReactNode} from 'somewhere'

declare module 'unified' {
  interface CompileResultMap {
    // Register a new result (value is used, key should match it).
    ReactNode: ReactNode
  }
}

export {} // You may not need this, but it makes sure the file is a module.

Use CompileResults to access the values.

Type
interface CompileResultMap {
  // Note: if `Value` from `VFile` is changed, this should too.
  Uint8Array: Uint8Array
  string: string
}

CompileResults

Acceptable results from compilers (TypeScript type).

To register custom results, add them to CompileResultMap.

Type
type CompileResults = CompileResultMap[keyof CompileResultMap]

Compiler

A compiler handles the compiling of a syntax tree to something else (in most cases, text) (TypeScript type).

It is used in the stringify phase and called with a Node and VFile representation of the document to compile. It should return the textual representation of the given tree (typically string).

👉 Note: unified typically compiles by serializing: most compilers return string (or Uint8Array). Some compilers, such as the one configured with rehype-react, return other values (in this case, a React tree). If you’re using a compiler that doesn’t serialize, expect different result values.

To register custom results in TypeScript, add them to CompileResultMap.

Type
type Compiler<
  Tree extends Node = Node,
  Result extends CompileResults = CompileResults
> = (tree: Tree, file: VFile) => Result

Data

Interface of known data that can be supported by all plugins (TypeScript type).

Typically, options can be given to a specific plugin, but sometimes it makes sense to have information shared with several plugins. For example, a list of HTML elements that are self-closing, which is needed during all phases.

To type this, do something like:

declare module 'unified' {
  interface Data {
    htmlVoidElements?: Array<string> | undefined
  }
}

export {} // You may not need this, but it makes sure the file is a module.
Type
interface Data {
  settings?: Settings | undefined
}

See Settings for more info.

Parser

A parser handles the parsing of text to a syntax tree (TypeScript type).

It is used in the parse phase and is called with a string and VFile of the document to parse. It must return the syntax tree representation of the given file (Node).

Type
type Parser<Tree extends Node = Node> = (document: string, file: VFile) => Tree

Pluggable

Union of the different ways to add plugins and settings (TypeScript type).

Type
type Pluggable =
  | Plugin<Array<any>, any, any>
  | PluginTuple<Array<any>, any, any>
  | Preset

See Plugin, PluginTuple, and Preset for more info.

PluggableList

List of plugins and presets (TypeScript type).

Type
type PluggableList = Array<Pluggable>

See Pluggable for more info.

Plugin

Single plugin (TypeScript type).

Plugins configure the processors they are applied on in the following ways:

  • they change the processor, such as the parser, the compiler, or by configuring data
  • they specify how to handle trees and files

In practice, they are functions that can receive options and configure the processor (this).

👉 Note: plugins are called when the processor is frozen, not when they are applied.

Type
type Plugin<
  PluginParameters extends unknown[] = [],
  Input extends Node | string | undefined = Node,
  Output = Input
> = (
  this: Processor,
  ...parameters: PluginParameters
) => Input extends string // Parser.
  ? Output extends Node | undefined
    ? undefined | void
    : never
  : Output extends CompileResults // Compiler.
  ? Input extends Node | undefined
    ? undefined | void
    : never
  : // Inspect/transform.
      | Transformer<
          Input extends Node ? Input : Node,
          Output extends Node ? Output : Node
        >
      | undefined
      | void

See Transformer for more info.

Example

move.js:

/**
 * @typedef Options
 *   Configuration (required).
 * @property {string} extname
 *   File extension to use (must start with `.`).
 */

/** @type {import('unified').Plugin<[Options]>} */
export function move(options) {
  if (!options || !options.extname) {
    throw new Error('Missing `options.extname`')
  }

  return function (_, file) {
    if (file.extname && file.extname !== options.extname) {
      file.extname = options.extname
    }
  }
}

example.md:

# Hello, world!

example.js:

import rehypeStringify from 'rehype-stringify'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import {read, write} from 'to-vfile'
import {unified} from 'unified'
import {reporter} from 'vfile-reporter'
import {move} from './move.js'

const file = await unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(move, {extname: '.html'})
  .use(rehypeStringify)
  .process(await read('example.md'))

console.error(reporter(file))
await write(file) // Written to `example.html`.

Yields:

example.md: no issues found

…and in example.html:

<h1>Hello, world!</h1>

PluginTuple

Tuple of a plugin and its configuration (TypeScript type).

The first item is a plugin, the rest are its parameters.

Type
type PluginTuple<
  TupleParameters extends unknown[] = [],
  Input extends Node | string | undefined = undefined,
  Output = undefined
> = [
  plugin: Plugin<TupleParameters, Input, Output>,
  ...parameters: TupleParameters
]

See Plugin for more info.

Preset

Sharable configuration (TypeScript type).

They can contain plugins and settings.

Fields
  • plugins (PluggableList, optional) — list of plugins and presets
  • settings (Data, optional) — shared settings for parsers and compilers
Example

preset.js:

import remarkCommentConfig from 'remark-comment-config'
import remarkLicense from 'remark-license'
import remarkPresetLintConsistent from 'remark-preset-lint-consistent'
import remarkPresetLintRecommended from 'remark-preset-lint-recommended'
import remarkToc from 'remark-toc'

/** @type {import('unified').Preset} */
const preset = {
  plugins: [
    remarkPresetLintRecommended,
    remarkPresetLintConsistent,
    remarkCommentConfig,
    [remarkToc, {maxDepth: 3, tight: true}],
    remarkLicense
  ]
  settings: {bullet: '*', emphasis: '*', fences: true},
}

export default preset

example.md:

# Hello, world!

_Emphasis_ and **importance**.

## Table of contents

## API

## License

example.js:

import {remark} from 'remark'
import {read, write} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import preset from './preset.js'

const file = await remark()
  .use(preset)
  .process(await read('example.md'))

console.error(reporter(file))
await write(file)

Yields:

example.md: no issues found

example.md now contains:

# Hello, world!

*Emphasis* and **importance**.

## Table of contents

*   [API](https://www.npmjs.com/package/unified#api)
*   [License](https://www.npmjs.com/package/unified#license)

## API

## License

[MIT](https://github.com/unifiedjs/unified/blob/HEAD/license) © [Titus Wormer](https://wooorm.com)

ProcessCallback

Callback called when the process is done (TypeScript type).

Called with either an error or a result.

Parameters
  • error (Error, optional) — fatal error
  • file (VFile, optional) — processed file
Returns

Nothing (undefined).

Example

This example shows how process can be used to process a file with a callback.

import remarkGithub from 'remark-github'
import remarkParse from 'remark-parse'
import remarkStringify from 'remark-stringify'
import {unified} from 'unified'
import {reporter} from 'vfile-reporter'

unified()
  .use(remarkParse)
  .use(remarkGithub)
  .use(remarkStringify)
  .process('@unifiedjs', function (error, file) {
    if (error) throw error
    if (file) {
      console.error(reporter(file))
      console.log(String(file))
    }
  })

Yields:

no issues found
[**@unifiedjs**](https://github.com/unifiedjs)

Processor

Type of a processor (TypeScript type).

RunCallback

Callback called when transformers are done (TypeScript type).

Called with either an error or results.

Parameters
  • error (Error, optional) — fatal error
  • tree (Node, optional) — transformed tree
  • file (VFile, optional) — file
Returns

Nothing (undefined).

Settings

Interface of known extra options, that can be supported by parser and compilers.

This exists so that users can use packages such as remark, which configure both parsers and compilers (in this case remark-parse and remark-stringify), and still provide options for them.

When you make parsers or compilers, that could be packaged up together, you should support this.data('settings') as input and merge it with explicitly passed options. Then, to type it, using remark-stringify as an example, do something like:

declare module 'unified' {
  interface Settings {
    bullet: '*' | '+' | '-'
    // …
  }
}

export {} // You may not need this, but it makes sure the file is a module.
Type
interface Settings {}

TransformCallback

Callback passed to transforms (TypeScript type).

If the signature of a transformer accepts a third argument, the transformer may perform asynchronous operations, and must call it.

Parameters
  • error (Error, optional) — fatal error to stop the process
  • tree (Node, optional) — new, changed, tree
  • file (VFile, optional) — new, changed, file
Returns

Nothing (undefined).

Transformer

Transformers handle syntax trees and files (TypeScript type).

They are functions that are called each time a syntax tree and file are passed through the run phase. When an error occurs in them (either because it’s thrown, returned, rejected, or passed to next), the process stops.

The run phase is handled by trough, see its documentation for the exact semantics of these functions.

👉 Note: you should likely ignore next: don’t accept it. it supports callback-style async work. But promises are likely easier to reason about.

Type
type Transformer<
  Input extends Node = Node,
  Output extends Node = Input
> = (
  tree: Input,
  file: VFile,
  next: TransformCallback<Output>
) =>
  | Promise<Output | undefined>
  | Output
  | Error
  | undefined

Types

This package is fully typed with TypeScript. It exports the additional types CompileResultMap, CompileResults, Compiler, Data, Parser, Pluggable, PluggableList, Plugin, PluginTuple, Preset, ProcessCallback, Processor, RunCallback, Settings, TransformCallback, and Transformer

For TypeScript to work, it is particularly important to type your plugins correctly. We strongly recommend using the Plugin type with its generics and to use the node types for the syntax trees provided by our packages (as in, @types/hast, @types/mdast, @types/nlcst).

/**
 * @typedef {import('hast').Root} HastRoot
 * @typedef {import('mdast').Root} MdastRoot
 */

/**
 * @typedef Options
 *   Configuration (optional).
 * @property {boolean | null | undefined} [someField]
 *   Some option (optional).
 */

// To type options:
/** @type {import('unified').Plugin<[(Options | null | undefined)?]>} */
export function myPluginAcceptingOptions(options) {
  const settings = options || {}
  // `settings` is now `Options`.
}

// To type a plugin that works on a certain tree, without options:
/** @type {import('unified').Plugin<[], MdastRoot>} */
export function myRemarkPlugin() {
  return function (tree, file) {
    // `tree` is `MdastRoot`.
  }
}

// To type a plugin that transforms one tree into another:
/** @type {import('unified').Plugin<[], MdastRoot, HastRoot>} */
export function remarkRehype() {
  return function (tree) {
    // `tree` is `MdastRoot`.
    // Result must be `HastRoot`.
  }
}

// To type a plugin that defines a parser:
/** @type {import('unified').Plugin<[], string, MdastRoot>} */
export function remarkParse(options) {}

// To type a plugin that defines a compiler:
/** @type {import('unified').Plugin<[], HastRoot, string>} */
export function rehypeStringify(options) {}

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, unified@^11, compatible with Node.js 16.

Contribute

See contributing.md in unifiedjs/.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.

For info on how to submit a security report, see our security policy.

Sponsor

Support this effort and give back by sponsoring on OpenCollective!

Vercel

Motif

HashiCorp

American Express

GitBook

Gatsby

Netlify

Coinbase

ThemeIsle

Expo

Boost Note

Markdown Space

Holloway


You?

Acknowledgments

Preliminary work for unified was done in 2014 for retext and inspired by ware. Further incubation happened in remark. The project was finally externalised in 2015 and published as unified. The project was authored by @wooorm.

Although unified since moved its plugin architecture to trough, thanks to @calvinfo, @ianstormtaylor, and others for their work on ware, as it was a huge initial inspiration.

License

MIT © Titus Wormer