markdown-it vs marked: Render Markdown in JavaScript

September 11, 2026 · 9 min read

markdown-it and marked: Render Markdown in JavaScript

markdown-it and marked are the two most used JavaScript Markdown parsers. Both take a Markdown string and return HTML in one call: md.render(text) in version 15 of the first and marked.parse(text) in marked 18. This tutorial builds a live previewer with each, adds DOMPurify so pasted HTML can't run scripts, and wires up the three plugins people ask for most.

What Is markdown-it?

markdown-it is a CommonMark-compliant Markdown parser for JavaScript, published on npm since 2014 and currently at version 15. It follows the CommonMark spec, adds GFM tables and strikethrough in its default preset, and exposes a rule system so you can add or replace syntax. The project describes itself as safe by default because HTML in the source is disabled unless you opt in.

It's also the parser behind VS Code's Markdown preview. The VS Code extension guide documents how extensions contribute plugins to that preview, which is why "markdown it Microsoft" shows up as a related search.

marked is the older alternative, first released in 2011 and now at version 18. It's smaller, runs GFM by default, and is the one you'll find in many quick browser demos. The two libraries cover the same ground, so the choice usually comes down to the plugin you need and whether you want token-level control over the output.

Build a Live Previewer with markdown-it

The whole point of a javascript markdown editor is the textarea-to-HTML loop, and it takes about 25 lines. Whether you call it a js markdown editor or a markdown editor javascript widget, this loop is the core. Install the parser and a sanitizer.

npm install markdown-it dompurify

Then add this to a page with a <textarea id="src"> and a <div id="out">.

import MarkdownIt from "markdown-it";
import DOMPurify from "dompurify";

const md = new MarkdownIt({
  html: false,       // ignore raw HTML in the source (default)
  linkify: true,     // turn bare URLs into links
  typographer: true, // smart quotes and dashes
  breaks: false      // keep CommonMark line-break rules
});

const src = document.getElementById("src");
const out = document.getElementById("out");

function render() {
  const html = md.render(src.value);
  out.innerHTML = DOMPurify.sanitize(html);
}

src.addEventListener("input", render);
render();

Type into the textarea and the preview updates on every keystroke. The four options in the constructor are the ones you'll actually touch. html controls whether <b> in the source passes through, linkify autolinks URLs, typographer swaps straight quotes for curly ones, and breaks decides whether a single newline becomes <br>.

One note from the v15 migration guide: types are now bundled, so drop @types/markdown-it, and linkify no longer treats bare example.com as a link unless you enable fuzzy links. The browser bundle paths also changed, so check the package's dist folder before hard-coding a CDN URL.

Build the Same Previewer with marked

Swap the import and the render call. Everything else stays the same, which is the fastest way to compare the two APIs.

import { marked } from "marked";
import DOMPurify from "dompurify";

marked.use({ gfm: true, breaks: false });

const src = document.getElementById("src");
const out = document.getElementById("out");

function render() {
  const html = marked.parse(src.value);
  out.innerHTML = DOMPurify.sanitize(html);
}

src.addEventListener("input", render);
render();

marked's gfm option is true by default, which turns on tables, strikethrough, task lists, and autolinks. breaks is false by default and does the same job as the option of the same name in the first example.

The API shape is the difference. marked is a function you configure globally with marked.use(), while the other library gives you an instance you construct, so two configurations can coexist on one page. We prefer the instance model for anything beyond a demo, because a comment box and a document editor rarely want the same rules.

Why Do You Need DOMPurify with a Markdown Parser?

Because Markdown output is HTML, and HTML can carry scripts. marked's README is explicit: it does not sanitize the output, and it recommends running DOMPurify on the result. Its own example is a Markdown string containing an <img> with an onerror handler, which would execute the moment you assign it to innerHTML.

markdown-it is safer out of the box because html: false escapes raw tags. But the moment you set html: true to allow <details> or <kbd> in user content, you're back to needing a sanitizer. Links are a second hole: [click](javascript:alert(1)) is valid Markdown. The built-in link validator in markdown-it blocks the javascript: scheme, while marked leaves that to your sanitizer.

The rule we follow: sanitize whenever the Markdown comes from someone other than you. One line, DOMPurify.sanitize(html), and both previewers above are safe to feed with pasted content.

