Markdown is the heart of writing content in Grav, and for Grav 2.0 we gave it a proper pass. The engine is still the same fast, Parsedown-based renderer it has always been, and everything you've written over the years still renders exactly as before. What's new is that Grav now understands a much fuller slice of GitHub Flavored Markdown out of the box, ships a set of opt-in power features for tables, and runs measurably faster while doing it.
This post walks through everything that changed, what now renders differently on existing sites (and how to switch any of it back), and where to go next. The full reference lives on the Learn site at Markdown Syntax.
Why We Did This
Grav has rendered Markdown through a fork of Parsedown since the very beginning. It's fast, it's battle-tested, and a large part of the plugin ecosystem hangs off its extension points, so replacing it was never on the table. But base Parsedown predates a lot of what people now simply expect from Markdown. Task list checkboxes, ==highlight== marks, bare www. links that just work — none of that was there, and closing that gap meant reaching for a plugin or hand-writing HTML.
So rather than swap the engine, we extended it. Grav 2.0 implements the missing GitHub Flavored Markdown constructs directly in core, adds a few widely-requested table features as opt-in extensions, and modernizes the underlying fork for PHP 8.3+. The result is a Markdown experience that feels current without breaking a decade of existing content.
Every one of these features is configurable. The GitHub Flavored Markdown set is on by default because it matches what most people expect; the enhanced table extensions are off by default because they're additive syntax. If anything below changes output you don't want, there's a one-line switch for it under Configuration.
GitHub Flavored Markdown, On By Default
These four features are enabled out of the box on a fresh 2.0 site.
Task Lists
Add [ ] (incomplete) or [x] (complete) right after a list marker and the item renders as a read-only checkbox:
- [x] Write the documentation
- [x] Review the examples
- [ ] Ship it
Each <li> gets a task-list-item class so you can style task lists in your theme, and the checkboxes are rendered disabled:
<ul>
<li class="task-list-item"><input type="checkbox" disabled checked /> Write the documentation</li>
<li class="task-list-item"><input type="checkbox" disabled checked /> Review the examples</li>
<li class="task-list-item"><input type="checkbox" disabled /> Ship it</li>
</ul>
Highlight, Subscript, and Superscript
Three new inline marks round out the formatting you can do without dropping to HTML:
Markdown makes ==certain words stand out==.
The chemical formula for water is H~2~O.
Einstein's famous equation is E = mc^2^.
which produce <mark>, <sub>, and <sup> respectively:
<p>Markdown makes <mark>certain words stand out</mark>.</p>
<p>The chemical formula for water is H<sub>2</sub>O.</p>
<p>Einstein's famous equation is E = mc<sup>2</sup>.</p>
A single ~text~ is subscript while the existing double ~~text~~ stays strikethrough, so the two never collide.
Automatic Links
Bare www. URLs and email addresses are now turned into links automatically, with no markup required:
Visit www.getgrav.org or email [email protected].
<p>Visit <a href="http://www.getgrav.org">www.getgrav.org</a> or email <a href="mailto:[email protected]">[email protected]</a>.</p>
This is GitHub's autolink behaviour. Note that bare http(s):// URLs are handled separately by the existing Auto URL Links option, which remains off by default so it doesn't surprise anyone relying on the old behaviour.
Disallowed Raw HTML (tagfilter)
Grav now escapes a small denylist of "disallowed raw HTML" tags in your content — title, textarea, style, xmp, iframe, noembed, noframes, script, and plaintext — so they render as visible text rather than active markup, matching GitHub Flavored Markdown's tagfilter.
This is a convenience to avoid accidental breakage, not a security boundary. Broad HTML sanitization is still the job of your site's security layer, exactly as before. The tagfilter just stops a stray <script> in a content file from quietly becoming active markup.
What Changes On Existing Sites
Because the GitHub Flavored Markdown features are on by default, four things now render differently than they did on 1.7. None of it is destructive, and every one has an off switch, but it's worth knowing about:
| What | Before 2.0 | On 2.0 |
|---|---|---|
- [ ] at the start of a list item |
literal [ ] text |
a checkbox |
==x==, ~x~, ^x^ in prose |
literal characters | <mark>, <sub>, <sup> |
bare www. URLs and email addresses |
plain text | autolinked |
<script>, <iframe>, <style> etc. in content |
passed through | escaped to visible text |
If any of those collide with your existing content, switch the individual feature off under gfm: in your Markdown config (see below) and that construct goes back to its 1.7 behaviour. You don't have to take an all-or-nothing position — each toggle is independent.
Enhanced Tables (Opt-In)
Grav 2.0 adds five table extensions on top of the standard GitHub-aligned tables. These are all off by default, so a normal table is never changed unless you turn one on — globally, or per page in the page's front matter:
markdown:
tables:
colspan: true
headerless: true
captions: true
attributes: true
multiline: true
Colspan — leave a cell empty and it merges into the cell on its left, increasing that cell's colspan (the MultiMarkdown convention):
| Item | Jan | Feb | Mar |
| :-- | --: | --: | --: |
| Widgets | 10 | 12 | 9 |
| Total for the quarter | | | 57 |
Header-less — start a table directly at the divider row to produce a <tbody>-only table with no header:
| --- | --- |
| Mercury | Closest to the Sun |
| Neptune | Furthest from the Sun |
Captions — a [Caption] line immediately after a table becomes a <caption>, with inline Markdown inside it parsed:
| Symbol | Element |
| --- | --- |
| H | Hydrogen |
| He | Helium |
[The first two elements of the periodic table]
Attributes — a {.class #id} line immediately after a table sets the class and id on the <table>, which is handy for CSS frameworks that style classes like .striped or .responsive. The kramdown-style {:.class} form is accepted too, and only class and id are recognised:
| Name | Role |
| --- | --- |
| Ada | Engineer |
| Grace | Architect |
{.striped .responsive #team-table}
Multi-line cells — end a row with a backslash (\) and the next line continues it, with the following row's cells merged column-wise into the row above and joined with a <br>:
| Feature | Description |
| --- | --- |
| Tables | Alignment, escaped pipes, \
| | and inline cell content |
| Lists | Ordered, unordered and task lists |
Mix and match whichever you need; they compose, and each is independent of the others.
A Faster Engine
While we were in there, we did a careful, benchmark-gated performance pass on the underlying Parsedown fork — avoiding redundant array copies on the hot path, adding a fast path for single-line text, and trimming repeated work in the inline loop. The net result is a Parsedown engine that runs roughly 6–7% faster on a real-world page, and that speedup applies to every page Grav renders, not just pages using the new features.
Just as important is what didn't change: every optimization was gated against a corpus of over 2,600 CommonMark and GFM spec cases, byte-for-byte. The new features add their work as cheap, targeted passes that skip text that can't possibly match, so turning on the full GitHub Flavored Markdown set costs only a few percent over the old baseline. Fast stays fast.
Configuration
Markdown rendering is configured under Configuration → System → Markdown in the Admin panel, or in user/config/system.yaml under the pages.markdown key. Any option can also be overridden per page in the page's front matter under a markdown: key. The defaults are:
pages:
markdown:
extra: false # Enable Markdown Extra
auto_line_breaks: false # Treat a single newline as a <br>
auto_url_links: false # Autolink bare http(s):// URLs
escape_markup: false # Escape inline HTML into entities
gfm: # GitHub Flavored Markdown - on by default
task_lists: true # - [ ] / - [x] checkboxes
marks: true # ==highlight==, ~subscript~, ^superscript^
tagfilter: true # Escape disallowed raw HTML tags
autolinks: true # Autolink bare www. URLs and emails
tables: # Enhanced table extensions - off by default
colspan: false # Empty cell merges into the cell on its left
headerless: false # Table may start at the divider row
captions: false # [Caption] line after a table becomes <caption>
attributes: false # {.class #id} line after a table sets attributes
multiline: false # Row ending in \ continues onto the next line
The gfm options change how existing content renders, so each can be switched off individually if you need the previous behaviour. The tables extensions are all off by default and never affect a standard table unless you opt in.
For Plugin Developers
The same release that added these built-ins also added a formal Markdown Extension API so plugin authors can teach the parser their own block and inline syntax — cleanly, with typed handler objects and a fluent element builder instead of hand-built arrays. If you maintain a plugin that touches Markdown, the next post in the developer series walks through it end-to-end, porting our own GitHub Markdown Alerts plugin to the new API as a worked example: Grav 2.0 Developer Guide: Markdown Extensions.
Quick Links
- The full reference on the Learn site: Markdown Syntax.
- For plugin authors: Grav 2.0 Developer Guide: Markdown Extensions and the Markdown Extensions API reference.
- As always, Discord is the fastest way to reach the team and the community.
Markdown is what you spend your time in when you use Grav, so getting it closer to what people expect — without breaking what's already written, and without slowing anything down — was well worth the effort.
— Andy