CommonMark vs GFM: what HTML your Markdown becomes

Line breaks, tables, task lists, bare links and raw HTML: the same Markdown through CommonMark and GitHub Flavored Markdown, with the HTML that comes out.

On this page

Markdown was published in 2004 as a short description and a Perl script, not a specification. Every implementation filled the gaps its own way, so the same file could render differently on two sites. Two efforts brought order: CommonMark, a precise specification of the core syntax with hundreds of examples, and GitHub Flavored Markdown (GFM), which is CommonMark plus a handful of extensions. Most tools today follow one of the two, and most surprises come from the difference between them.

This article puts small inputs through our Markdown to HTML converter and shows the HTML that comes out, then notes where CommonMark, GFM and the converter differ.

Line breaks: the most common surprise

Line one
line two

becomes <p>Line one and line two</p>, one paragraph with an ordinary newline inside it, which a browser displays as a space. A single line break in the source is a soft break. To force a <br />, end the first line with two spaces or a backslash; both give <p>Line one<br />line two</p>. A blank line starts a new paragraph.

Two spaces at the end of a line are invisible and many editors strip them, which is why the backslash form is safer. Some renderers, including GitHub's comment boxes, treat every newline as a break, so text that looked right there collapses into one line elsewhere.

Emphasis and underscores

*em* and _em_ both give <em>, and **strong** or __strong__ give <strong>. The rules for underscores were tightened in CommonMark so that snake_case_name stays plain text: an underscore inside a word does not open emphasis. Asterisks do not have that protection, so 2*3*4 can turn the 3 into italics. Escape it as 2\*3\*4 when you mean multiplication.

What GFM adds

These come from GFM, not core CommonMark. Our converter supports them:

MarkdownHTML produced
~~old~~ price<del>old</del> price
- [x] done<li><input type="checkbox" disabled checked /> done</li>
a pipe table with :--- and ---:<th style="text-align:left"> and style="text-align:right"

A table needs a header row and a delimiter row of dashes; colons in the delimiter row set the alignment of each column. The checkboxes are disabled because a rendered document cannot save a tick anywhere.

GFM also has an autolink extension: Visit https://example.com today becomes a link on GitHub. Core CommonMark leaves it as plain text, and so does our converter. Both turn <https://example.com>, in angle brackets, into <a href="https://example.com">. If a link must work in every renderer, use the angle brackets or write [text](url).

Lists: tight, loose and renumbered

- a
- b

- c

A blank line anywhere between the items makes the whole list loose, and every item is wrapped in <p>, which adds vertical space. Remove the blank line and the items become plain <li>a</li>. This is why a list sometimes looks double-spaced for no visible reason.

Ordered lists take only their first number into account. 5. five followed by 6. six produces <ol start="5">; whatever numbers follow are ignored, so writing 1. on every line is legitimate and makes reordering painless. Changing the bullet character, * then +, starts a new list rather than continuing the first one.

Raw HTML: where the flavours really differ

CommonMark lets raw HTML through unchanged: <b>bold</b> in the source becomes bold. GFM does the same but filters a short list of dangerous tags such as <script>, <style> and <iframe>, and GitHub then runs its own sanitiser on top.

Our converter takes the strictest line: raw HTML is treated as text. <b>bold</b> <script>alert(1)</script> comes out as &lt;b&gt;bold&lt;/b&gt; &lt;script&gt;…, displayed literally. Links are filtered as well: only http, https, mailto, tel, relative links and #anchors are kept, so [click](javascript:alert(1)) becomes the word "click" with no link. The output can only contain tags from a fixed list, which is what makes it safe to paste into a CMS or an e-mail template. If you need specific HTML in the output, convert first and edit the HTML afterwards; HTML entities and escaping explains why pasting unescaped user text into a page is dangerous.

Code blocks

A fenced block is opened by a line of three backticks followed by a language name, here js, and closed by three backticks. With the line const a = 1 < 2; inside, it <pre><code class="language-js">const a = 1 &lt; 2;</code></pre>. The < is escaped, and the language-js class is what syntax highlighters such as Prism or highlight.js look for. The converter does not colour code itself.

Common mistakes

  1. Expecting one newline to be a line break. Use a blank line for a paragraph, or end the line with a backslash.
  2. A blank line inside a list that makes the whole list loose.
  3. Relying on bare URLs being linked. That is a GFM extension; use <…> or [text](url) for portable links.
  4. Pasting HTML into Markdown for a site that escapes it. Some comment systems, and our converter, show it as text by design.
  5. Tables without the delimiter row. Without |---|---| under the header, the "table" is just a paragraph full of pipe characters.
  6. Reference-style links ([text][1] with [1]: url below). They are core CommonMark but not supported by every small converter, ours included; use inline links if the text will travel.

Write a draft in the Markdown to HTML converter, check the preview, and copy either the HTML fragment or a full standalone document. If the output will be dropped into another page, the HTML entity encoder helps with any characters that need escaping by hand.

Tools mentioned in this article

Back to the blog