Manual Migration to Grav 2.0
For most sites, the Migrate to Grav 2.0 plugin is the right tool. It automates every step of the process and runs in a clean PHP environment outside of Grav 1.x.
There are setups where the wizard's assumptions do not fit:
- Multisite installs with shared user folders or per-site environments
- Complex symlink layouts (for example,
user/pages,user/data, or individual plugin folders symlinked into a deployment workflow) - Externally-mounted
user/folders (NFS, sshfs, Docker volume mounts) - Custom or private plugins that aren't on GPM and don't yet declare 2.0 compatibility
- Build pipelines where the site is generated by CI rather than edited in place
This page documents the same migration steps the plugin performs, expressed as a procedure you can run yourself. It assumes you have shell access and are comfortable with the standard Grav layout.
Important
The principle of the manual migration is the same as the plugin's: your live 1.x site stays untouched until the very last step. Everything is built up in a staged copy first.
Prerequisites
- Shell access to the server
- PHP 8.3 or newer available somewhere on the path (it does not have to be the default PHP for your live site yet)
- Write access to a directory next to your live webroot for the staged install
Tip
If you can't change the active PHP version on the live host yet, you can do the entire staging and verification on a separate machine that has PHP 8.3+, then rsync the verified stage back into place at promote time.
Step 1: Back Up the Live Site
Take a full backup of the live 1.x install before you change anything:
cd /path/to/grav-site
bin/grav backup
Or zip user/, backup/, and logs/ manually.
Keep this backup somewhere outside the webroot.
Step 2: Stage a Fresh Grav 2.0 Install
Download the latest stable Grav 2.0 build:
cd /path/to/grav-site
curl -L -o /tmp/grav-2.0.zip https://getgrav.org/download/core/grav-update/latest
Extract it next to your live install. The archive contains a top-level grav-admin/ folder, which we rename to grav-2/:
unzip /tmp/grav-2.0.zip -d /tmp/grav-2.0-extract
mv /tmp/grav-2.0-extract/grav-admin ./grav-2
At this point you have a pristine Grav 2.0 install at ./grav-2/ alongside your unchanged Grav 1.x at ./.
Note
If your webroot is served by Apache, the migrate-grav wizard adds an .htaccess rule to prevent the live site's rewrite rules from interfering with /grav-2/. If you hit redirect loops or asset 404s when previewing the stage, add a RewriteRule ^grav-2/ - [L] line near the top of your live .htaccess.
Step 3: Resolve Plugin and Theme Compatibility
This is the step that benefits the most from manual review. For each plugin and theme installed on your live site, you need to decide: install the 2.0 version, leave it disabled, or drop it entirely.
For each package in user/plugins/ and user/themes/:
Check the Compatibility Flag
Open the package's blueprints.yaml and look for a compatibility: block:
compatibility:
grav:
- '1.7'
- '2.0'
- Lists
2.0: the package is 2.0-ready. Install via GPM into the stage. - Lists
1.7only: the package is not 2.0-ready. Either skip it, install it disabled, or port it yourself (see below). - No
compatibility:block: fall back to inference: ifdependencies:declaresgrav >= 2.0, treat as 2.0-compatible; otherwise treat as 1.7-only.
See Plugin Compatibility for the full rules.
Install Compatible Packages Fresh
Do not copy plugin folders across from the 1.x install. Pull each compatible package fresh from GPM into the stage:
cd grav-2
bin/gpm install <plugin-slug>
For themes:
bin/gpm install -t <theme-slug>
This guarantees you get the 2.0-ready release rather than the 1.x copy. Many plugins ship significant changes (Composer dependencies, vendor folders, API integration) in their 2.0 versions that copying would miss.
Handle Custom / Private Plugins
For plugins that aren't on GPM:
- Copy the plugin folder from
user/plugins/<name>/tograv-2/user/plugins/<name>/. - Open its
blueprints.yamland add acompatibility:block declaring2.0once you've verified it works. - If the plugin uses Composer dependencies, run
composer install --no-devinside its folder against PHP 8.3+. - If the plugin has not been ported yet, see the AI-Assisted Development page for a fast path through the porting work.
Disable, Don't Delete
If you have plugins you want to keep on the filesystem but not load on 2.0 (for example, while you wait for an author to release a 2.0 version), copy the folder across and add a config entry to disable it:
grav-2/user/config/plugins/<plugin-name>.yaml:
enabled: false
This matches the behaviour of the wizard's "disable" policy.
Step 4: Copy User Accounts
Accounts copy across directly:
cp -a user/accounts/. grav-2/user/accounts/
Roles and permissions are stored inside each account file, so nothing else needs to change.
Step 5: Copy Content and Configuration
These directories are content-format-compatible between 1.7 and 2.0 and can be copied across as-is:
cp -a user/pages/. grav-2/user/pages/
cp -a user/config/. grav-2/user/config/
cp -a user/data/. grav-2/user/data/
cp -a user/env/. grav-2/user/env/
cp -a user/languages/. grav-2/user/languages/
Tip
Use cp -a (or rsync -a) rather than plain cp so timestamps, permissions, and symlinks are preserved. This matters in particular for any user/pages/ symlinks you have set up for multisite or shared content.
Review Deprecated Config Keys
A small number of system.yaml keys have been renamed or removed in 2.0. The migrate-grav wizard surfaces these with suggested replacements; doing it by hand means scanning system.yaml after the copy and consulting the Grav 2.0 release notes for any keys that are flagged.
Step 6: Verify the Staged Site
Open the stage in a browser. Depending on your webserver configuration, this is one of:
http://your-site.test/grav-2/(relative subdirectory)http://stage.your-site.test/(separate virtualhost pointing atgrav-2/)- A separate dev machine where you set up the stage entirely
Verify:
- The homepage and a representative sample of pages render correctly
- Admin login works with your existing credentials
- Each enabled plugin loads without errors (check
logs/grav.log) - The active theme renders correctly (Grav 2.0 ships with Quark 2 as the default theme; if your old theme has no 2.0 version yet, you may want to switch)
- Form submissions, login flows, and any plugin-specific functionality work as expected
Important
Do not skip this step. Once you promote, rolling back means restoring from the backup zip. Catching issues in the stage is much cheaper.
Step 7: Promote the Stage
This is the only step that modifies the live webroot. Do it during a low-traffic window if you can.
Take a Final Backup
cd /path/to/grav-site
zip -r migration-backup-$(date +%Y%m%d-%H%M%S).zip . -x 'grav-2/*' 'tmp/*' 'cache/*' 'backup/*'
mv migration-backup-*.zip /somewhere/safe/
Swap the Stage In
Move every top-level entry except grav-2/ aside, then promote the contents of grav-2/:
cd /path/to/grav-site
mkdir _old
shopt -s extglob dotglob
mv !(grav-2|_old|tmp|cache|backup) _old/ 2>/dev/null
mv grav-2/* grav-2/.* . 2>/dev/null
rmdir grav-2
Warning
Adapt the mv !(grav-2|_old|tmp|cache|backup) _old/ line to your shell. The above uses Bash's extended globbing: shopt -s extglob dotglob enables it and ensures dotfiles (.htaccess, .env) are moved. Test the pattern with echo first.
Clear Caches
bin/grav clear-cache --all
Verify
Reload the live site. If everything works, you're done. The _old/ directory is your immediate rollback path: rename it back into place if anything is broken.
Step 8: Clean Up
After a day or two of stable operation on 2.0, remove the rollback directory:
rm -rf _old
Keep the migration backup zip you created in Step 7 indefinitely.
When to Reach for the Plugin Instead
The manual procedure is straightforward but it's also a lot of small careful steps. If, partway through, you realise your setup is in fact close enough to a standard install that the wizard would handle it, the right move is to:
rm -rf grav-2/to discard the partial manual stage- Install the Migrate to Grav 2.0 plugin on the live 1.x site
- Let the wizard handle the rest
There's no penalty for switching paths. The live site has been untouched throughout.