Markdown flavors explained: CommonMark, GFM, MDX, and why your table doesn't render
Markdown looks universal until you paste a GitHub table into a Discord post. Here's what each flavor supports, and how to pick one for your project.
Why there's no single Markdown
John Gruber's original Markdown (2004) was a rough spec and a Perl script. It intentionally left many cases ambiguous ("if it looks like HTML, treat it as HTML"). Every serious tool that used Markdown for anything real had to make its own decisions โ and those decisions diverged. Today we have at least a dozen dialects.
Four matter most for developers:
- Original Markdown โ Gruber's 2004 script. Nobody targets this anymore.
- CommonMark โ a strict, unambiguous specification. What Discord, Reddit, and Stack Overflow are (mostly) based on.
- GitHub Flavored Markdown (GFM) โ CommonMark plus tables, task lists, strikethrough, autolinks. Used by GitHub, GitLab, most static-site generators.
- MDX โ GFM plus JSX components. Used by Next.js, Docusaurus, Astro โ anywhere docs and React coexist.
Feature matrix
| Feature | CommonMark | GFM | MDX |
|---|---|---|---|
| Bold/italic/lists/links | โ | โ | โ |
| Fenced code blocks | โ | โ | โ |
| Tables | โ | โ | โ |
Task lists (- [ ]) |
โ | โ | โ |
Strikethrough (~~) |
โ | โ | โ |
| Autolinks (raw URLs) | โ | โ | โ |
| Footnotes | โ | โ (2021+) | โ |
| JSX / components | โ | โ | โ |
| Raw HTML | โ | โ | โ (interpreted as JSX) |
If your table isn't rendering, you're targeting CommonMark. If your <div className="foo"> breaks, you're not in MDX.
The five gotchas that break Markdown across flavors
1. Line breaks. In CommonMark, a single newline inside a paragraph is not a break โ it's collapsed to a space. Two trailing spaces or a blank line force a break. GitHub's editor was inconsistent about this for years and now defaults to "single newline = break" (like Slack).
2. Nested lists indentation. CommonMark requires 2-4 space indent for nested list items depending on marker width. Most parsers accept anything; strict parsers reject.
3. Fenced code language identifiers. Every parser has a slightly different list. tsx works everywhere; svelte works on GitHub but might not on your static site. Missing highlighting is usually this.
4. Raw HTML. CommonMark and GFM let you drop <div> into a doc and it passes through. MDX parses that as a component reference โ <Foo> means "render the Foo component I've imported", not "output literal <Foo>".
5. Underscores inside words. snake_case_word renders italic in some parsers, literal in others. Escape with backslashes if you must have literal underscores.
When to pick which
- Writing docs for GitHub or a static site? GFM. Every static-site generator (Jekyll, Hugo, Zola, Astro, Next.js MDX) speaks it.
- Rendering user input on a forum or chat? CommonMark โ the strict spec means no surprises from clever edge cases.
- Building a docs site that needs interactive examples? MDX โ you can drop a React demo component right into the markdown.
- Just need Markdown โ HTML once? Any GFM parser is fine.
Sanitizing user-generated Markdown
If you're rendering user-submitted Markdown, remember: raw HTML is a valid Markdown feature. A user can drop <script> into their post. Every Markdown-to-HTML converter emits raw HTML in the output. You must sanitize downstream, typically with DOMPurify:
import { marked } from 'marked';
import DOMPurify from 'dompurify';
const dirty = marked.parse(userInput);
const clean = DOMPurify.sanitize(dirty);
element.innerHTML = clean;
Some parsers have "safe mode" that strips HTML at parse time (marked's sanitize option was removed in v5+; use DOMPurify instead).
Converting between flavors
Downgrading (GFM โ CommonMark) means stripping tables and task lists โ they become literal pipe characters and [ ]. Upgrading (CommonMark โ GFM) is safe; GFM is a superset.
Markdown to HTML uses marked in GFM mode โ the closest thing to a lingua franca for developer Markdown. Preview and HTML render side by side, so you can debug rendering differences in a few seconds.
Related tools:
- HTML Beautifier โ for cleaning up the generated HTML before shipping.
- JSON Formatter โ for frontmatter that's actually JSON.