Grav Markdown & Shortcodes — Extras, Custom Shortcodes and Formatting
In this tutorial, you'll learn Grav's Markdown extras and shortcode system — how to enable Markdown Extra features, create custom shortcodes, format content, and extend Markdown with Twig-based shortcodes.
What You'll Learn
- Grav's Markdown Extra features: tables, footnotes, definition lists, abbreviations
- How shortcodes work in Grav (WordPress-style shortcodes)
- Built-in shortcodes: safe-email, random, markdown-fallback
- Creating custom shortcodes with Twig and plugins
- Shortcode parameters, nesting, and escaping
- Combining shortcodes with page content
Why It Matters
Standard Markdown covers bold, italic, links, and images. But real-world content needs more: tables, footnotes, collapsible sections, alerts, and reusable components. In WordPress, you use Gutenberg blocks or shortcode plugins. In Grav, you enable Markdown Extra for extended syntax and build shortcodes for anything the content editors need. Shortcodes let you embed complex functionality (forms, galleries, code samples) with a simple tag like {{< gallery >}}.
Real-World Use
A documentation site needs a consistent "callout" box for warnings, tips, and information. Rather than writing raw HTML every time, the team creates a {{< callout "type" "message" >}} shortcode. Content editors use it throughout pages. When the design of callout boxes changes, only the shortcode template is updated — all 200 callouts update automatically.
Learning Path
flowchart LR
A["Page Collections"] --> B["Markdown & Shortcodes
← You are here"]:::current
B --> C["Twig Filters & Functions"]
C --> D["Twig Macros"]
D --> E["Twig Inheritance"]
E --> F["Twig Debugging"]
F --> G["Custom Twig Extensions"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Markdown Extra
Grav supports Markdown Extra, an extension of standard Markdown with additional syntax. Enable it in user/config/system.yaml:
pages:
markdown:
extra: true
Tables
| Feature | Standard Markdown | Markdown Extra |
|---------|------------------|----------------|
| Tables | No | Yes |
| Footnotes | No | Yes |
| Definition Lists | No | Yes |
Output:
<table>
<thead>
<tr>
<th>Feature</th>
<th>Standard Markdown</th>
<th>Markdown Extra</th>
</tr>
</thead>
<tbody>
<tr>
<td>Tables</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Footnotes</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Definition Lists</td>
<td>No</td>
<td>Yes</td>
</tr>
</tbody>
</table>
Footnotes
Grav is a flat-file CMS[^1].
[^1]: A flat-file CMS stores content as files instead of a database.
The footnote appears at the bottom of the page as a linked reference.
Definition Lists
Grav CMS
: A flat-file content management system
: Uses Markdown files instead of a database
Twig
: The templating engine used by Grav
: Based on Symfony's Twig component
Output:
<dl>
<dt>Grav CMS</dt>
<dd>A flat-file content management system</dd>
<dd>Uses Markdown files instead of a database</dd>
<dt>Twig</dt>
<dd>The templating engine used by Grav</dd>
<dd>Based on Symfony's Twig component</dd>
</dl>
Abbreviations
*[CMS]: Content Management System
*[PHP]: PHP: Hypertext Preprocessor
Grav is a flat-file CMS written in PHP.
Shortcodes
Shortcodes are Grav's equivalent of WordPress shortcodes — reusable content snippets with parameters.
Built-in Shortcodes
safe-email:
{{< safe-email "user@example.com" >}}
Renders the email address in a way that is obfuscated from spam bots but readable to users.
random:
Random number: {{< random 100 999 >}}
Outputs a random number between 100 and 999 on each page load.
markdown-fallback:
{{< markdown-fallback >}}
**Bold text** inside a shortcode
{{< /markdown-fallback >}}
Forces Markdown processing inside shortcodes that normally escape content.
Enabling Shortcodes
Shortcodes require the shortcode-core plugin:
bin/gpm install shortcode-core
After installation, configure in user/config/plugins/shortcode-core.yaml:
enabled: true
active: true
active_admin: true
admin_pages: true
parser: regular
fontawesome:
load: false
Custom Shortcodes with Twig
Shortcode templates live in user/themes/YOURTHEME/shortcodes/. Each shortcode is a Twig file.
Simple Shortcode
Create user/themes/mytheme/shortcodes/notice.twig:
<div class="notice notice-{{ type|default('info') }}">
<strong>{{ type|default('Info') }}:</strong> {{ content|raw }}
</div>
Usage in Markdown:
{{< notice type="warning" >}}
This feature is deprecated in Grav 1.8.
{{< /notice >}}
Output:
<div class="notice notice-warning">
<strong>Warning:</strong> This feature is deprecated in Grav 1.8.
</div>
Shortcode with Parameters
{# user/themes/mytheme/shortcodes/button.twig #}
<a href="{{ url }}" class="btn btn-{{ style|default('primary') }} btn-{{ size|default('md') }}">
{{ content|default('Click Here') }}
</a>
{{< button url="/download" style="primary" size="lg" >}}Download Now{{< /button >}}
Self-Closing Shortcodes
{# user/themes/mytheme/shortcodes/youtube.twig #}
<iframe
width="{{ width|default('560') }}"
height="{{ height|default('315') }}"
src="https://www.youtube.com/embed/{{ id }}"
frameborder="0"
allowfullscreen
></iframe>
{{< youtube id="dQw4w9WgXcQ" width="800" height="450" >}}
Nested Shortcodes
Shortcodes can be nested inside each other:
{{< notice type="tip" >}}
Check out this video: {{< youtube id="abc123" >}}
{{< /notice >}}
The inner shortcode (youtube) is processed first, then the outer shortcode (notice) wraps it.
Shortcode with Plugin Logic
For complex shortcodes, register them in a plugin:
<?php
// user/plugins/my-custom-shortcodes/my-custom-shortcodes.php
namespace Grav\Plugin;
use Grav\Common\Plugin;
use Grav\Plugin\ShortcodeCore\ShortcodeManager;
class MyCustomShortcodesPlugin extends Plugin
{
public static function getSubscribedEvents()
{
return [
'onShortcodeHandlers' => ['onShortcodeHandlers', 0],
];
}
public function onShortcodeHandlers()
{
$this->grav['shortcode']->registerHandler(
'weather',
function($attrs, $content, $tag) {
$city = $attrs->get('city', 'London');
// Fetch weather data from an API
return "<div class='weather'>Weather in {$city}: Sunny, 22°C</div>";
}
);
}
}
Then use in Markdown:
{{< weather city="Mumbai" >}}
Shortcode Configuration
Shortcodes can access plugin configuration:
{% set default_color = grav.config.plugins.myplugin.default_color|default('blue') %}
<div style="color: {{ color|default(default_color) }}">
{{ content }}
</div>
Disabling Shortcodes
Shortcodes can be escaped by prefixing with &:
&{{< notice >}}This is not processed as a shortcode{{< /notice >}}
The & prefix tells the parser to output the shortcode as literal text.
Shortcode Aliases
Define aliases in shortcode-core configuration:
# user/config/plugins/shortcode-core.yaml
shortcodes:
notice:
enabled: true
aliases:
- info
- warning
- alert
Then {{< info >}} works the same as {{< notice type="info" >}}.
Common Mistakes
Shortcode plugin not installed: Shortcodes do not work without the
shortcode-coreplugin. Install it withbin/gpm install shortcode-coreand enable it in configuration.Twig shortcode in wrong directory: Shortcode templates go in
user/themes/YOURTHEME/shortcodes/, nottemplates/. Grav looks for shortcode templates in theshortcodes/subdirectory.Forgetting to close shortcodes: Wrapping shortcodes (those with content between opening and closing tags) require both
{{< shortcode >}}and{{< /shortcode >}}. Self-closing shortcodes use single tags.Markdown Extra disabled: Tables, footnotes, and definition lists require
markdown.extra: truein system.yaml. Without it, the syntax renders as raw text.Shortcode whitespace issues: Extra whitespace inside shortcode parameters can cause Parsing errors. Use
param="value"without spaces around the=sign.
Practice Questions
What configuration enables Markdown Extra features in Grav? Answer: Set
pages.markdown.extra: trueinuser/config/system.yaml. This enables tables, footnotes, definition lists, and abbreviations.Where do custom shortcode templates live? Answer: In
user/themes/YOURTHEME/shortcodes/. Each shortcode is a Twig file named after the shortcode tag (e.g.,notice.twigfor{{< notice >}}).How do you create a self-closing shortcode vs a wrapping shortcode? Answer: Self-closing shortcodes use a single tag:
{{< shortcode param="value" >}}. Wrapping shortcodes have opening and closing tags:{{< shortcode >}}content{{< /shortcode >}}.What happens when you nest shortcodes? Answer: Inner shortcodes are processed first, then the outer shortcode wraps the result. This allows composition of shortcodes (e.g., a YouTube video inside a notice box).
Challenge: Create a complete shortcode library for a documentation site. Build the following shortcodes: a callout with type (info, warning, danger, success) and icon support, a code-tabs component that displays multiple code samples with tab switching, an accordion with collapsible sections, a tooltip that shows help text on hover, a gallery with lightbox image display, and a file-download with icon, filename, and size. Each shortcode must support parameters and be nestable.
FAQ
{{< faq "How do I pass complex data structures as shortcode parameters?" "Shortcode parameters are strings. For complex data, use JSON in a single parameter: {{< gallery images='[\"img1.jpg\",\"img2.jpg\"]' >}}. Then decode it in the shortcode template." >}}
Mini Project
Goal: Build a shortcode library for a developer documentation site.
- Install the shortcode-core plugin and enable Markdown Extra
- Create a
calloutshortcode with types: info, warning, danger, success - Create a
codeshortcode that wraps content in a syntax-highlighted block with copy button - Create a
tabsshortcode system with tab switching for multi-language code examples - Create a
downloadshortcode that renders a file download button with icon - Create an
accordionshortcode for FAQ sections - Create a
youtubeshortcode for embedded videos - Create a
gistshortcode that embeds GitHub gists - Test nesting: callout containing tabs containing code
- Document the shortcode library for content editors
What's Next
Now you can extend Markdown with shortcodes. Next, learn Twig filters and functions:
Continue to Lesson 15: Twig Filters & Functions — Date formatting, translation, array functions, and built-in Twig utilities.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro