markdown-it vs marked vs node-html-markdown vs showdown vs turndown
Markdown and HTML Conversion Libraries for JavaScript
markdown-itmarkednode-html-markdownshowdownturndownSimilar Packages:

Markdown and HTML Conversion Libraries for JavaScript

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
markdown-it021,167768 kB59a month agoMIT
marked036,673444 kB1715 days agoMIT
node-html-markdown0256113 kB124 months agoMIT
showdown014,839801 kB236-MIT
turndown010,939192 kB1365 months agoMIT

Markdown and HTML Conversion Libraries: Architecture and Use Cases

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.

๐Ÿ”„ Conversion Direction: Markdown to HTML vs HTML to Markdown

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

๐Ÿงฉ Extensibility: Plugins vs Custom Renderers

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
});

๐Ÿ›ก๏ธ Security and Sanitization

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 and Environment

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>');

๐ŸŒฑ Similarities: Shared Ground

Despite their differences, these libraries share common goals and patterns.

1. ๐Ÿ“ Standard Markdown Support

  • All support core CommonMark or GitHub Flavored Markdown.
  • Basic syntax like headers, lists, and links works out of the box.
// All handle basic syntax similarly
// markdown-it
md.render('- item'); 
// marked
marked('- item');
// showdown
converter.makeHtml('- item');

2. ๐Ÿ”Œ Customization Capabilities

  • Each allows some form of output modification.
  • Developers can tweak how specific elements render.
// All allow some config
// markdown-it: md.set({ html: true })
// marked: marked.setOptions({ breaks: true })
// showdown: new Converter({ tables: true })

3. ๐ŸŒ Open Source Ecosystem

  • All are maintained by community contributors.
  • Plugins and extensions exist for all major use cases.
// Ecosystem examples
// markdown-it: markdown-it-container
// marked: marked-highlight
// turndown: turndown-plugin-gfm

๐Ÿ“Š Summary: Key Differences

Featuremarkdown-itmarkedshowdownturndownnode-html-markdown
DirectionMD โ†’ HTMLMD โ†’ HTMLMD โ†’ HTMLHTML โ†’ MDHTML โ†’ MD
ExtensibilityPlugin SystemRenderer OverrideExtensionsRule SystemConfig Options
SanitizationExternal RequiredExternal RecommendedBuilt-in OptionNoneNone
EnvironmentNode & BrowserNode & BrowserNode & BrowserNode & BrowserNode Only
Primary UseComplex DocsFast RenderingLegacy/ExtensionsContent EditorsServer Scripts

๐Ÿ’ก The Big Picture

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.

How to Choose: markdown-it vs marked vs node-html-markdown vs showdown vs turndown

  • markdown-it:

    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.

  • marked:

    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.

  • node-html-markdown:

    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.

  • showdown:

    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.

  • turndown:

    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.

README for markdown-it

markdown-it

CI NPM version Coverage Status Gitter

Markdown parser done right. Fast and easy to extend.

Live demo

  • Follows the CommonMark spec + adds syntax extensions & sugar (URL autolinking, typographer).
  • Configurable syntax! You can add new rules and even replace existing ones.
  • High speed.
  • Safe by default.
  • Community-written plugins and other packages on npm.

Table of content

Install

node.js:

npm install markdown-it

browser (CDN):

Usage examples

See also:

Simple

// node.js
// can use `require('markdown-it')` for CJS
import markdownit from 'markdown-it'
const md = markdownit()
const result = md.render('# markdown-it rulezz!');

// browser with UMD build, added to "window" on script load
// Note, there is no dash in "markdownit".
const md = window.markdownit();
const result = md.render('# markdown-it rulezz!');

Single line rendering, without paragraph wrap:

import markdownit from 'markdown-it'
const md = markdownit()
const result = md.renderInline('__markdown-it__ rulezz!');

Init with presets and options

(*) presets define combinations of active rules and options. Can be "commonmark", "zero" or "default" (if skipped). See API docs for more details.

import markdownit from 'markdown-it'

// commonmark mode
const md = markdownit('commonmark')

// default mode
const md = markdownit()

// enable everything
const md = markdownit({
  html: true,
  linkify: true,
  typographer: true
})

