mrkeyoor.com_
Sat 08 Aug 20:59 UTC
npmUtilsupdated 08 Aug 2026

remark

remark is a programmable Markdown processor built on unified. The main package gives you a ready-made pipeline with the CommonMark parser and Markdown serializer already attached, so it can parse text into the mdast syntax tree, run plugins that inspect or change that tree, and write Markdown back out. Add-ons provide GitHub Flavored Markdown, frontmatter, linting, tables of contents, HTML conversion, and many other jobs. It is best understood as an AST toolchain for applications and content workflows, not as a one-call Markdown-to-HTML renderer.

Verdict

Choose remark when Markdown is structured data that your program must inspect, enforce, or rewrite. For plain Markdown-to-HTML rendering, use the smaller path its own maintainers recommend and start with micromark.

API stability4/5Version 15 exposes a very small surface: remark is a frozen unified processor with remark-parse and remark-stringify already attached, and the parse, use, run, process, and stringify model is established across the unified ecosystem. Stability is not absolute because the current package is ESM-only and the compatibility policy explicitly allows new majors to drop Node versions after those runtimes become unmaintained.
Docs5/5The README explains what belongs to remark versus unified, mdast, rehype, micromark, and remark-cli, then gives runnable examples for HTML, GFM, frontmatter, linting, CLI formatting, custom plugins, types, and security. It also says when not to use the package and names the direct mdast utilities for manual tree work, which is unusually useful decision guidance rather than just an API catalog.
Maintenance4/5The npm package remains on 15.0.1, published in September 2023, but its tiny wrapper depends on actively maintained unified, remark-parse, and remark-stringify lines rather than containing a large private engine. The repository is not archived, was pushed on July 1, 2026, and reports 11 open issues and pull requests. The long release gap matters, but current repository work and the small stable surface do not look abandoned.
Ecosystem5/5The package recorded 5,168,394 downloads for the fetched week and the repository has 8,973 stars. More important than either number, the README lists over 150 plugins and connects remark to unified, mdast, rehype, micromark, remark-lint, remark-cli, GFM, MDX, and TypeScript types. That breadth makes it easy to compose established tools, though each third-party plugin still needs its own maintenance and security review.

Use it if

  • You need to inspect or rewrite Markdown structurally instead of relying on regular expressions
  • You want a plugin pipeline for linting, formatting, tables of contents, frontmatter, or custom content rules
  • Your input and output are both Markdown and you want parsing plus serialization preconfigured
  • You need CommonMark by default with optional GFM, MDX, or other syntax supplied explicitly by plugins
Skip it if

Setup reality

Installation is only npm install remark, and TypeScript declarations ship in the package, but the useful setup nearly always includes more packages. Version 15 is ESM-only, so use import {remark} from 'remark' from an ES module; a CommonJS require call is the first common failure. The package already includes remark-parse and remark-stringify, which is convenient only when both sides are Markdown. HTML output needs remark-rehype and rehype-stringify, and untrusted HTML needs rehype-sanitize in the correct place before stringification. GitHub Flavored Markdown and frontmatter are not built in; install remark-gfm and remark-frontmatter separately. Tree traversal generally adds unist-util-visit. Linting adds remark-lint rules or presets and vfile-reporter, while the command line is a separate remark-cli package. Plugins can be synchronous or asynchronous, so process() returns a promise and should normally be awaited. parse() is synchronous and only builds the tree; stringify() only serializes a tree; run() or runSync() is the step that applies transforms when you separate stages manually. Plugin order matters because syntax extensions must be registered before parsing and sanitization must happen after converting mdast to hast but before HTML serialization. The README also warns that third-party plugins have independent quality and security profiles. For user-controlled input, cap document size, avoid expensive unchecked plugins, and move processing to a worker if you need a hard timeout. Maintained unified releases follow maintained Node versions; the remark 15 documentation specifically targets Node 16, and a future major can drop an end-of-life runtime.

Patterns

Parse and format Markdownformat-markdown

import {remark} from 'remark'

const file = await remark().process('# Hello, *Mars*!')
console.log(String(file))

process() parses, runs plugins, and serializes. It is asynchronous even when every configured transform is synchronous.

Parse Markdown into mdastparse-syntax-tree

import {remark} from 'remark'

const tree = remark().parse('## Hello *Pluto*!')
console.log(tree.type, tree.children[0].type)

parse() is synchronous and does not run transformer plugins. Call run() afterward if the tree must pass through transforms.

Serialize an mdast treeserialize-syntax-tree

import {remark} from 'remark'

