If you ship a Grav plugin or theme, the new compatibility flag is one of the most important things to know about for Grav 2.0, and it's also one of the easiest 2.0 updates you can make. It's a one-block addition to your blueprints.yaml that tells Grav exactly which major versions you've tested against. The Migrate to Grav 2.0 wizard reads it when bringing existing 1.x sites across, GPM reads it when installing or updating plugins on a 2.0 site, the website uses it for plugin and theme listings, and the Learn site has the full reference at Plugin Compatibility.
For the past decade we haven't really needed something like this. Everything was Grav 1.7, dependencies were straightforward, and the existing dependencies: field told the system everything it needed to know. With 2.0 that's no longer enough, so I want to walk through the new flag once, properly: why it exists, what it actually does, what the fallback inference rules look like when you haven't set it yet, and what you need to do as a developer.
Why We Added This
Grav blueprints have always had a dependencies: field, and you've probably been writing it for years:
dependencies:
- { name: grav, version: '>=1.7.0' }
That sentence reads as "needs Grav 1.7 or later". It's a minimum requirement. It says nothing about whether you've tested on 2.0, nothing about whether your plugin still works on the latest 1.7.x, and nothing about whether you've actively decided not to support a particular line.
For most of Grav's life that didn't matter, because most of Grav's life was a single major version. With Grav 2.0 we now have two active majors in the wild, the underlying library stack has moved (latest Symfony, latest Twig, PHP 8.3 as the minimum), and we need a clear, explicit signal from plugin authors about what works where.
The new compatibility: block is that signal. It's a deliberate, developer-asserted statement of "I have tested my plugin on these versions and it works."
The Compatibility Flag
Syntax
The flag goes in your plugin or theme blueprints.yaml, alongside the existing fields:
name: My Plugin
slug: my-plugin
type: plugin
version: 2.1.0
description: A great plugin
dependencies:
- { name: grav, version: '>=1.7.0' }
compatibility:
grav:
- '1.7'
- '2.0'
A few details worth calling out:
- The flag uses major.minor version identifiers (
1.7,2.0) only. Patch versions don't appear here. Compatibility is declared at the line level, not per patch release. - It's a list, so a plugin tested on both 1.7 and 2.0 lists both. A 2.0-only plugin lists only
'2.0'. - The flag works identically for plugins and for themes. Same key, same structure.
- It lives next to (not instead of) your existing
dependencies:block. They do different jobs and they coexist.
Key Principles
A few things to keep in mind about how this is meant to work:
- Developer-asserted. You do the testing. The flag is your declaration that the plugin behaves correctly on the listed versions, full stop. There's no automated verifier doing this for you.
- Explicit over implicit. Setting the flag is a deliberate act. It signals that you've done the work, and that's the entire point.
- Complementary to dependencies.
dependencies:is the runtime gate ("you must have at least this").compatibility:is the testing statement ("I've verified these"). Both can be true at the same time, and usually are. - Patch-version stable. You don't need to update the flag for every 2.0.x release. It's the major.minor line you're declaring against, and we're treating 2.x patches as backwards compatible by definition.
How Grav Uses the Flag
The flag isn't just a label that sits in a file. Three different parts of the system read it, and you should know what each one does.
During the Migration to 2.0
This is by far the most common reading of the flag right now. The Migrate to Grav 2.0 plugin (covered end-to-end in the migration post) runs every installed plugin through the compatibility check during its Step 2 wizard pass. Plugins that declare 2.0 compatibility are kept and updated to their latest 2.0-ready release via GPM. Plugins that don't are routed through the plugin policy the user has chosen: Skip removes them from the staged install entirely, Disable keeps the files in place but writes a config entry that disables the plugin in 2.0.
The user also picks between strict and permissive compatibility modes. Strict trusts the flags as declared. Permissive promotes inferred-incompatibles to "compatible" so the user can test them on the staged 2.0 site even if their authors haven't formally flagged them yet. Plugins that have been explicitly marked as incompatible always block, in either mode.
The whole thing happens in a fresh, side-by-side staged install, so the user's live 1.x site is never touched. They run the wizard, pick a policy, click through, and either promote when they're happy or reset and try again.
During GPM Installs on a 2.0 Site
Once a site is running Grav 2.0, GPM reads the flag whenever a plugin or theme is installed or updated. A package that doesn't list 2.0 in its compatibility block won't be installed cleanly on a 2.0 site, and the user is told why. This is the second big reason to set the flag: even users who never run the migration wizard will hit your plugin via GPM eventually, and the flag is what tells GPM your plugin belongs on their site.
On the Grav Website
The plugin and theme listings on getgrav.org will display the compatibility data as version badges, and users will be able to filter by Grav version. If a user is running 2.0, they can see at a glance which plugins are flagged for it. This is also where unflagged plugins start to look conspicuous, which is a gentle nudge to set the flag once you've done the testing.
Inference Rules When No Flag Is Set
Not every plugin in the wild has the new flag yet. We can't break those plugins, but we also can't pretend everything is 2.0 compatible. So Grav has a small, deliberately conservative set of fallback rules that infer compatibility from the existing dependencies: field when no compatibility: block is present:
| Scenario | Inferred Compatibility |
|---|---|
No compatibility: block, and the grav dependency is < 2.0 (e.g. >=1.6.0, >=1.7.0, ~1.7) |
['1.7'] only |
No compatibility: block, and the grav dependency is >= 2.0 (e.g. >=2.0.0) |
['2.0'] only |
Explicit compatibility: block set |
The flag wins, every time, no inference |
The rule is simple in spirit: if we don't know, we assume the safer thing. A plugin that requires Grav 1.7 or later but doesn't say anything about 2.0 is treated as 1.7 only, because the author has not declared the work has been done. This protects users from accidentally upgrading into a half-broken site.
The flag always overrides inference. If you set a compatibility: block, the inference logic doesn't run. That's exactly what you want: a confident, tested declaration is always more useful than the system guessing.
What You Need to Do
The good news is that for most plugins this is a short, mostly mechanical job.
Step 1: Test on Grav 2.0
Stand up a fresh Grav 2.0 site running PHP 8.3+. Install your plugin. Enable it. Click through its functionality. The Learn site has a checklist of what to verify and it pays to work through it end-to-end:
- Install and enable cleanly with no errors.
- Core functionality (whatever your plugin actually does) works as expected.
- No PHP deprecation warnings or notices in the logs on the supported PHP versions.
- Composer dependencies line up with the Grav 2.0 stack. Watch in particular for
psr/logandmonolog, which moved with the upgrade. - If your plugin has any admin presence, the configuration page and any custom UI render correctly in Admin Next.
If anything looks off, fix it before declaring compatibility. The whole point of the flag is that it's load-bearing, and users will trust it.
Step 2: Add the Flag to Your Blueprint
Open blueprints.yaml in your plugin or theme and drop in the block. Three common variations:
# Plugin tested and working on both 1.7 and 2.0
compatibility:
grav:
- '1.7'
- '2.0'
# Plugin that's 2.0 only (e.g. uses APIs that don't exist on 1.7)
compatibility:
grav:
- '2.0'
# Plugin staying on 1.7 only (deprecated, or you haven't done the 2.0 work yet)
compatibility:
grav:
- '1.7'
That's the entire change. For a lot of plugins this is the whole 2.0 update.
Step 3: Release an Update
Bump your plugin version, ship the blueprint change to GitHub, and push the release through GPM. From the moment that update lands, your users on both 1.7 and 2.0 will see the explicit declaration and benefit from it. The migration wizard will pull your plugin into staged 2.0 sites without forcing the user into the skip-or-disable policy, GPM will install it cleanly on a fresh 2.0 site, and the website badges will light up.
Core Team Plugins
For the plugins maintained by the Grav Team we're going through every one and adding the flag. The vast majority will end up flagged for both 1.7 and 2.0, because most of them already work on both. A small number that we've decided not to carry forward will be flagged as 1.7 only and clearly marked as deprecated. These also serve as worked examples if you want to see real compatibility: blocks in shipping blueprints, alongside the free, openly available git-sync and license-manager plugins covered in the previous post in this series.
After Migrating
If you're a site administrator on the receiving end of all this, the post-migration flow is straightforward and worth knowing. Once your staged 2.0 site is promoted and live:
- Run
bin/gpm update(or the Admin equivalent) to pull in any plugin releases that have been flagged for 2.0 since you migrated. - Re-enable any plugins that the wizard set to Disable under your plugin policy, one at a time, and verify them as you go.
- If you hit a plugin that still hasn't been updated, contact the author and point them at this post.
Don't re-enable a plugin that hasn't been flagged for 2.0 unless you've tested it yourself. The flag exists for a reason, and silently bypassing it is the fastest way to find out the hard way that something doesn't work.
FAQ
Does the compatibility flag replace dependencies:?
No. They do different jobs. dependencies: is the runtime gate, and it expresses minimum version requirements. compatibility: is your testing declaration, and it lists the versions you've verified. Both belong in your blueprint.
What happens if I never add the flag?
Grav will fall back to inference based on your dependencies: field. For most plugins that means you'll be treated as 1.7 only, which means the migration wizard will route your plugin through the user's skip-or-disable policy and GPM will refuse to install it cleanly on a 2.0 site. Adding the flag is the simplest possible fix.
Can I flag compatibility without changing any code?
Yes, and that's deliberate. If your plugin already works on 2.0 (and many do), all you need to do is add the compatibility: block, bump the version, and release. No code changes required.
What about minor versions like 2.1 or 2.2?
Don't worry about them. The flag uses major.minor identifiers (1.7, 2.0), and we're treating 2.x patch and minor releases as backwards compatible. Your '2.0' flag covers the entire 2.x line.
Is there a way to override the block during the migration?
Yes, and that's exactly what permissive mode is for in the migration wizard. Strict mode trusts the flags as declared and applies your skip-or-disable policy to anything that isn't 2.0 compatible. Permissive mode promotes inferred-incompatible plugins to "compatible" so they're carried into the staged 2.0 site, which is the right setting if you want to test whether your plugins actually work on 2.0 rather than waiting on their authors to formally flag them. Plugins that have been explicitly marked as incompatible always block, in either mode.
Quick Links and Help
- The full reference for this on the Learn site: Plugin Compatibility.
- The previous post in the developer series: Grav 2.0 for Plugin Developers: Overview.
- The next post: API Integration for Plugins.
- And as always, Discord is the fastest way to get a hand from the team and the broader community if you hit something unexpected.
If you only do one thing this week, add the flag. It takes five minutes per plugin, it materially improves the migration experience for everyone running your work, and it gets your plugin onto the right side of the badge filters on the website.
Andy
Next in the developer series: Grav 2.0 Developer Guide: API Integration for Plugins