markdown-it, marked, and showdown are libraries that convert Markdown text into HTML, enabling rich text rendering in web applications. turndown and node-html-markdown perform the reverse operation, converting HTML content back into Markdown format. These tools are essential for content management systems, comment sections, documentation sites, and any application requiring flexible text formatting or content migration.
When building content-driven applications, you often need to transform text between Markdown and HTML. The ecosystem offers several mature tools, but they serve different directions and architectural needs. markdown-it, marked, and showdown convert Markdown to HTML. turndown and node-html-markdown convert HTML back to Markdown. Let's explore how they differ in practice.
The most critical distinction is the direction of conversion. Using the wrong tool for the direction will break your workflow.
markdown-it converts Markdown to HTML with a focus on plugins.
import MarkdownIt from 'markdown-it';
const md = new MarkdownIt();
const result = md.render('# Hello World');
// Output: <h1>Hello World</h1>
marked converts Markdown to HTML with a focus on speed.
import { marked } from 'marked';
const result = marked('# Hello World');
// Output: <h1>Hello World</h1>
showdown converts Markdown to HTML with a focus on extensions.
import { Converter } from 'showdown';
const converter = new Converter();
const result = converter.makeHtml('# Hello World');
// Output: <h1>Hello World</h1>
turndown converts HTML to Markdown, running in Node or browser.
import TurndownService from 'turndown';
const turndownService = new TurndownService();
const result = turndownService.turndown('<h1>Hello World</h1>');
// Output: # Hello World
node-html-markdown converts HTML to Markdown, optimized for Node.js.
import { NodeHtmlMarkdown } from 'node-html-markdown';
const result = NodeHtmlMarkdown.translate('<h1>Hello World</h1>');
// Output: # Hello World
How you customize the output varies significantly between these libraries. Some use a plugin system, while others rely on configuration objects.
markdown-it uses a token-based plugin system.
You can insert rules into the parsing chain to modify how tokens are generated.
import MarkdownIt from 'markdown-it';
const md = new MarkdownIt();
md.use(require('markdown-it-emoji'));
const result = md.render(':smile:');
marked uses a renderer object or async extensions.
You override methods on the renderer to change HTML output for specific elements.
import { marked } from 'marked';
const renderer = new marked.Renderer();
renderer.heading = (text, level) => `<h${level} class="custom">${text}</h${level}>`;
const result = marked('# Hello', { renderer });
showdown uses a flavor system and custom extensions.
You can enable predefined flavors like 'github' or write custom language extensions.
import { Converter } from 'showdown';
const converter = new Converter({ flavor: 'github' });
const result = converter.makeHtml('# Hello');
turndown uses a rule-based system for HTML elements.
You add rules to handle specific tags or classes during the HTML to Markdown conversion.
import TurndownService from 'turndown';
const turndownService = new TurndownService();
turndownService.addRule('strikethrough', {
filter: ['del', 's', 'strike'],
replacement: (content) => `~~${content}~~`
});
node-html-markdown uses configuration options for specific tags.
It has fewer extension points but allows configuring how specific elements like images or links are handled.
import { NodeHtmlMarkdown } from 'node-html-markdown';
const result = NodeHtmlMarkdown.translate('<img src="a.jpg">', {
ignore: ['img'] // Example config to ignore specific tags
});
Rendering user-generated HTML carries security risks like XSS. Some libraries include sanitization, while others expect you to handle it separately.
markdown-it does not sanitize HTML by default.
You must pair it with a sanitizer like sanitize-html to prevent script injection.
import sanitizeHtml from 'sanitize-html';
import MarkdownIt from 'markdown-it';
const md = new MarkdownIt();
const raw = md.render(userInput);
const clean = sanitizeHtml(raw);
marked has a built-in sanitizer option but it is deprecated.
The official recommendation is to use a dedicated library like dompurify for security.
import { marked } from 'marked';
import DOMPurify from 'dompurify';
const raw = marked(userInput);
const clean = DOMPurify.sanitize(raw);
showdown has a built-in sanitize option.
It can strip dangerous tags directly during the conversion process without extra dependencies.
import { Converter } from 'showdown';
const converter = new Converter({ sanitize: true });
const result = converter.makeHtml(userInput);
turndown focuses on conversion, not sanitization.
Since it outputs Markdown, the risk is lower, but you should still validate input HTML before conversion.
import TurndownService from 'turndown';
const turndownService = new TurndownService();
// Validate HTML string before passing to turndown
const result = turndownService.turndown(unsafeHtml);
node-html-markdown does not include sanitization features.
It assumes the input HTML is trusted or has been sanitized prior to conversion in your Node.js pipeline.
import { NodeHtmlMarkdown } from 'node-html-markdown';
// Ensure htmlInput is safe before translating
const result = NodeHtmlMarkdown.translate(htmlInput);
Performance matters when processing large documents or running on edge devices. Environment support (Browser vs Node) also dictates your choice.
markdown-it is highly optimized for Node and browser.
It parses into tokens first, which adds a slight overhead but allows for complex transformations.
// markdown-it works in both environments
const md = new MarkdownIt();
console.log(md.render('# Test'));
marked is known for being very fast.
It compiles Markdown directly to HTML strings, making it efficient for high-volume rendering.
// marked is lightweight and fast
console.log(marked('# Test'));
showdown is slightly heavier due to its extension system.
It works well in browsers but may be slower than marked on large documents.
// showdown is versatile but can be slower
const converter = new Converter();
console.log(converter.makeHtml('# Test'));
turndown relies on DOM parsing in the browser.
In Node.js, it requires a DOM implementation like jsdom to function correctly.
// turndown needs a DOM in Node.js
// const { JSDOM } = require('jsdom');
// global.window = new JSDOM('').window;
const turndownService = new TurndownService();
node-html-markdown is built specifically for Node.js.
It does not rely on a virtual DOM, making it faster and lighter for server-side scripts.
// node-html-markdown is Node-only
const result = NodeHtmlMarkdown.translate('<h1>Test</h1>');
Despite their differences, these libraries share common goals and patterns.
// All handle basic syntax similarly
// markdown-it
md.render('- item');
// marked
marked('- item');
// showdown
converter.makeHtml('- item');
// All allow some config
// markdown-it: md.set({ html: true })
// marked: marked.setOptions({ breaks: true })
// showdown: new Converter({ tables: true })
// Ecosystem examples
// markdown-it: markdown-it-container
// marked: marked-highlight
// turndown: turndown-plugin-gfm
| Feature | markdown-it | marked | showdown | turndown | node-html-markdown |
|---|---|---|---|---|---|
| Direction | MD โ HTML | MD โ HTML | MD โ HTML | HTML โ MD | HTML โ MD |
| Extensibility | Plugin System | Renderer Override | Extensions | Rule System | Config Options |
| Sanitization | External Required | External Recommended | Built-in Option | None | None |
| Environment | Node & Browser | Node & Browser | Node & Browser | Node & Browser | Node Only |
| Primary Use | Complex Docs | Fast Rendering | Legacy/Extensions | Content Editors | Server Scripts |
markdown-it is the choice for complex documentation systems. Its plugin architecture allows you to build custom content experiences, but it requires more setup to secure properly.
marked is the workhorse for general-purpose rendering. If you need to display comments or blog posts quickly and safely, pair this with a sanitizer and move on.
showdown remains useful for legacy systems or specific extension needs. For new greenfield projects, marked or markdown-it usually offer better long-term support.
turndown is the standard for HTML to Markdown conversion. If you are building a rich-text editor that exports to Markdown, this is the most reliable option across environments.
node-html-markdown is a niche tool for server-side Node.js scripts. Use it when you need to process HTML files in a build pipeline without the overhead of a DOM implementation.
Final Thought: Direction matters most. Pick markdown-it or marked for displaying content. Pick turndown for editing or migrating content. Always sanitize HTML output when rendering user input to keep your application secure.
Choose marked if you prioritize speed and simplicity without sacrificing essential features. It is a solid default for rendering user-generated content where performance matters. Recent versions support async rendering, which helps when integrating with asynchronous highlighters or custom renderers.
Choose markdown-it if you need a highly extensible parser with a robust plugin ecosystem. It is ideal for projects requiring strict CommonMark compliance or custom syntax rules. Its architecture allows deep customization of the token stream, making it suitable for complex documentation platforms.
Choose turndown if you need to convert HTML to Markdown in the browser or Node.js with high fidelity. It is the standard choice for content editors that allow switching between rich text and Markdown sources. It handles complex DOM structures well and supports custom rules.
Choose showdown if you are maintaining a legacy project that already depends on it or need specific older extensions. For new projects, consider modern alternatives, as its development pace is slower compared to marked or markdown-it. It remains viable for simple client-side rendering tasks.
Choose node-html-markdown if you are working exclusively in a Node.js environment and need a lightweight HTML to Markdown converter. It is optimized for server-side processing where browser DOM APIs are unavailable. It is less flexible than turndown but requires fewer dependencies.
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="https://raw.githubusercontent.com/markedjs/marked/HEAD/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)