Markdown to HTML: A Developer's Guide
July 26, 2026 · DevTools
You wrote your README in Markdown. Your issue tracker accepts Markdown. Your static-site generator eats Markdown. Your internal wiki renders Markdown. At this point, Markdown isn't one of many formats you could choose — it's the lingua franca of developer-facing documentation. Which means at some point, every team that takes user input needs to turn Markdown into HTML. And the moment you do, you have to think about what to do with the raw HTML and the <script> tags hidden inside.
You can try every example in this guide with the Markdown to HTML Converter. It runs entirely in your browser — no upload, no telemetry, no copy of your content sitting on someone else's server.
Why Markdown won for documentation
Markdown was designed in 2004 as a minimal authoring format: readable as plain text, easy to write, easy to convert to HTML. The bet was that you could lose most of HTML's expressive power and keep 90% of its usefulness for the things people actually write — paragraphs, lists, links, code, the occasional table. That bet paid off spectacularly. A few specific reasons:
- Diff-friendly — a one-word edit doesn't reflow the document; line-based Markdown produces small, reviewable diffs in version control
- Portable — the same source renders sensibly on GitHub, GitLab, Bitbucket, Notion, Slack, Discord, Reddit, static-site generators, blog platforms, and your editor's preview
- Author-resistant — there are very few ways to make Markdown look bad, and most of them are intentional
- Recoverable — even if your renderer breaks, the source is still readable as plain text
HTML, by contrast, was designed for a different audience (browsers, not authors) and it shows. A diff of an HTML edit is a wall of angle brackets. A Markdown diff is a wall of words.
CommonMark, GFM, and the dialects that matter
Markdown has dialects. Which one you target decides which features you get:
| Dialect | Notable additions |
|---|---|
| Original Markdown | The 2004 spec; vague by design; almost no one targets it anymore |
| CommonMark | A formal spec that resolved 10 years of edge cases; the baseline for most modern parsers |
| GFM (GitHub Flavored Markdown) | Tables, task lists, strikethrough, autolinks, mention syntax — what GitHub renders |
| MultiMarkdown | Tables, footnotes, citations, math — popular in academic and book workflows |
| Pandoc Markdown | A superset that supports nearly everything; the kitchen-sink format Pandoc converts from |
For most developer documentation, GFM is the right target — it's what GitHub renders, what most READMEs assume, and what users expect when they paste a table into an issue. The Markdown to HTML Converter supports GFM out of the box.
The GFM extensions worth knowing
Five additions cover the vast majority of "why doesn't my Markdown render right?" surprises:
1. Tables — pipe syntax with a separator row:
| Tool | Use for |
| ---------- | ---------------- |
| JSON | structured data |
| YAML | config files |
| TOML | app config |
Renders as a proper <table> with header cells. Without GFM, the pipes are just literal text.
2. Task lists — checkbox syntax inside a list item:
- [x] convert XML to JSON
- [ ] validate the output
- [ ] ship it
Renders as a <ul> of checkboxes (or <input type="checkbox" disabled> in pure HTML). Useful for progress notes, but the checkboxes aren't interactive — they're just visual state.
3. Strikethrough — wrap text in double tildes:
~~old behaviour~~ → new behaviour
Renders as <del>old behaviour</del> <em>→</em> new behaviour. Handy for showing what changed.
4. Autolinks — bare URLs become clickable links:
See https://example.com for details.
The bare URL becomes <a href="https://example.com">https://example.com</a> without any Markdown syntax. Saves a lot of typing in long documents.
5. Disabling inline HTML — most parsers still allow raw HTML, but you can flip the converter into a strict mode that strips it. This is the security-relevant switch, and we'll come back to it.
Fenced code blocks: more useful than they look
Indented code blocks have been in Markdown since day one. Fenced code blocks (surrounded by ) are a GFM/Pandoc addition that's worth their weight in gold:
```python
def greet(name):
return f"Hello, {name}!"
```
Three backticks opens the fence, three backticks closes it. The optional language identifier (after the opening fence) is a convention — most renderers, including this one, use it to pick a syntax-highlighting grammar. Python, JavaScript, TypeScript, Go, Rust, SQL, Bash, JSON, YAML, HTML, CSS — pick from a dropdown or accept the auto-detect.
This is one of the reasons Markdown has displaced raw HTML for technical docs: a fenced code block with a language hint produces better output than <pre><code class="language-python">…</code></pre> and is far easier to type.
The sanitization question — and why it matters
Now the security-relevant part. CommonMark and GFM both allow inline HTML in Markdown:
This is <em>emphasised</em> and this is **bold**.
<script>alert('xss')</script>
<img src="x" onerror="alert('xss')">
The first line is fine. The second line will execute when the HTML is rendered in a browser. The third line will execute immediately, without user interaction, because the image source x fails to load and the onerror handler fires.
If your Markdown source comes from you (your README, your docs), this is fine — your threat model is "I won't XSS myself." If your Markdown source comes from users (a comment system, a wiki, an issue tracker, a notes app), you must treat every document as untrusted input and strip or escape the dangerous parts before rendering.
Three strategies, from most to least restrictive:
| Strategy | What it allows | What it blocks |
|---|---|---|
| Strip all HTML | Only Markdown syntax | <script>, <iframe>, <img onerror>, all event handlers |
| Allowlist safe HTML | Common "safe" tags (<b>, <i>, <a>, <code>, <pre>) | Anything not on the allowlist; all event handlers |
| Allow all HTML, then sanitize output | Anything the user writes | Active content via DOMPurify-style cleaning |
For user-generated content, strip or allowlist is the safest default. The sanitizer built into the converter applies the allowlist approach by default — let through formatting tags, drop anything that could execute or load remote resources, and reject <a href="javascript:…"> URLs entirely.
If you must allow user-provided HTML, use a battle-tested library like DOMPurify on the output. Don't roll your own escape function — the XSS surface is enormous, browser parsers are forgiving in surprising ways, and bypasses get discovered regularly.
When Markdown isn't the right choice
Markdown is a great default, but it's not universal:
- Rich text editing in a CMS — Markdown's "no formatting toolbar" is a feature for developers but a wall for non-technical authors
- Complex layouts — you can't build a magazine-style page in Markdown; reach for a templating engine instead
- Inline images with captions — supported in some dialects, but you'll be fighting the syntax
- Author-controlled metadata — frontmatter is a hack; if you need structured metadata, pick a format that supports it natively
For 90% of developer-facing documentation — READMEs, blog posts, internal wikis, changelogs, API reference, design docs — Markdown is the right answer.
A complete before-and-after
Input — a typical Markdown document mixing GFM features:
# Release 1.2.0
- [x] ship the new dashboard
- [x] ~~deprecate~~ remove the old `legacy` flag
- [ ] write migration docs
| Flag | Status |
| ---- | ------ |
| `legacy` | removed |
| `verbose` | default |
See https://example.com/changelog for the full list.
Output (sanitized, syntax-highlighted, GFM):
<h1>Release 1.2.0</h1>
<ul>
<li><input type="checkbox" disabled checked> ship the new dashboard</li>
<li><input type="checkbox" disabled checked> <del>deprecate</del> remove the old <code>legacy</code> flag</li>
<li><input type="checkbox" disabled> write migration docs</li>
</ul>
<table>
<thead><tr><th>Flag</th><th>Status</th></tr></thead>
<tbody>
<tr><td><code>legacy</code></td><td>removed</td></tr>
<tr><td><code>verbose</code></td><td>default</td></tr>
</tbody>
</table>
<p>See <a href="https://example.com/changelog">https://example.com/changelog</a> for the full list.</p>
Notice the task-list checkboxes, the <del> for strikethrough, the table structure, and the autolinked URL — none of which appear in vanilla CommonMark.
Common mistakes
- Heading jumps — going from
#to###in the same document breaks the outline that screen readers and tables of contents rely on - Missing blank lines around blocks — lists, code fences, and blockquotes need blank lines around them or they run into the paragraph above
- Unescaped pipes in tables —
|inside a cell needs to be escaped as\|, or the parser thinks your row has more columns than it does - Mixing tabs and spaces — indentation is meaningful in Markdown; use spaces consistently
- Trusting user input — always sanitize Markdown that didn't come from you, even if your users are "trusted"
Everything stays in your browser
The Markdown to HTML Converter parses and renders locally, so if you need to convert internal docs, drafts, or customer-supplied content, none of it touches a remote server. The previewer in the sidebar updates as you type, which makes it useful as a scratchpad too.
Next steps
- Clean up the Markdown before converting: Markdown Linter
- Need a live preview while you write? Markdown Previewer
- Going the other direction — HTML to Markdown? HTML to Markdown
- Encoding special characters? HTML Encoder / Decoder
- Style rules worth following: Writing Clean Markdown