Markdown to HTML

Convert Markdown to clean HTML, with a preview and no HTML you did not ask for

Markdown

HTML

Rendered with your browser's own styling, so this is the shape of the document, and not how it will look wherever it ends up.

Paste Markdown, get HTML. Tables, task lists, reference links and heading ids are all handled, and there is a preview so you can see the shape of the document before you paste it anywhere.

HTML is escaped unless you say otherwise

Markdown source:

  Hello <img src=x onerror=alert(1)>

Most converters give you:

  <p>Hello <img src=x onerror=alert(1)></p>

This one gives you:

  <p>Hello &lt;img src=x onerror=alert(1)&gt;</p>

unless you tick the box.

Markdown allows raw HTML in the source and nearly every converter passes it through without comment. That is the right behaviour for a file you wrote and the wrong behaviour for a file somebody sent you, a comment from a form, or anything else you did not type yourself.

So the default here is to escape it, and the switch is there when you want it. If the output is going anywhere near a page other people can see, leave the switch alone and run a real sanitiser on the result as well. This is a converter, not a security boundary.

Links are filtered either way. A javascript: ordata: URL in a link or an image is dropped, not escaped, because there is no legitimate reason for one to be in a Markdown document.

Underscores in the middle of a word

MAX_BUFFER_SIZE

Read naively:  MAX<em>BUFFER</em>SIZE

An underscore only starts emphasis at the edge of
a word. A star does it anywhere, which is why
a*b*c works and a_b_c does not.

This is the single most common bug in a hand-rolled Markdown converter, and it shows up the moment somebody writes a constant name or a file path. CommonMark solves it by saying an underscore can only open or close emphasis at a word boundary, while a star can do it anywhere.

The practical upshot: a*b*c gives you emphasis in the middle of a word and a_b_c does not. That asymmetry looks arbitrary until you have had a variable name italicised without asking.

Code spans come out first

Source:   a `x` b

Wrong:    a<code>x</code> b
Right:    a <code>x</code> b

The space in front went missing because the code
span was lifted out with a space-delimited marker
and the space got eaten with it.

Everything inside backticks has to survive untouched, so code spans are lifted out of the text and replaced with a marker before any other rule runs, then put back at the very end. That is the only way `*not emphasis*`keeps its stars.

The example above is a bug this had on the first attempt. The marker was delimited with spaces, so lifting a code span ate the space in front of it. Markers now use a character out of the Unicode private use area, and anything arriving with one of those in it has it stripped on the way in.

Tight lists and loose ones

- one
- two

<ul><li>one</li><li>two</li></ul>


- one

- two

<ul><li><p>one</p></li><li><p>two</p></li></ul>

A blank line between items turns a tight list
loose, and loose items get paragraph tags. That
is the specification, and it is why your list
suddenly grew gaps.

A list with no blank lines between its items is tight, and its items hold bare text. Put a blank line anywhere between them and the whole list becomes loose, and every item gets wrapped in a paragraph. Since paragraphs have margins, the list visibly grows gaps.

It surprises everybody once. It is in the specification, every conforming converter does it, and the fix is to take the blank line out.

What this does and does not cover

Covered: headings both ways, paragraphs, emphasis, strikethrough, code spans and fences, indented code, blockquotes including nested ones, ordered and unordered lists with nesting, task lists, thematic breaks, inline and reference links, images, autolinks, backslash escapes, pipe tables with alignment, and heading ids in the same style GitHub uses.

Not covered: HTML blocks with their full seven-way classification, link reference definitions inside list items, footnotes, definition lists, and the more exotic corners of emphasis nesting. Those are either rare, or specific to one flavour, or the kind of thing that doubles the size of a parser for a case nobody hits.

Nothing is sent anywhere. The parser is a few hundred lines running in your browser, with no library behind it, which is also why the list above is honest instead of a claim of full compliance.