Add Heading Anchors, Math, and Syntax Highlighting

Three plugins account for most of the "markdown it" plugin searches. All three go through md.use().

Heading IDs with markdown-it-anchor (version 10), so a table of contents can link to #installation:

import anchor from "markdown-it-anchor";

md.use(anchor, { permalink: anchor.permalink.headerLink() });

LaTeX math with @vscode/markdown-it-katex, the same plugin VS Code ships. Wrap inline math in single dollar signs and display math in double:

import katex from "@vscode/markdown-it-katex";

md.use(katex);
// Renders $E = mc^2$ inline and $$...$$ as a block

Load the KaTeX stylesheet on the page or the symbols stack up unstyled. Our Markdown equation guide covers the syntax that plugin expects.

Syntax highlighting doesn't need a plugin. The constructor takes a highlight option that receives each fenced block's code and language.

import hljs from "highlight.js";

const md = new MarkdownIt({
  highlight(str, lang) {
    if (lang && hljs.getLanguage(lang)) {
      return hljs.highlight(str, { language: lang }).value;
    }
    return ""; // fall back to plain escaped code
  }
});

Returning an empty string tells the parser to escape the code itself, which is the documented fallback.

For marked, the equivalents are marked-gfm-heading-id, marked-katex-extension, and marked-highlight, each added with marked.use(). Heading IDs were built in until marked 8 removed the headerIds option, so older tutorials will mislead you here.

Compare the output in the editor

Our editor renders GitHub Flavored Markdown with tables, task lists, and strikethrough turned on, but not footnotes. Paste the sample below into it, then run the same text through both previewers to see what each handles by default.

Parser test

Feature markdown-it default marked default
Tables yes yes
Strikethrough yes yes
Task lists plugin yes
Footnotes[^1] plugin plugin
  • Open task
  • Done task

Inline code and a link.

[^1]: Footnotes need markdown-it-footnote or marked-footnote.

62 words359 characters15 lines
Markdown

What you'll find: the markdown-it default preset renders the table and strikethrough but leaves - [ ] as a literal bullet until you add markdown-it-task-lists. marked renders the task list because GFM is on. Neither handles footnotes without a plugin. If you only need a rendered result and no code, the Markdown to HTML converter does this without any JavaScript on your side.

Token Streams vs Syntax Trees: When markdown-it Isn't Enough

markdown-it parses to a flat token stream rather than a tree. The architecture docs describe tokens as a simple array where inline containers hold nested tokens. That's fast and easy to post-process for things like "add a class to every table", and it's why the plugin ecosystem is large.

The limitation shows up when you need to transform the document structurally: MDX, custom directives that wrap several blocks, or converting Markdown to something other than HTML. For that, remark and the unified ecosystem give you a real syntax tree (mdast) with visitor utilities. It's heavier and slower to set up, so we reach for it only when a token stream gets awkward.

For a search like "markdown js" or "js markdown", the practical split is: markdown-it or marked for rendering to HTML, remark when you're building tooling. The old markdown-js package that some tutorials still reference hasn't kept pace and isn't worth starting with today.

Common markdown-it and marked Mistakes

Assigning unsanitized HTML to innerHTML. Works fine until someone pastes <img src=x onerror=...>. Wrap the output in DOMPurify.sanitize() every time the source isn't yours.

Expecting GitHub behaviour from the defaults. Neither library adds heading IDs, task lists (markdown-it), or footnotes by default. If your README looks different in the browser than on GitHub, a missing plugin is usually why. Our Markdown to HTML conversion guide lists the non-programmatic routes if you just need the file converted.

Re-creating the parser on every keystroke. new MarkdownIt() builds a rule chain, so construct it once and call render() repeatedly. The same markdown javascript performance advice applies to marked. Calling marked.use() inside the input handler stacks extensions on every event.

markdown-it FAQ

A working markdown-it previewer is a constructor, a render call, and a sanitize step, and the marked version is the same three lines with a different import. Add anchors, KaTeX, and highlighting through plugins as the project needs them, and move to remark only when a token stream stops being enough. To see what correct GFM output looks like before you write any code, paste your sample into the editor and compare.