const tree = {
  type: 'root',
  children: [
    {type: 'heading', depth: 2, children: [{type: 'text', value: 'Status'}]}
  ]
}

console.log(remark().stringify(tree))

The object must follow mdast node shapes. TypeScript users can import Root and other node types from mdast.

Choose Markdown serialization styleconfigure-output-style

import {remark} from 'remark'

const output = await remark()
  .use({bullet: '-', emphasis: '_', fences: true})
  .process('* one\n* two\n\n    code')

console.log(String(output))

Parser and compiler settings are passed through use(). Formatting normalizes the whole document, so review large rewrites before applying them in bulk.

Enable GitHub Flavored Markdownenable-gfm

import {remark} from 'remark'
import remarkGfm from 'remark-gfm'

const output = await remark()
  .use(remarkGfm)
  .process('| a | b |\n| - | - |\n| 1 | 2 |')

console.log(String(output))

Install remark-gfm separately. Tables, task lists, autolinks, and strikethrough are not part of remark's default CommonMark syntax.

Recognize YAML frontmatterparse-frontmatter

import {remark} from 'remark'
import remarkFrontmatter from 'remark-frontmatter'

const tree = remark()
  .use(remarkFrontmatter, ['yaml'])
  .parse('---\ntitle: Mars\n---\n\n# Page')

console.log(tree.children[0])

remark-frontmatter creates a yaml node but does not parse its value into an object. Add a YAML parser if you need fields.

Render Markdown to sanitized HTMLrender-safe-html

import {remark} from 'remark'
import remarkRehype from 'remark-rehype'
import rehypeSanitize from 'rehype-sanitize'
import rehypeStringify from 'rehype-stringify'

const file = await remark()
  .use(remarkRehype)
  .use(rehypeSanitize)
  .use(rehypeStringify)
  .process(userMarkdown)

console.log(String(file))

All three add-ons are separate packages. Keep rehype-sanitize between the mdast-to-hast bridge and HTML stringification for untrusted content.

Write a plugin that changes headingswrite-transform-plugin

import {remark} from 'remark'
import {visit} from 'unist-util-visit'

function shiftHeadings() {
  return (tree) => {
    visit(tree, 'heading', (node) => {
      node.depth = Math.min(6, node.depth + 1)
    })
  }
}

const file = await remark().use(shiftHeadings).process('# Title')

A plugin is registered by passing the function, not by calling it. unist-util-visit is an additional dependency.

Collect links from Markdowncollect-links

import {remark} from 'remark'
import {visit} from 'unist-util-visit'

const tree = remark().parse(markdown)
const links = []
visit(tree, 'link', (node) => {
  links.push({url: node.url, title: node.title})
})

console.log(links)

This only visits inline link nodes. Definitions and image links use different mdast node types and need separate visits.

Lint Markdown with maintained presetslint-markdown

import {remark} from 'remark'
import consistent from 'remark-preset-lint-consistent'
import recommended from 'remark-preset-lint-recommended'
import {reporter} from 'vfile-reporter'

const file = await remark()
  .use(consistent)
  .use(recommended)
  .process(markdown)

console.error(reporter(file))

The presets and vfile-reporter are separate installs. Lint messages live on file.messages; process() does not throw merely because warnings exist.

Run an asynchronous pluginrun-async-transform

import {remark} from 'remark'

function loadTitle() {
  return async (tree, file) => {
    const title = await lookupTitle(file.path)
    tree.children.unshift({
      type: 'heading',
      depth: 1,
      children: [{type: 'text', value: title}]
    })
  }
}

const file = await remark().use(loadTitle).process({path: 'post.md', value: body})

Use process() or run() for asynchronous transformers. runSync() throws when any plugin returns a promise.

Format project Markdown from npm scriptsformat-with-cli

npm install --save-dev remark-cli remark-toc

# package.json
# {"scripts": {"format:md": "remark . --output --use remark-toc"}}

npm run format:md

remark does not include the executable; install remark-cli separately. The --output flag rewrites matching files, so run it on a clean worktree first.

Alternatives

PackageRegistryPick it when
micromarknpmYou primarily need standards-focused Markdown-to-HTML conversion and do not need a plugin-driven mdast workflow
markdown-itnpmYou want a direct HTML renderer with a familiar renderer-rule plugin API and broad CommonJS history
markednpmYou want a fast, direct parser and renderer with less AST pipeline ceremony
unifiednpmYou are assembling a mixed Markdown, HTML, or text pipeline and want to choose the parser and compiler yourself