design

Markdown to HTML: Which Converter for Which Job (and the Cheatsheet)

The same Markdown file produces different HTML depending on the tool. Which converter fits which job — browser, pandoc, Python, JS, VS Code — plus the cheatsheet.

Published 2026-09-03 · 8 min read

Affiliate disclosure

Some links below are affiliate links. I may earn a commission from qualifying purchases at no extra cost to you. Recommendations come from published specifications and independent reviews, not hands-on testing.

Markdown to HTML conversion on a developer's laptop screen — original hero illustration
AI illustration

Paste a Markdown file into two different converters and you can get two different documents. Not different formatting, but different structure. A table that becomes a real <table> in one tool comes out as a paragraph full of pipe characters in another, and neither tool is broken.

That is the part most "markdown to html converter" guides skip, and it is the part that decides which tool you should actually use.

TL;DR — For a one-off paste, use a browser converter such as our Markdown to HTML tool. For batch jobs and docs pipelines, use pandoc. For converting inside your own app, use markdown-it (JavaScript) or the markdown package (Python). If the Markdown came from a user, sanitize the HTML afterwards with DOMPurify no matter which converter produced it.

Why does the same Markdown produce different HTML?

There is no single Markdown. The original 2004 release by John Gruber and Aaron Swartz was a Perl script and a prose description, not a specification, and implementations drifted apart for a decade.

CommonMark exists to end that drift. It is a precise spec, currently version 0.31.2 and released January 2024, started in 2014 by John MacFarlane with engineers from GitHub, Reddit, Stack Overflow and Discourse. GitHub Flavored Markdown is a strict superset of CommonMark that adds tables, strikethrough, task lists and autolinks.

The gap between those two is where most surprises live. Take this input:

| Fruit | Qty |
|---|---|
| Apple | 3 |
| Pear  | 5 |

Converted with pandoc -f commonmark -t html, the pipes are literal text:

<p>| Fruit | Qty | |---|---| | Apple | 3 | | Pear | 5 |</p>

Converted with pandoc -f gfm -t html, you get the table you expected:

<table><thead><tr><th>Fruit</th><th>Qty</th></tr></thead>
<tbody><tr><td>Apple</td><td>3</td></tr><tr><td>Pear</td><td>5</td></tr></tbody></table>

Same file, same program, one flag apart. The same split reproduces in an unrelated codebase: new MarkdownIt('commonmark') renders ~~strike~~ as literal text, while new MarkdownIt() renders <s>strike</s>. So this is not a pandoc quirk. It is the CommonMark/GFM boundary itself.

Practical consequence: when output looks wrong, the first question is not "is this tool buggy" but "which flavor is this tool parsing".

How can you tell which flavor a converter uses?

Most online converters never state which parser sits behind them, so test it rather than trust it. Paste this four-line probe into any tool and read the output:

| a | b |
|---|---|
| 1 | 2 |

~~strike~~ and a task: - [x] done

If the table renders as a table and strike comes out struck through, you are on a GFM-capable parser. If either one comes back as literal punctuation, the tool is running closer to plain CommonMark, and any document you convert with it will lose those constructs silently. Silently is the operative word: nothing errors, the output just quietly loses structure.