// full options list (defaults)
const md = markdownit({
  // Enable HTML tags in source
  html:         false,

  // Use '/' to close single tags (<br />).
  // This is only for full CommonMark compatibility.
  xhtmlOut:     false,

  // Convert '\n' in paragraphs into <br>
  breaks:       false,

  // CSS language prefix for fenced blocks. Can be
  // useful for external highlighters.
  langPrefix:   'language-',

  // Autoconvert URL-like text to links
  linkify:      false,

  // Enable some language-neutral replacement + quotes beautification
  // For the full list of replacements, see https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.mjs
  typographer:  false,

  // Double + single quotes replacement pairs, when typographer enabled,
  // and smartquotes on. Could be either a String or an Array.
  //
  // For example, you can use 'ยซยปโ€žโ€œ' for Russian, 'โ€žโ€œโ€šโ€˜' for German,
  // and ['ยซ\xA0', '\xA0ยป', 'โ€น\xA0', '\xA0โ€บ'] for French (including nbsp).
  quotes: 'โ€œโ€โ€˜โ€™',

  // Highlighter function. Should return escaped HTML,
  // or '' if the source string is not changed and should be escaped externally.
  // If result starts with <pre... internal wrapper is skipped.
  highlight: function (/*str, lang*/) { return ''; }
});

Plugins load

import markdownit from 'markdown-it'

const md = markdownit
  .use(plugin1)
  .use(plugin2, opts, ...)
  .use(plugin3);

Syntax highlighting

Apply syntax highlighting to fenced code blocks with the highlight option:

import markdownit from 'markdown-it'
import hljs from 'highlight.js' // https://highlightjs.org

// Actual default values
const md = markdownit({
  highlight: function (str, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return hljs.highlight(str, { language: lang }).value;
      } catch (__) {}
    }

    return ''; // use external default escaping
  }
});

Or with full wrapper override (if you need assign class to <pre> or <code>):

import markdownit from 'markdown-it'
import hljs from 'highlight.js' // https://highlightjs.org

// Actual default values
const md = markdownit({
  highlight: function (str, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return '<pre><code class="hljs">' +
               hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
               '</code></pre>';
      } catch (__) {}
    }

    return '<pre><code class="hljs">' + md.utils.escapeHtml(str) + '</code></pre>';
  }
});

Linkify

linkify: true uses linkify-it. To configure linkify-it, access the linkify instance through md.linkify:

md.linkify.set({ fuzzyEmail: false });  // disables converting email to link

API

API documentation

If you are going to write plugins, please take a look at Development info.

Syntax extensions

Embedded (enabled by default):

Via plugins:

Manage rules

By default all rules are enabled, but can be restricted by options. On plugin load all its rules are enabled automatically.

import markdownit from 'markdown-it'

// Activate/deactivate rules, with currying
const md = markdownit()
  .disable(['link', 'image'])
  .enable(['link'])
  .enable('image');

// Enable everything
const md = markdownit({
  html: true,
  linkify: true,
  typographer: true,
});

You can find all rules in sources:

Benchmark

Here is the result of readme parse at MB Pro Retina 2013 (2.4 GHz):

npm run benchmark-deps
benchmark/benchmark.mjs readme

Selected samples: (1 of 28)
 > README

Sample: README.md (7774 bytes)
 > commonmark-reference x 1,222 ops/sec ยฑ0.96% (97 runs sampled)
 > current x 743 ops/sec ยฑ0.84% (97 runs sampled)
 > current-commonmark x 1,568 ops/sec ยฑ0.84% (98 runs sampled)
 > marked x 1,587 ops/sec ยฑ4.31% (93 runs sampled)

Note. CommonMark version runs with simplified link normalizers for more "honest" compare. Difference is โ‰ˆ1.5ร—.

As you can see, markdown-it doesn't pay with speed for its flexibility. Slowdown of "full" version caused by additional features not available in other implementations.

markdown-it for enterprise

Available as part of the Tidelift Subscription.

The maintainers of markdown-it and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. Learn more.

Authors

markdown-it is the result of the decision of the authors who contributed to 99% of the Remarkable code to move to a project with the same authorship but new leadership (Vitaly and Alex). It's not a fork.

References / Thanks

Big thanks to John MacFarlane for his work on the CommonMark spec and reference implementations. His work saved us a lot of time during this project's development.

Related Links:

Ports