# Grav CMS: A Flat-File PHP CMS You Install by Unzipping

> Grav stores content as Markdown files with YAML front matter and renders them through Twig, with no database in the stack. It suits small sites and hand-managed deployments, and it is the wrong pick when content lives behind a relational schema.

**getgrav/grav** — Modern, Crazy Fast, Ridiculously Easy and Amazingly Powerful Flat-File CMS powered by PHP, Markdown, Twig, and Symfony

- Repository: https://github.com/getgrav/grav
- Website: https://getgrav.org
- Stars: 15,675 · Forks: 1,426
- Language: PHP
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/getgrav-grav

## What Grav solves, and who it is actually for

Grav is a file-based web platform. Pages are Markdown files with YAML front matter; templates are Twig; configuration is YAML. There is no database to provision, no schema to migrate, and the README states plainly that there is zero installation required: extract the ZIP archive and the site is running. The user/ directory holds pages, themes, plugins and configuration, which means a deploy is a file copy and a backup is a tarball.

That design targets a specific person. A freelancer shipping a brochure site or a documentation microsite, a developer who wants the CMS inside the same Git repository as the templates, or a team that has been burned by a database migration on a Friday afternoon. It is a poor fit for anyone whose content model is relational, where an article belongs to three categories and a dozen authors with permissions, because the flat-file model has to emulate those joins with folder structure and metadata.

## The mechanism: Markdown in, Twig out, cache in between

Content lives on disk. Grav reads the page tree, parses YAML front matter for metadata (title, taxonomy, template, published state), and converts the Markdown body using Parsedown, which the README lists as the Markdown and Markdown Extra renderer. Twig then renders the result against the theme's templates. Configuration is layered from YAML files, and the Symfony Cache component sits underneath as the performance layer, so the parsed and rendered output is not recomputed on every request.

The glue is a Pimple dependency injection container for services and the Symfony Event Dispatcher for plugin hooks, with Symfony Console backing the bin/grav and bin/gpm command line tools. Dynamic image manipulation comes from the Gregwar Image Library, which is why resizing a page image does not require a separate build step. The practical consequence of the cache layer: a cold cache after a deploy is slower than steady state, and a stale cache is the first thing to suspect when an edit does not appear.

## Installing Grav and publishing a first page

There are three documented routes. The ready-built package from the downloads page on getgrav.org needs only a PHP 8.3 or higher host with the required modules. Composer is the cleanest for a new project, and the README gives this command, which creates a project in the target directory:

```bash
composer create-project getgrav/grav ~/webroot/grav
```

Cloning from GitHub is the route for people who want the repository itself. Clone into your webroot, then run the Grav CLI installer from inside the directory, which pulls the plugin and theme dependencies:

```bash
cd ~/webroot
git clone https://github.com/getgrav/grav.git
cd ~/webroot/grav
bin/grav install
```

Adding functionality goes through the Grav Package Manager rather than manual downloads. Indexing lists what is available, and install takes a plugin or theme slug:

```bash
bin/gpm index
bin/gpm install <plugin/theme>
```

After that, a page is a Markdown file with YAML front matter in the user/pages tree. The README does not reproduce a page template, so the front matter keys to use are documented on learn.getgrav.org rather than in the repository README. What you should see after a successful install is a working site at your webroot, a populated user/ directory, and bin/grav and bin/gpm responding to commands.

## Environment overrides without committing secrets

The repository ships a .env.example at the top level, and it documents a layering order that is more careful than most PHP projects bother with. Grav loads .env before its configuration is compiled, so values behave like real environment variables. The layers stack: .env for base defaults safe to commit, .env.local for machine-specific overrides, .env.<environment> for per-environment values such as .env.production, and .env.<environment>.local for per-environment machine overrides. Real server-set environment variables always win over all of them.

The environment suffix is driven by GRAV_ENVIRONMENT, and when that is unset Grav falls back to hostname-based detection. Individual config values can be overridden without touching YAML by setting GRAV_CONFIG to a truthy value and then using GRAV_CONFIG__ keys, where double underscores map to dots:

```bash
GRAV_CONFIG=true
GRAV_CONFIG__system__cache__enabled=true
GRAV_CONFIG__plugins__email__mailer__smtp__user=postmaster@example.com
GRAV_CONFIG__plugins__email__mailer__smtp__password=super-secret
```

The file notes one sharp edge: variable names allow only letters, digits and underscores, so a plugin whose slug contains a hyphen cannot be named directly and needs a GRAV_CONFIG_ALIAS__ alias with the hyphenated path in the value. That is a real constraint, not a documentation gap.

## Where the flat-file model stops being the right answer