There is a third family worth knowing about, because it explains output that matches neither result. PHP Markdown Extra, maintained by Michel Fortin since 2003, adds footnotes, definition lists, abbreviations and attribute IDs such as {#id}. It sits behind much of the PHP ecosystem and older CMSs, so a file that renders one way in a WordPress-era tool and another way on GitHub is usually crossing that boundary rather than hitting a bug.

Which converter should you use for which job?

MethodSetup costBest forBad fit for
Browser toolZeroOne-off paste, no install, works on a phoneAutomation; confidential text you haven't checked
pandoc CLIMedium (~279MB)Batch conversion, docs pipelines, many output formatsA single quick paste; embedding in a web request
Python (markdown)Low (pip)Django/Flask backends, static site generatorsClient-side rendering
JS (markdown-it, marked)Low (npm)Web apps, live preview, Node backendsOne-off internal scripts
VS Code extensionLow, one-timeDevs already writing docs in the editorNon-technical users, automation

When is pandoc the right tool?

Pandoc is the right tool once you are converting more than a handful of files, or converting to several formats from one source. Version 3.11 is current.

brew install pandoc                       # macOS
winget install --exact --id JohnMacFarlane.Pandoc   # Windows

On Debian and Ubuntu, apt install pandoc works but ships well behind. Ubuntu 26.04 carries 3.7.0.2 and some Debian branches are older still. If you need current behaviour, take the .deb from the releases page instead.

pandoc input.md -o output.html      # HTML fragment
pandoc -s input.md -o output.html   # standalone document with <html> and <head>

The -f flag is the flavor selector that the section above is really about: -f commonmark, -f gfm, -f markdown_strict, or pandoc's own extended dialect by default.

One caveat worth knowing before you diff the output against another tool: pandoc does not emit a plain <pre><code> for fenced code blocks. It wraps them in <div class="sourceCode"> with syntax-highlighting spans. That is a deliberate choice, not a bug, but it means pandoc's HTML is not byte-comparable with the libraries below.

Skip pandoc if you are converting one pasted paragraph. A 279MB install to convert a changelog once is the wrong trade, and pandoc is a native binary — shelling out to it inside a web request path to process user input is an operational and security smell.

How do you convert Markdown inside your own app?

Python. The markdown package is the common answer. The current release is 3.10.3, but note it requires Python 3.10 or newer — on Python 3.9, pip install Markdown quietly resolves to 3.9 instead of failing.

import markdown
html = markdown.markdown(text)                                        # bare
html = markdown.markdown(text, extensions=['tables', 'fenced_code'])  # tables + code blocks

That second line matters more than it looks. Without fenced_code, a triple-backtick block does not become <pre><code> at all — it collapses into a single inline <code> run inside a paragraph. If you have ever wondered why your code samples came out mangled, this is usually why. markdown-it-py (4.2.0) is the alternative when you want strict CommonMark compliance or a plugin architecture.

JavaScript. Two libraries dominate, and the difference between them is a security posture, not a feature list.

npm install markdown-it   # or: npm install marked
import MarkdownIt from 'markdown-it';
const html = new MarkdownIt().render('# markdown-it rulezz!');

marked (18.0.11) is fast and permissive: raw HTML passes through untouched by default. markdown-it (15.0.1) is CommonMark-compliant and escapes raw HTML by default, and html: false is the literal default in its own preset source. For a web app rendering Markdown you did not write, that default is the one you want.

Is it safe to convert Markdown you did not write?

Markdown permits inline HTML by design, so a converter is not a sanitizer and mostly does not claim to be. Give both libraries the string Hello <script>alert(1)</script> with default options and they disagree: marked emits the script tag intact, markdown-it escapes it to harmless text.

marked used to ship sanitize and sanitizer options. Both were deprecated in v0.7.0 and removed in v8.0.0; the library's documentation now points at DOMPurify (3.4.14) instead:

import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(marked.parse(input));

Three things follow. Sanitize whenever the Markdown is user-submitted — comments, wikis, issue trackers, anything you did not author. Do it regardless of library, because markdown-it's safe default is one html: true away from being unsafe and Python's markdown package does not sanitize either. And note that DOMPurify is DOM-based: in the browser it works natively, but server-side in Node it needs jsdom.

If you only need to display Markdown as visible text rather than render it, escaping the angle brackets with an HTML entity encoder sidesteps the question entirely.

Markdown to HTML cheatsheet

MarkdownHTMLSpec
# H1###### H6<h1><h6>CommonMark
**bold**<strong>CommonMark
*italic*<em>CommonMark
`code`<code>CommonMark
[text](url)<a href="url">CommonMark
![alt](url)<img src="url" alt="alt">CommonMark
- item<ul><li>CommonMark
1. item<ol><li>CommonMark
> quote<blockquote><p>CommonMark
fenced block<pre><code class="language-…">CommonMark
---<hr>CommonMark
~~text~~<del> (MDN)GFM only
| a | b |<table><thead>…<tbody>GFM only
- [x] done<li><input type="checkbox" checked disabled>GFM only

The three GFM-only rows are the ones that break when you move a file from GitHub to a stricter parser.

How do you export to HTML from VS Code or a browser?

VS Code's built-in preview (Cmd+Shift+V, or Ctrl+Shift+V) renders Markdown but does not export it. The documentation describes no built-in save-as-HTML command. Add Markdown All in One (yzhang.markdown-all-in-one) and its "Print current document to HTML" command, or Markdown PDF (yzane.markdown-pdf), which exports HTML too despite the name.

Browser converters are the fastest route for a single paste and the only one that works on a phone. The trade-off is that most do not disclose which parser they use, so you cannot assume a flavor, and you should know whether the text leaves your device. That is checkable in about ten seconds: open devtools, watch the Network tab, run the conversion, and look for an outgoing POST carrying your text. A genuinely client-side tool makes no request at all. Do that check before pasting anything proprietary.

Verdict

Pick by job, not by popularity. A one-off paste wants a browser converter; a docs pipeline wants pandoc; an application that renders Markdown wants markdown-it plus DOMPurify. The one rule that spans all of them is that Markdown is a family of dialects rather than a format, so name your flavor before you debug your output — and if you also work across config formats, the same lesson applies to choosing between JSON, YAML and TOML and to knowing when a browser tool beats a CLI.

What you should not do is write the converter yourself. Unbalanced asterisks, escaped literals, emphasis inside link text and doubled-backtick code spans all parse correctly under a real library and all break a chain of regex replacements. Markdown's grammar is context-sensitive; one npm install buys you a decade of edge cases already handled.

Keep reading