Grav vs Astro: Flat-File CMS vs Content-Driven Framework, Hands-On
If you follow this blog you know the Astro connection already: this site is built on Astro, and we worked through the framework decision once before, in the Astro vs SvelteKit comparison, where we argued Astro for mostly static, content-heavy sites and SvelteKit for full-stack apps. Astro is the framework answer to content websites. Grav is the CMS answer, and the two now sit closer together than either side's marketing admits.
Grav 2.0, stable since 2026, is a flat-file CMS: Markdown and YAML in folders, no database, PHP under the hood, and, in a twist that should make you smile, a brand new admin SPA built on SvelteKit 5. So the CMS camp is literally building its editor with the framework we compared against Astro. This post is the hands-on comparison I wish I had found: what each tool actually is, how a blog post lives in each, how the content models differ, and the honest decision rule for when to reach for a PHP CMS and when to reach for a content framework.
The short version
- Grav is a CMS that happens to be fast. It runs PHP 8.3+, Symfony 7, and Twig 3. Your content is a folder of Markdown files with YAML front matter, and there is no database anywhere. Pages render per request with aggressive caching (the project quotes sub-100ms renders) and a real admin UI ships out of the box.
- Astro is a framework that happens to be content-driven. It is JavaScript, it compiles your site at build time into static HTML with zero JavaScript by default, and interactive pieces opt in as islands. Content lives in typed collections loaded from files, APIs, or any loader you write.
- Both are git-friendly, database-free, and MIT licensed. If you can structure it in folders, both are comfortable homes for a blog or a documentation site.
- The real differences are operational: who edits content (a non-developer needs Grav's admin; Astro needs a headless CMS or git), where rendering happens (PHP per request versus a build step), and what kind of interactivity you need (islands versus Twig server logic).
- Grav 2.0's admin is built on SvelteKit 5, the very framework we compared against Astro in the earlier post. Frameworks and CMSs keep borrowing each other's ideas, and this comparison is more interesting because of it.
- Grav CMS
2.0.19 (stable) - Grav Admin 2.0
SPA on SvelteKit 5, Vite, Tailwind 4 - Grav stack
PHP 8.3+, Symfony 7, Twig 3 - Astro
7.x (7.2 current banner on astro.build)
Checked 2026-08-19 against getgrav.org (2.0 features, Admin 2.0, API and MCP pages) and astro.build (7.2 banner, islands and content collections docs). Both projects ship fast; re-check versions before you rely on them.
What Grav actually is
Grav is a flat-file CMS from Trilby Media, the team formerly known as RocketTheme, and it has been around for roughly 12 years. The pitch is simple: no database, no SQL surface, your entire site is a directory of Markdown files with YAML front matter. You write a page, drop it in user/pages/, and Grav renders it through Twig templates.
The 2.0 line is the interesting one. It runs on PHP 8.3+, Symfony 7, and Twig 3, all on current stable releases, and it ships three things that change the calculus for developers: a first-party REST API under /api/v1, a built-in MCP server so AI agents get the same access as a human admin under the same permission model, and Admin 2.0, a single-page application built on SvelteKit 5, Vite, and Tailwind 4 with real-time collaborative editing, a CodeMirror-based Markdown editor, and Uppy-powered media. The admin is included, not an add-on.
Content structure: Grav organizes pages as folders under user/pages/, each folder holding a Markdown file plus optional media. YAML front matter sets the title, taxonomy, template, and anything your blueprints define. You get unlimited taxonomies, modular content, built-in multi-language support, an automatic image pipeline, and plugins for the rest (SimpleSearch ships for search; YetiSearch Pro is the premium option).
The honest framing: Grav is a CMS first. It gives non-developers a way to edit pages, gives developers Twig templates and a plugin API, and now gives machines a REST API and an MCP server. It wants to be the WordPress replacement that respects developers.
What Astro actually is
Astro starts from the opposite corner. It is a JavaScript web framework for content-driven websites: marketing pages, blogs, docs, e-commerce catalogs. Its islands architecture compiles your .astro components to static HTML at build time and only hydrates the interactive parts you mark with a client: directive, so the default page ships zero JavaScript.
The content story is content collections. You define a collection schema with Zod, drop Markdown, MDX, or JSON files into a content directory, and every entry is type-checked at build time. A getCollection() call in a page or endpoint gives you typed data, and loaders let you pull content from remote APIs or a CMS instead of local files. File-based routing, view transitions, built-in image optimization, middleware, and actions cover most of what a content site needs, and deploy adapters target Node, Cloudflare, Vercel, Netlify, Deno, and AWS with one-line configuration.
Astro also ships an official MCP server for AI tooling and positions itself as AI-ready, which is the same agent story Grav tells from the CMS side.
The honest framing: Astro is a framework first. There is no admin UI, no content editing surface, no user permissions. You bring the CMS (a headless one, or a static CMS that commits to git) or you accept that content is edited (or somehow automated) by developers. The performance story is real: Astro publishes Core Web Vitals data from HTTP Archive and the Chrome UX Report showing roughly 66% of real-world Astro sites passing, versus 48% for WordPress, 47% for Gatsby, 30% for Next.js, and 28% for Nuxt. That is vendor-published data, so treat it as direction rather than gospel.
The content model: YAML folders versus typed collections
This is the heart of the difference, and it decides which tool fits which team.
In Grav, a blog post is a file:
---
title: 'Hello from Grav'
date: '2026-08-19 09:00:00'
taxonomy:
tag: [grav]
---
# Hello from Grav
This page is a Markdown file with YAML front matter. There is no database row anywhere.The folder it lives in determines its route, its parent, and which Twig template renders it. Anything beyond the built-in fields goes into YAML, and blueprints define what editors see in the admin. It is flexible, and it is untyped: Grav validates against your blueprint definitions, not against a schema you compile.
In Astro, a blog post is also a file, but it belongs to a collection with a schema:
---
title: 'Hello from Astro'
description: 'A typed collection entry'
pubDate: 2026-08-19
tags: ['astro']
------
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
---
<article>
<h1>{post.data.title}</h1>
<slot />
</article>The Zod schema in src/content.config.ts defines exactly what front matter a post may carry. Misspell a field, add an unknown tag, or forget a required property and the build fails with a type error. That is a genuinely different guarantee than Grav's YAML, and for developer-driven content it catches real mistakes before they ship.
Templating and rendering: Twig versus .astro
Grav renders with Twig. Your theme is a set of Twig templates, and a page template looks like this:
<article>
<h1>{{ page.title }}</h1>
{{ page.content|raw }}
</article>Twig is mature, sandboxed, and safe by default, and if you have done any Symfony or Craft work it will feel familiar. Rendering happens per request on the PHP side, with caching layers (page, Twig, and asset caches) keeping it fast. That means Grav runs on any PHP host, including shared hosting, and dynamic features like search, forms, and per-visitor logic live naturally in the same runtime.
Astro templates are HTML with a frontmatter script that runs at build time:
---
const { title } = Astro.props;
---
<article>
<h1>{title}</h1>
</article>If you know HTML, you know most of .astro. The frontmatter runs on the server (at build for static output, at request time for SSR routes), and anything interactive is a component from your framework of choice (React, Vue, Svelte, Preact, Solid) hydrated on demand with a client: directive. Static output can live on a CDN or any static host, which is why Astro wins so many Core Web Vitals comparisons.
Hands-on: standing up both
The two fastest paths, side by side:
$ composer create-project getgrav/grav:^2.0 my-grav-site
$ cd my-grav-site
$ bin/grav install
$ bin/grav server
# Grav dev server: http://localhost:8000
# Admin 2.0 and the REST API are included out of the box$ npm create astro@latest my-astro-site
$ cd my-astro-site
$ npm run dev
# dev server: http://localhost:4321
$ npm run build
# static output in dist/With Grav you get a working site with an admin and an API in minutes, but it is a PHP application that needs a PHP host in production. With Astro you get a dev server and a static build, and you decide later whether it is static output on a CDN or SSR behind an adapter.
One deployment note worth keeping: we covered the Astro side of shipping to a VPS in the Woodpecker CI deployment post, and the same build-then-sync pattern applies to Grav. Since content is files, a CI job can pull, build (or just sync), and push to the server. Both tools treat git as the source of truth, which is exactly what you want from content in 2026.
The comparison table
| Feature | Grav 2.0 (flat-file CMS) | Astro 7 (content framework) |
|---|---|---|
| What it is | Flat-file CMS (PHP) | Content-driven web framework (JavaScript) |
| Content storage | Markdown + YAML front matter in user/pages/ | Content collections from files, APIs, or custom loaders |
| Content validation | Blueprint-defined YAML fields | Typed collections validated with Zod at build time |
| Templating | Twig 3 | .astro components, HTML-like |
| Admin UI | Included: Admin 2.0 SPA (SvelteKit 5) | None; bring a headless or static CMS |
| Headless and agents | First-party REST API + MCP server, same permission model | Static output or SSR adapters; dev-time MCP server |
| Database | None | None |
| Runtime | PHP 8.3+, per request with caching | Build-time static, or SSR via adapter |
| Deploy targets | Any PHP host, shared hosting, Docker | CDN/static hosts, Node, Cloudflare, Vercel, Netlify, Deno, AWS |
| License | MIT | MIT |
| Best for | Teams with non-developer editors | Developer-owned content sites, docs, marketing |
What it is
- Grav 2.0 (flat-file CMS)
- Flat-file CMS (PHP)
- Astro 7 (content framework)
- Content-driven web framework (JavaScript)
Content storage
- Grav 2.0 (flat-file CMS)
- Markdown + YAML front matter in user/pages/
- Astro 7 (content framework)
- Content collections from files, APIs, or custom loaders
Content validation
- Grav 2.0 (flat-file CMS)
- Blueprint-defined YAML fields
- Astro 7 (content framework)
- Typed collections validated with Zod at build time
Templating
- Grav 2.0 (flat-file CMS)
- Twig 3
- Astro 7 (content framework)
- .astro components, HTML-like
Admin UI
- Grav 2.0 (flat-file CMS)
- Included: Admin 2.0 SPA (SvelteKit 5)
- Astro 7 (content framework)
- None; bring a headless or static CMS
Headless and agents
- Grav 2.0 (flat-file CMS)
- First-party REST API + MCP server, same permission model
- Astro 7 (content framework)
- Static output or SSR adapters; dev-time MCP server
Database
- Grav 2.0 (flat-file CMS)
- None
- Astro 7 (content framework)
- None
Runtime
- Grav 2.0 (flat-file CMS)
- PHP 8.3+, per request with caching
- Astro 7 (content framework)
- Build-time static, or SSR via adapter
Deploy targets
- Grav 2.0 (flat-file CMS)
- Any PHP host, shared hosting, Docker
- Astro 7 (content framework)
- CDN/static hosts, Node, Cloudflare, Vercel, Netlify, Deno, AWS
License
- Grav 2.0 (flat-file CMS)
- MIT
- Astro 7 (content framework)
- MIT
Best for
- Grav 2.0 (flat-file CMS)
- Teams with non-developer editors
- Astro 7 (content framework)
- Developer-owned content sites, docs, marketing
Admin, editors, and non-developers
This is the fork in the road. Grav ships Admin 2.0: a real editing surface where a non-developer can log in, create a page, pick from your blueprints, upload media with Uppy, and publish. Revisions, real-time collaborative editing with named cursors, and a customizable dashboard are part of the story, with premium Revisions Pro for deeper history. If your client, your boss, or your content team needs to edit without touching git, Grav gives you that out of the box.
Astro gives you nothing out of the box. Content is files in a repo, so editing means either a developer, wiring up a headless CMS that Astro's loaders can pull from, or a static CMS that commits to git. That is not a flaw, it is the trade: Astro assumes the developer owns the content pipeline. The moment you add a headless CMS, you also add its API, its auth, and its billing to your stack, which is real complexity that Grav does not have.
The tip for teams that cannot pick: Grav is the pragmatic default when editing is part of the product. Astro plus a headless CMS is the choice when the site is code and the content is data.
API, headless, and agents
Both projects have clearly spent 2026 courting the same future.
Grav 2.0's first-party REST API is the foundation everything else sits on: the admin, the MCP server, your CI, and your agents all talk to the same /api/v1 endpoints under the same permission model. You can run Grav headless and render the frontend however you want, or monolithically with Twig, or both. The MCP server gives an AI agent the same operations a human admin has, with no sidecar.
Astro's agent story lives on the other side: the official Astro MCP server helps AI tools understand and work inside an Astro project during development, and the framework's stance is AI-ready workflows rather than content administration. If your need is an editor that can publish through an API, Grav wins. If your need is an agent that can scaffold and build a content site, Astro is the more natural seat.
Deployment and hosting
Grav is PHP, so production means a PHP host: shared hosting, a VPS with PHP-FPM, a Docker container, or Trilby Media's managed hosting. Pages render per request, so caching (Twig, page, and plugin caches) is the performance job, and the project quotes sub-100ms page renders with that caching warm. It is not static output, and it should not pretend to be.
Astro is a build step. npm run build produces dist/ that you can drop on any static host or CDN, or you add an adapter for Node, Cloudflare Workers, Vercel, Netlify, Deno, or AWS. Static output means no server to patch and no per-request cost; SSR means you accept a runtime but keep dynamic features. This is the same static-versus-SSR decision we walked through in the Astro vs SvelteKit post, and it applies here unchanged.
Which should you pick?
- Choose Grav when: non-developers edit content and need an admin, you want a CMS with plugins, blueprints, users, and permissions out of the box, you need a first-party API and MCP access for agents, your host is PHP-only or shared, or you want one tool that runs monolithic now and headless later.
- Choose Astro when: developers own the content pipeline, you want typed content collections and build-time validation, performance and zero-JavaScript defaults matter most, you want to deploy to a CDN or an edge runtime with an adapter, or you want to bring your own UI framework components as islands.
- Consider neither when: the site is a few static pages with no content workflow (plain HTML or a tiny static generator is less tooling), or you need a full application with sessions and complex state, where SvelteKit or a similar app framework is the honest answer, as we covered in the Astro vs SvelteKit post.
My honest take after building with both: Grav 2.0 is the surprise of the year for teams that need a CMS without a database and without WordPress baggage. Astro remains the best default when the site is developer-owned content. The two are not really competitors for the same slot, they are the CMS answer and the framework answer to the same question, and which one you need is decided by who edits the content, not by how fast either one renders.
Official sources
- Grav: https://getgrav.org/
- Grav 2.0 features: https://getgrav.org/features
- Grav 2.0 Admin (SvelteKit 5): https://getgrav.org/blog/grav-2-admin-next
- Grav API: https://getgrav.org/blog/grav-2-api
- Grav MCP server: https://getgrav.org/blog/grav-2-mcp-server
- Astro: https://astro.build/
- Astro islands: https://docs.astro.build/en/concepts/islands/
- Astro content collections: https://docs.astro.build/en/guides/content-collections/
- Our Astro vs SvelteKit post: https://systhoughts.com/posts/astro-vs-sveltekit-which-framework-should-you-choose
- Our Astro deployment with Woodpecker CI: https://systhoughts.com/posts/automating-astro-website-deployment-with-ci-woodpecker-and-ssh
Are you running Grav, Astro, or both? Which side did the content editors land on, and what did the content model decide for you? Drop it in the comments.
Until next time, keep your systems thoughtful.

No comments yet