The failure mode is concurrency. Two editors saving the same page produce a last-write-wins file, and there is no transaction log to reconcile it. If your editorial process assumes simultaneous editing, Grav is the wrong tool and the mismatch will show up in lost work rather than in an error message.

Scale is the second boundary. Every page is a file the application must stat, parse and cache, and the cache is the thing keeping that affordable. The README presents Symfony Cache as the performance backend without quoting any numbers, and the repository contains no benchmark figures to cite, so treat any throughput expectation as something you measure on your own hardware. A site with tens of thousands of pages and heavy taxonomy filtering is a different engineering problem from a site with a few hundred.

Third, the upgrade path has a hard wall. The README is explicit that Grav 2.0 is a major release with a PHP 8.3+ baseline and that bin/gpm selfupgrade alone will not carry you across the 1.x to 2.x boundary; the migration guide at getgrav.org/migrate-to-2 has to be followed first. Within a major version selfupgrade handles point upgrades, and the README links separate in-place guides for 1.8, 1.7, 1.6 and pre-1.6. If you are on 1.x and cannot schedule a migration, that is a reason to stay put rather than upgrade casually.

## Grav compared with a database-backed CMS

The honest alternative for most teams evaluating Grav is WordPress, and the difference is not a feature list. WordPress keeps content in MySQL and renders it through PHP templates; Grav keeps content in Markdown files and renders it through Twig. That single choice propagates into everything else.

With WordPress, deployment means moving a database, and the database is where the query power lives: arbitrary taxonomy queries, custom post types joined across tables, and a mature multi-author permission model. With Grav, deployment means rsyncing a directory or checking out a branch, and the content is diffable in a pull request. You trade query flexibility for version control over content.

Plugins follow the same split. WordPress plugins can lean on the database; Grav plugins hook into the event dispatcher and the service container, and the README points to the Grav Package Manager for installing them. If your site is fundamentally a content store with complex retrieval, WordPress or another database-backed system will fight you less. If your site is a set of pages that a small team edits and deploys, Grav removes an entire moving part from the stack.

## Maintenance cost and the MIT licence

The repository is not archived, and the last push was on 2026-09-19, with releases 2.1.8 and 2.1.7 both dated the same day and 2.1.6 on 2026-09-15. That is a tight release cadence on the 2.1 line, and it is the concrete signal to weigh rather than any claim about project health.

Upgrades inside a major version are a single command, which keeps routine maintenance cheap:

```bash
bin/gpm selfupgrade
bin/gpm update
```

The first updates Grav itself, the second updates plugins and themes. The cost that does not disappear is the major-version migration described above, plus the ongoing need to keep PHP at or above the version the release requires. Grav is MIT licensed, which is permissive and imposes no copyleft obligation on your own code; the licence text is in LICENSE.txt at the repository root. That is a factual statement about the licence identifier, not legal advice, and anything involving redistribution or bundled third-party plugins should go to someone qualified to review it.

## Conclusion

Adopt Grav if your content is a few hundred Markdown pages, you control the server, and you want the whole site in Git instead of a database dump. Do not adopt it if editors need multi-user workflows with roles and audit trails, or if your content already lives in SQL and is queried from several applications. Before committing, verify that your host runs PHP 8.3 or higher with the required modules, and read getgrav.org/migrate-to-2 if you are coming from a 1.x install, because bin/gpm selfupgrade alone will not cross that boundary.

## FAQ

### Is Grav free to use?

Yes. The repository is licensed under MIT, and the licence text sits in LICENSE.txt at the root of the project. The README also points to OpenCollective pages for backers, supporters and sponsors, which is a donation channel rather than a paid tier.

### Is Grav a good CMS for beginners?

It depends on what you already know. The README states there is zero installation required and that extracting the ZIP archive leaves you running, which is a low barrier. Editing content means writing Markdown with YAML front matter, and customising output means Twig templates, so anyone comfortable with those will move faster than someone expecting a purely visual builder.

### What is Grav?

Grav is a file-based web platform that stores content as Markdown with YAML front matter and renders it through Twig templates. Its README lists Parsedown, the Symfony Cache component, Pimple and the Symfony Event Dispatcher and Console as the underlying technologies, and it ships a package manager for plugins and themes.

## Sources

- [getgrav/grav on GitHub](https://github.com/getgrav/grav)
- [License: MIT](https://github.com/getgrav/grav/blob/develop/LICENSE)
- [Project website](https://getgrav.org)
- [README](https://github.com/getgrav/grav/blob/develop/README.md)
- [Releases](https://github.com/getgrav/grav/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/getgrav-grav
