Website Development with Grav CMS
Grav is a flat-file CMS: content is stored in Markdown files, database is absent. This simplifies deployment, backup, and version control — entire site lives in git. Grav is built on PHP, uses Twig for templates, and YAML for configuration.
When Grav Is Suitable
Grav fits projects where:
- content is static or changes rarely
- team works with Markdown and git
- fast site needed without database overhead
- hosting is limited (no MySQL/PostgreSQL)
- site-documentation, portfolio, blog, landing
Grav is not suitable for e-commerce with thousands of SKU, complex portals with user content, or sites with intensive user sessions.
Site Structure
user/
pages/
01.home/ # page (number = sort order)
home.md # filename matches template → templates/home.html.twig
02.services/
_service-dev/ # _ = hidden page, not in listings
default.md
services.md
03.blog/
blog.md # template blog.html.twig
2024-12-01.my-post/
post.md
themes/
my-theme/
plugins/
config/
site.yaml
system.yaml
data/ # form data, custom data
Markdown filename is template name. home.md → home.html.twig.
Page Frontmatter
Each .md file starts with YAML frontmatter:
---
title: Custom Development
slug: custom-development
date: 2024-11-15
published: true
template: service-detail
taxonomy:
category: [services]
tag: [php, laravel, api]
metadata:
description: 'Web application development with PHP/Laravel'
keywords: 'development, laravel, api'
hero_image: hero.jpg
show_sidebar: true
---
## Web Application Development
Page text in **Markdown** with shortcode support...
Collections and Listings
Template blog.html.twig lists child pages:
{% set posts = page.children.visible.order('date', 'desc').slice(0, 10) %}
{% for post in posts %}
<article class="post-card">
<time datetime="{{ post.date|date('Y-m-d') }}">{{ post.date|date('d.m.Y') }}</time>
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
{% if post.header.summary %}
<p>{{ post.header.summary }}</p>
{% else %}
<p>{{ post.content|striptags|slice(0, 200) }}…</p>
{% endif %}
{% if post.header.hero_image %}
<img src="{{ page.media[post.header.hero_image].url }}" alt="{{ post.title }}">
{% endif %}
</article>
{% endfor %}
{{ page.children.paginate(10) }}
Plugins and Functionality
Grav extends via plugins through GPM (Grav Package Manager):
bin/gpm install form # forms
bin/gpm install login # user login
bin/gpm install sitemap # XML-sitemap
bin/gpm install seo # SEO meta tags
bin/gpm install shortcode-core # shortcodes in Markdown
bin/gpm install admin # admin panel
Form plugin allows creating contact form via YAML in page frontmatter without programming.
Blueprints: Custom Fields
Blueprints describe page fields for admin form:
# user/blueprints/pages/service-detail.yaml
title: Service Detail
extends@:
type: default
context: blueprints://pages
form:
fields:
tabs:
type: tabs
active: 1
fields:
service:
type: tab
title: Service
fields:
header.intro:
type: textarea
label: Introduction
size: large
header.hero_image:
type: filepicker
label: Featured Image
preview_images: true
accept: ['image/*']
header.features:
type: list
label: Features
fields:
.icon:
type: text
label: Icon (CSS class)
.text:
type: text
label: Text
Performance
Grav caches pages in /cache/ automatically. Configuration in system.yaml:
cache:
enabled: true
driver: auto # auto chooses between file, apc, memcache, redis
lifetime: 604800 # 7 days
gzip: true
pages:
cache_all: true
assets:
css_pipeline: true # combine CSS
js_pipeline: true # combine JS
js_minify: true
css_minify: true
On weak hosting without APC/Redis, pages served from file cache — speed comparable to static sites.
Typical Project Structure
| Site Type | Templates | Timeline |
|---|---|---|
| Landing / business card | 3–5 templates | 1–2 weeks |
| Corporate site with blog | 6–10 templates | 2–4 weeks |
| Documentation portal | 5–8 templates + plugins | 2–3 weeks |







