marked vs markdown-it vs remark: Choosing a JavaScript Markdown Parser
By VeloForge Labs · · 2 min read
JavaScript has a rich Markdown ecosystem. Three names show up in nearly every comparison: marked, markdown-it and remark. They all turn Markdown into HTML, but they are designed around different ideas.
marked
marked aims to be fast and simple. You call one function and get an HTML string back:
import { marked } from "marked";
const html = await marked.parse("# Hello **world**");
It supports CommonMark-style Markdown and GitHub-Flavored Markdown, can be extended with custom renderers and tokenizers, and works in Node.js and the browser. It is a good fit when you want a small API and good performance and have no need to manipulate the document structure.
Important caveat: marked does not sanitize its output. Its own documentation tells you to pass the HTML through a sanitizer. See Why Markdown is not safe by default.
markdown-it
markdown-it is built around a pluggable rule system and is known for closely following the CommonMark specification. You configure a parser instance and can add plugins for footnotes, containers, emoji, and many other extensions.
import MarkdownIt from "markdown-it";
const md = new MarkdownIt({ html: false, linkify: true });
const html = md.render("# Hello **world**");
The html: false option makes it escape raw HTML in the input instead of passing it through, which is a useful safety setting. It is still wise to sanitize the final output, because plugins and link handling can introduce edge cases.
remark
remark belongs to the unified ecosystem. Rather than going straight from Markdown to HTML, it parses Markdown into an abstract syntax tree (AST), lets plugins transform the tree, and then a second step turns it into HTML via rehype.
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
const file = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.process("# Hello **world**");
console.log(String(file));
That tree-based design is powerful. You can rewrite links, extract headings for a table of contents, lint prose, or generate a different output format entirely. The cost is more moving parts: several packages to install and a pipeline to configure.
Side-by-side
| marked | markdown-it | remark | |
|---|---|---|---|
| Main idea | Fast and simple | Pluggable, CommonMark-focused | AST pipeline (unified) |
| Output | HTML string | HTML string | Any, via the tree |
| Extension style | Extensions and renderers | Plugins | Plugins on the tree |
| Learning curve | Low | Medium | Higher |
| Built-in sanitizing | No | Option to escape raw HTML | No (add rehype-sanitize) |
Which should you pick?
- You just need HTML from Markdown: marked is the least code.
- You want strict spec behavior and a big plugin catalog: markdown-it.
- You need to analyze or transform the document: remark.
Whichever you choose, the pipeline for user-written content is the same: parse, sanitize with an allow-list, then render. For sanitizers, see sanitize-html vs DOMPurify.
Or call an endpoint
If your services are not all JavaScript, a shared HTTP endpoint avoids having each one pick a parser. TexToolsAI uses marked and sanitize-html behind a single POST /api/convert. The docs show examples in cURL, Node.js, Python and Go.