
⚙️ Grav: frequently used settings and code snippets (Markdown, Twig, Atom editor)
When you've been working with Grav CMS for several years, you accumulate a set of snippets that travel from project to project. YAML frontmatter, Twig templates, syntax highlighting configs, Atom regex patterns: digging through documentation or an old repository every time wastes time. Especially when Grav is running on a VPS and you need to remember how to properly install the sqlite driver for PHP 8.3 or comment out SetHandler after an Apache update.
This cheat sheet is a distillation from real-world practice: default header, Markdown settings for pages, Twig snippets in post body, Atom regex for post-processing.md exports, and Ubuntu server commands. All in one place, with explanations of what and why.
💡 Quick overview:
- Configure Grav YAML frontmatter with all fields: taxonomy, page-toc, highlight, process, custom page-addon
- Set up Markdown logic in page headers: title, description, date, taxonomies, template
- Add Twig code to the body: auto table of contents via
toc(), Unitegallery gallery - Process Writage.md exports in Atom: replacing media/ in paths, passing classes for lazy load
- Set up server environment: SQLite, Memcached, PHP 8.3 on Ubuntu 24.04, editing php*.conf for Virtualmin
Grav default header for all site pages
The basic Grav page YAML frontmatter includes fields that the CMS reads during rendering. Below is a working template covering most typical tasks: from taxonomies to custom page-addon flags.
1 title: '' 2 date: '2026-06-14 10:00' 3 published: true 4 media_order: poster.jpg 5 metadata: 6 description: '' 7 taxonomy: 8 category: 9 - WordPress 10 tag: 11 - WordPress - Plugins 12 - WordPress - Admin 13 page-toc: 14 active: true 15 template: blog_item 16 highlight: 17 enabled: true 18 lines: true 19 page-addon: 20 ifarmelazy: true
page-toc.active: true activates the Page Toc plugin (GitHub), which builds a table of contents from H1-H6 directly on the page. template: blog_item explicitly sets the blog child page template so Grav doesn't determine it heuristically on each render.
The page-addon block is a custom section for user-defined flags. Here ifarmelazy: true enables lazy loading for iframes: in the file user/themes/g5_helium/custom/templates/partials/blog_item.html.twig a condition triggers, loading the iframe.ly script.
1 {% if attribute(page.header, 'page-addon').ifarmelazy %} 2 <script async charset="utf-8" src="//cdn.iframe.ly/embed.js"></script> 3 {% endif %} 4
To insert the iframe itself (for example, a YouTube video), use this structure:
1 <iframe allowfullscreen 2 data-iframely-url="https://www.youtube.com/embed/54WI1XSilb4" 3 scrolling="no" 4 style="border: 0; top: 0; left: 0; width: 100%; height: 100%; position: absolute;"> 5 </iframe>
Markdown settings for post page headers
This section covers fields specified in the header of each individual post.
Title and description
1 title: 'Grav + Gantry 5 — an overview of useful extensions and resources' 2 metadata: 3 description: 'A collection of Grav extensions for Gantry 5 that speed up work with content and templates.'
Description goes into <meta name="description"> and is important for search result snippets.
Creation date and Auto Date
1 date: '2026-06-14 10:00'
To have the date automatically set when creating a page, install the Auto Date plugin, github.com/getgrav/grav-plugin-auto-date. It inserts the current date and time if the date field is not filled in manually.
Taxonomies
For a single category and single tag, flat syntax:
1 taxonomy: 2 category: WEB 3 tag: Markdown
For multiple values, use list format:
1 taxonomy: 2 category: 3 - Grav 4 tag: 5 - Grav - Gantry 5 6 - Grav - Plugins
Page Toc and template
Page Toc generates a table of contents from headings within the page body. Besides the page-toc.active: true flag in the header, you can add Twig code directly to the theme template (see the Twig section below).
template: blog_item is specified manually so Grav reliably uses the blog child post template, as the CMS sometimes fails with auto-detection for new pages.
Markdown and Twig preprocessors
1 process: 2 markdown: true 3 twig: true 4 twig_first: true
twig_first: true means Grav will first process the Twig templating engine, then Markdown. The order is critical if the page body contains Twig logic that generates Markdown markup.
Syntax highlighting (Highlight)
Depends on the Highlight plugin, github.com/getgrav/grav-plugin-highlight.
1 highlight: 2 enabled: true 3 lines: true
lines: true adds line numbering in highlighted code blocks.
Twig settings for page body
Auto table of contents via toc()
Page Toc can work not only from the header but also through direct insertion in the theme template. In the file user/themes/g5_helium/custom/templates/partials/blog_item.html.twig, add:
1 {# PAGE TOC START #} 2 {# 3 page-toc: 4 active: true 5 #} 6 {% if attribute(page.header, 'page-toc').active %} 7 {% set table_of_contents = toc(page.content) %} 8 {% if table_of_contents is not empty %} 9 Table of contents: 10 {{ table_of_contents|raw }} 11 {% endif %} 12 13 {% endif %} 14 {# PAGE TOC END #} 15
The Twig function toc(page.content) parses headings within page.content and returns a structured tree. The condition checks the page-toc.active flag from the header, allowing you to enable and disable the table of contents for individual pages without touching the template.
Image gallery via Unitegallery
The Unitegallery plugin, github.com/variar/grav-plugin-unitegallery, adds a gallery to Grav based on the Unitegallery JS library with several display themes.
After installation through the admin panel or GPM (Grav Package Manager), a single line is added to the page body:
1 {{ unite_gallery(page.media.images) | raw }} 2
It renders all images uploaded to the page's media collection as a gallery.
Atom search and replace for Writage.md exports
When exporting WordPress posts to Markdown via Writage, image paths contain the media/ prefix, and enabling lazy loading requires figure-img and img-fluid classes. Manually editing each URL is pointless; Atom with regular expressions does it in a second.
The Lazy Image plugin handles lazy loading, github.com/unsaturated/grav-plugin-lazy-image.
Find (regular expression):
1 (![.*]()(media/)(.*)())
Replace with lightbox variant (FeatherLight plugin):
1 $1$3)
Replace with lazy loading only:
1 $1$3?classes=figure-img,img-fluid)
After replacement, the image receives a classes query parameter that Lazy Image picks up.
Working with thumbnails
Grav can dynamically manipulate images: resize, crop, cache. A page thumbnail is set through Markdown syntax with query parameters:
1 
More about media capabilities in the official documentation: learn.getgrav.org/content/media.
Ubuntu 24.04 server setup for Grav
Grav on a VPS requires PHP, SQLite (for internal plugin database), and preferably Memcached (page caching). Below are current commands for Ubuntu 24.04 LTS and PHP 8.3.
Installing PDO SQLite on PHP 8.3
1 sudo apt-get update 2 sudo apt-get install php8.3-sqlite 3 sudo systemctl restart apache2
SQLite is used by Grav for storing logs, plugin cache, and internal bookkeeping. Without the driver, the admin panel will crash with a database connection error.
Installing Memcached
Current instructions: tecadmin.net/install-memcached-with-php-on-ubuntu/.
1 sudo apt-get update 2 sudo apt-get install memcached 3 4 sudo add-apt-repository ppa:ondrej/php 5 sudo apt-get update 6 sudo apt-get install -y php php-dev php-pear libapache2-mod-php 7 8 sudo apt-get install -y php-memcached 9 10 sudo systemctl restart apache2
PHP files not executing after version change (Virtualmin/Webmin)
After switching from PHP 7.x to 8.3, Apache may serve .php files as text instead of executing them, a known behavior when changing modules. The problem is SetHandler directives inside the PHP module config.
1 sudo nano /etc/apache2/mods-enabled/php8.3.conf
Comment out these lines:
1 # SetHandler application/x-httpd-php 2 # SetHandler application/x-httpd-php-source
Final php8.3.conf after edits:
1 <filesmatch ".+\.ph(ar|p|tml)$"> 2 # SetHandler application/x-httpd-php 3 </filesmatch> 4 <filesmatch ".+\.phps$"> 5 # SetHandler application/x-httpd-php-source 6 # Deny access to raw php sources by default 7 # To re-enable it's recommended to enable access to the files 8 # only in specific virtual host or directory 9 Require all denied 10 </filesmatch> 11 12 # Deny access to files without filename (e.g. '.php') 13 <filesmatch "^\.ph(ar|p|ps|tml)$"> 14 Require all denied 15 </filesmatch> 16 17 # Running PHP scripts in user directories is disabled by default 18 # 19 # To re-enable PHP in user directories comment the following lines 20 # (from <IfModule ...> to </IfModule>.) Do NOT set it to On as it 21 # prevents .htaccess files from disabling it. 22 <ifmodule mod_userdir.c=""> 23 <directory /home/*/public_html> 24 php_admin_flag engine Off 25 </directory> 26 </ifmodule>
After editing, restart Apache:
1 sudo systemctl restart apache2
Video: what is Grav CMS and where to start
A short introduction to Grav for those hearing about flat-file CMS for the first time: what's under the hood, how content is structured, and why running without a database can be faster.
⁉️🤔 Frequently asked questions
Does Grav require a database?
No, Grav is a flat-file CMS. All content, settings, taxonomies, and metadata are stored in text files (Markdown and YAML). SQLite is used optionally for internal needs: plugin cache, logs, queues. PostgreSQL and MySQL are not officially required. In practice this means that backing up a site is simply copying the
/userfolder.
Is the Page Toc plugin mandatory for table of contents?
It can be replaced with direct Twig code using
toc(page.content)in the theme template. But the plugin is more convenient for quick enable/disable through a flag in the YAML frontmatter of a specific page. If your site has 50+ pages, managing through header is more practical than branching in the template.
How does Highlight differ from Prism.js or Highlight.js?
Highlight is the official Grav plugin that integrates highlight.js directly into the rendering pipeline. It understands
enabledandlinesflags from the YAML header and doesn't require manual JS/CSS inclusion; GPM handles everything. Prism.js would need to be included manually in the theme.
Is Grav still relevant in 2026 compared to WordPress?
For certain scenarios, yes. Grav wins for static business card sites, portfolios, documentation, and landing pages where complex user logic and e-commerce aren't needed. Flat-file architecture provides instant response without a database, and content version control works through Git. But for a blog with thousands of posts, a media library, and dynamic archives, WordPress is more practical due to its plugin ecosystem.
What should I do if the Grav admin panel stops working after a PHP update?
Three typical causes: (1)
php8.x-sqliteis not installed, and Grav silently crashes without the database driver; (2)SetHandleris not commented out inphp8.x.confwhen using Virtualmin, and Apache serves.phpas text; (3) permissions oncache/,logs/, andbackup/folders inside/userhave been reset, so runchown -R www-data:www-data user/.
Ready-made cheat sheet: what to copy into your project
Six files and plugins that start a typical Grav project on VPS: header with taxonomies and page-toc, Twig table of contents in the template, Markdown preprocessor, Highlight, Unitegallery, and Atom regex for post-processing exports. Add the server setup from the Ubuntu 24.04 section, and you have a fully ready environment in half an hour. Keep this page bookmarked; we update snippets for current Grav and PHP versions.



