tooldura

Developer Tools

Markdown Is Not One Language: CommonMark, GFM and the Rest

T
tooldura editorial
8 min readUpdated August 14, 2026Open tool →

The same Markdown file can produce different HTML in your editor, your static site generator and your CMS. That is not a bug in any of them. Markdown was released in 2004 as a Perl script and a page of prose, with no grammar and no test suite, and for a decade the script was the specification. Everything since has been an attempt to pin down what it should have said.

What the Original Left Undecided

John Gruber's Markdown.pl came with a description of the syntax and a note that the implementation was the final word on behaviour. That worked while there was one implementation. Once there were dozens, every ambiguity became a fork.

The questions the original never answered are the ones that still bite. How many spaces indent a nested list? What does a list marker followed by nothing mean? Does emphasis apply inside a word? What happens when you open a bracket and never close it? Markdown.pl had an answer for each, but it was whatever the regular expressions happened to do, not a decision anyone made.

Jeff Atwood, who had hit the problem building Stack Overflow, put it plainly in 2012: there was no way to write a test suite because there was nothing to test against. The effort he started became CommonMark.

What CommonMark Settled

CommonMark is a specification with roughly 650 test cases, and its value is that it makes the awkward cases explicit rather than leaving them to the parser.

Emphasis is the clearest example. The spec devotes a long section and a formal algorithm to deciding which asterisks and underscores pair up, including a rule about delimiter runs whose lengths sum to a multiple of three. Nobody writes Markdown thinking about that rule, but it is why intraword_underscores_survive while *asterisks* do not, and why a naive parser mangles snake_case identifiers in prose.

List indentation got the same treatment. A nested item is indented to the column where the parent's content starts, not a fixed number of spaces, so a list under "10. item" needs four spaces where one under "- item" needs two. Most people learn this by getting it wrong.

CommonMark is now the base for GitHub, GitLab, Reddit, Discourse, Stack Overflow, and the major libraries in every language. It is the closest thing to a definition Markdown has.

Where the Flavours Actually Differ

Anything below the first two rows is an extension. If your document depends on one, it will render as literal text wherever that extension is not enabled.

FeatureSupportNotes
Headings, lists, links, emphasisEverywhereThe 2004 core, unchanged in substance since
Fenced code blocksCommonMarkBacktick fences with a language label, now universal
TablesGFMNot in CommonMark; pipes render as literal pipes without the extension
Task listsGFMCheckbox syntax, rendered as disabled inputs
StrikethroughGFMDouble tildes; single tildes work on GitHub but not everywhere
Bare URL autolinkingGFMCommonMark requires angle brackets around the URL
FootnotesNeitherWidely implemented, specified nowhere; pandoc and many SSGs add it
Front matterNeitherA convention of static site generators, not part of any Markdown spec

The Differences That Catch People

These four account for most of the moments where a document renders correctly in one place and wrong in another.

1

Single line breaks

In Markdown a newline inside a paragraph is just a space; a line break needs two trailing spaces or a backslash. GitHub comments, issues and pull request descriptions ignore that and break on every newline. Text written in an issue and pasted into a static site turns into one long paragraph.

2

Underscores inside words

CommonMark refuses to start emphasis inside a word with an underscore, precisely so that file_name_here survives. Older parsers italicise the middle. Asterisks have no such protection, which is why a literal asterisk in prose needs escaping.

3

Nested list indentation

Two spaces is the habit, but the rule is alignment with the parent item's content. Under an ordered list with two-digit numbers you need more. Get it wrong and the nested list becomes a paragraph inside the previous item, or a separate list entirely.

4

Raw HTML

CommonMark passes HTML in your source straight through to the output. Most hosted platforms do not: GitHub, Reddit and comment systems strip or escape it, because rendering user HTML on a shared page is how sites get exploited.

🛡️

Markdown is not a sandbox

A Markdown parser is not a sanitiser, and the good ones say so in their own documentation. If raw HTML passes through, a script tag in the source becomes a script tag in the output. Even with HTML disabled, a link destination of javascript:… is legal Markdown and most parsers will emit it as an href. Anywhere you render Markdown that someone else wrote, the output has to go through a sanitiser such as DOMPurify before it reaches the page. This tool sanitises its own preview for exactly that reason, and leaves the HTML you copy untouched, because that document is yours.

Convert Markdown and see both sides

GFM tables and task lists, heading anchors, escaped or raw HTML.

Open Markdown to HTML →

Heading Anchors, and Why Yours Do Not Match

Nothing in any Markdown specification says a heading should get an id. Every platform that supports linking to a section invented its own rule, and the rules disagree.

GitHub lowercases the heading, removes punctuation, replaces spaces with hyphens, and appends -1, -2 to repeats. It keeps non-Latin characters as they are, so a Russian heading gets a Russian anchor that arrives percent-encoded when shared. Other generators transliterate instead, some strip stop words, and some number every heading from the start.

This matters when content moves. A table of contents generated for GitHub and published through a different pipeline can end up with links that point at anchors nobody generated. If your document links to its own headings, generate the anchors with the same rule as the destination, or write explicit ids in HTML and accept that the file is no longer portable.

Writing Markdown That Survives the Move

The practical advice is narrow, because most Markdown is fine anywhere. The parts that break are predictable.

Stay inside CommonMark plus GFM unless you control the renderer. Footnotes, definition lists and math blocks are useful and well supported in specific ecosystems, but they are extensions, and outside those ecosystems they render as the literal characters you typed.

Use blank lines between blocks, always. Almost every reported difference between renderers involves a block that starts on the line immediately after another one, and a blank line removes the ambiguity in every parser.

Avoid raw HTML in documents that will travel. It is the single feature most likely to be stripped, escaped, or rendered depending on where the file lands. If you need the structure, ask whether a table or a fenced block gets you close enough.

And check the output rather than trusting the input. A rendered preview alongside the source is the fastest way to catch the list that did not nest and the emphasis that swallowed a paragraph.

Frequently Asked Questions

Related Tools

Keep Reading