markdown-it, marked, micromark, remark, and showdown are all JavaScript libraries for parsing Markdown and converting it to HTML, but they differ significantly in architecture, performance, extensibility, and use cases. markdown-it offers a flexible token-based pipeline with rich plugin support. marked provides a straightforward, fast parser with renderer customization. micromark is a low-level, streaming parser focused on spec compliance and performance. remark is part of the unified ecosystem, enabling powerful AST-based transformations through plugins. showdown uses a regex-driven approach and is best suited for simple or legacy use cases.
When you need to render user-authored content or convert documentation from Markdown to HTML in a web application, choosing the right parser is critical. The five libraries — markdown-it, marked, micromark, remark, and showdown — all solve this problem but with very different architectures, extension models, and performance characteristics. Let’s break down how they work under the hood and where each shines.
markdown-it uses a token-based pipeline: it parses Markdown into an array of tokens, then renders those tokens to HTML via rules. This makes it highly extensible because plugins can modify tokens at any stage.
// markdown-it: token-based rendering
import MarkdownIt from 'markdown-it';
const md = new MarkdownIt();
md.use(require('markdown-it-highlightjs'));
const html = md.render('# Hello'); // <h1>Hello</h1>
marked follows a direct AST-to-HTML approach. It parses Markdown into a syntax tree, then walks that tree to emit HTML strings. Extensions are added via custom renderer methods.
// marked: direct rendering with custom renderer
import { marked } from 'marked';
marked.use({
renderer: {
heading(text, level) {
return `<h${level} class="heading">${text}</h${level}>`;
}
}
});
const html = marked.parse('# Hello');
micromark is a low-level, streaming parser built on finite-state machines. It doesn’t produce HTML directly — instead, it emits events that other tools (like mdast-util-from-markdown) consume to build syntax trees. It’s designed for composability and spec compliance.
// micromark: event-based parsing (requires additional utilities for HTML)
import { micromark } from 'micromark';
const html = micromark('# Hello'); // <h1>Hello</h1>
// For ASTs, you’d combine with `mdast-util-from-markdown` and `hast-util-to-html`
remark is not a parser itself — it’s a unified ecosystem that uses micromark under the hood. It builds an abstract syntax tree (MDAST), lets you transform it with plugins, then compiles it to HTML (via rehype). This makes it ideal for complex document processing pipelines.
// remark: AST transformation pipeline
import { remark } from 'remark';
import remarkHtml from 'remark-html';
const html = await remark()
.use(remarkHtml)
.process('# Hello'); // <h1>Hello</h1>
showdown uses a regex-heavy, string-replacement engine. It applies a series of regular expressions to convert Markdown constructs to HTML. While simple, this approach can struggle with edge cases and nested structures.
// showdown: string-based conversion
import showdown from 'showdown';
const converter = new showdown.Converter();
const html = converter.makeHtml('# Hello'); // <h1>Hello</h1>
Extending behavior varies dramatically:
markdown-it: Plugins register new rules or modify existing ones in the tokenizer/renderer. You get full control over token generation and HTML output.// markdown-it plugin example
md.core.ruler.push('my_rule', function(state) {
// manipulate state.tokens
});
marked: Extensions override specific renderer methods (e.g., link, image) or add custom tokenizer rules for new syntax.// marked extension
marked.use({
extensions: [{
name: 'emoji',
level: 'inline',
start(src) { return src.indexOf(':'); },
tokenizer(src) { /* ... */ },
renderer(token) { return `<span>${token.text}</span>`; }
}]
});
micromark: Extensions are low-level — they define new character codes, states, and effects. This is powerful but requires deep understanding of the parser’s internals.
remark: Plugins operate on the MDAST (Markdown AST). You can inspect, modify, or replace nodes using standard tree-walking patterns.
// remark plugin
function myPlugin() {
return (tree) => {
// mutate tree
};
}
showdown: Extensions use regex to match patterns and return replacement strings. Limited to inline or block-level hooks.// showdown extension
showdown.extension('myExt', function() {
return [{
type: 'lang',
regex: /:emoji:(\w+)/,
replace: '<span class="emoji">$1</span>'
}];
});
All five libraries support basic XSS protection, but implementation differs:
markdown-it, marked, and showdown escape HTML by default but allow disabling it (risky!).micromark and remark are safe by design — they never interpret raw HTML unless explicitly configured to do so via additional utilities.For performance:
micromark is the fastest due to its optimized state machine.marked and markdown-it are fast for typical use cases.showdown can slow down on large documents due to regex backtracking.remark has overhead from AST construction but enables powerful optimizations in complex workflows.You just need to convert user comments from Markdown to sanitized HTML.
marked or markdown-it// marked for comments
import { marked } from 'marked';
marked.setOptions({ sanitize: false, // deprecated; use DOMPurify instead
gfm: true });
// Pair with DOMPurify for real security
You’re building a docs site that supports [note]This is a tip[/note] blocks.
remark + custom plugin// remark handles custom syntax cleanly
remark()
.use(customNotePlugin)
.use(remarkHtml)
You’re processing thousands of Markdown files per second.
micromarkYou’re replacing an old system that used showdown and must preserve exact output.
showdown if compatibility is non-negotiable.| Package | Architecture | Extensibility Model | Best For | Security Approach |
|---|---|---|---|---|
markdown-it | Token pipeline | Rule-based plugins | Flexible rendering, rich features | Escape by default |
marked | Direct AST → HTML | Renderer/Tokenizer hooks | Simple, fast conversion | Escape by default |
micromark | Streaming FSM | Low-level state machines | Performance-critical parsing | Safe by default |
remark | Unified AST pipeline | MDAST transformers | Complex document processing | Safe by default |
showdown | Regex replacements | String-replace extensions | Legacy compatibility only | Escape by default (weak) |
micromarkremarkmarkdown-it or markedshowdown? → Keep it only if migration isn’t feasible; otherwise, move on.All five are actively maintained as of 2024, but their design philosophies reflect different eras and priorities in the JavaScript ecosystem. Choose based on whether you value raw performance (micromark), transformation power (remark), ease of use (marked), flexibility (markdown-it), or legacy compatibility (showdown).
Choose marked for straightforward, high-performance Markdown-to-HTML conversion with minimal setup. It’s ideal for blogs, comment systems, or any scenario where you need reliable rendering with light customization via renderer overrides. Avoid it if you require complex document transformations or strict CommonMark compliance beyond basic GitHub Flavored Markdown.
Choose markdown-it when you need a balance of performance, flexibility, and rich feature support. Its token-based architecture makes it easy to write plugins that modify parsing or rendering behavior, which is ideal for applications requiring custom syntax (like wikis or forums). It’s well-suited for client-side or server-side rendering where you want fine-grained control without the overhead of a full AST pipeline.
Choose remark when you need to analyze, transform, or generate Markdown documents programmatically. Built on the unified ecosystem, it excels in complex workflows like documentation sites, linters, or content migration tools where you must manipulate the document structure before rendering. Be prepared for a steeper learning curve and more dependencies compared to simpler parsers.
Choose showdown only if you’re maintaining legacy code that already depends on it or if you need basic Markdown support with minimal dependencies. Its regex-based engine is less reliable with edge cases and nested structures, and it lacks the modern extensibility models of other libraries. For new projects, prefer marked or markdown-it instead.
Choose micromark when performance, spec compliance, and composability are top priorities. As a low-level streaming parser, it’s perfect for building custom tooling, static site generators, or high-throughput APIs where you need to process Markdown efficiently. However, you’ll need additional utilities (like mdast-util-from-markdown) if you want to work with syntax trees or perform transformations.
Check out the demo page to see Marked in action ⛹️
Our documentation pages are also rendered using marked 💯
Also read about:
Node.js: Only current and LTS Node.js versions are supported. End of life Node.js versions may become incompatible with Marked at any point in time.
Browser: Baseline Widely Available
CLI:
npm install -g marked
In-browser:
npm install marked
DOMPurify.sanitize(marked.parse(`<img src="x" onerror="alert('not happening')">`));
CLI
# Example with stdin input
$ marked -o hello.html
hello world
^D
$ cat hello.html
<p>hello world</p>
# Print all options
$ marked --help
Browser
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Marked in the browser</title>
</head>
<body>
<div id="content"></div>
<script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.js"></script>
<script>
document.getElementById('content').innerHTML =
marked.parse('# Marked in the browser\n\nRendered by **marked**.');
</script>
</body>
</html>
or import esm module
<script type="module">
import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
document.getElementById('content').innerHTML =
marked.parse('# Marked in the browser\n\nRendered by **marked**.');
</script>
Copyright (c) 2018+, MarkedJS. (MIT License) Copyright (c) 2011-2018, Christopher Jeffrey. (MIT License)