I’ve been meaning to get my WordPress site into source control for years. Today I finally did it — managing WordPress with git took some wrong turns worth documenting.
Managing WordPress with git: what to version control
WordPress core — wp-admin/, wp-includes/, the root PHP files — doesn’t belong in git. It’s vendor code managed by WordPress auto-updates. What you actually own:
wp-content/themes/— your themewp-content/plugins/— your pluginswp-config.php— your configuration- Your web server config
The FSE template trap
This is the part nobody warns you about. With Full Site Editing themes (twentytwentyfour and newer), when you customize a template in the WordPress block editor, it saves to the database — not to a file. That means it’s invisible to git.
I discovered this the hard way. A “Join 900+ subscribers” call-to-action block appeared on my blog page after a theme update — I never put it there. WordPress had shipped a new default template that overwrote my DB-stored customization, injecting a sample CTA block. Without source control on the templates, there’s no diff, no history, no way to know what changed or when.
The block wasn’t in any theme file. It was in wp_posts with post_type='wp_template', tagged to the twentytwentyfour theme. Finding it required querying the database directly.
The fix: create your own theme with the templates as .html files. Theme file templates take precedence over DB-stored ones when the active theme matches. A theme update can no longer silently inject content into your pages — your file wins.
Building a standalone theme
I started with a child theme of twentytwentyfour, which is the right instinct but creates a dependency. Eventually converted it to standalone by pulling in just what I needed:
theme.json— color palette, typography, spacing tokensfunctions.php— font registrationassets/— the actual font filesparts/— header, footer, post-meta template partspatterns/— the hidden block patterns referenced by template parts
Two gotchas:
- Pattern slugs are namespaced to the theme (
twentytwentyfour/hidden-post-meta). When you copy them to a new theme, update the slug or they won’t resolve. - Template part blocks have a
"theme"attribute in their markup. Remove it so WordPress resolves them against the active theme rather than looking for a specific one.
Two caching layers to flush
When testing changes, I kept seeing stale pages despite updating the database. I forgot that I had set up two independent caches.
Both need to be flushed or temporarily disabled when debugging template changes. You can do this from the admin GUI (WP Super Cache under Settings, Redis under Tools) or from the command line:
# WP Super Cache (file-based)
find /var/www/wordpress/wp-content/cache -type f -delete
# Redis object cache
redis-cli FLUSHALL
The .gitignore
/wp-admin/
/wp-includes/
/wp-*.php
!/wp-config.php
wp-content/uploads/
wp-content/cache/
wp-content/upgrade/
Plugin auto-updates cause git drift
If you’re tracking wp-content/plugins/ in git — and the .gitignore above does — WordPress auto-updates will silently modify those files without any commit. Every plugin update is untracked drift between what’s on disk and what’s in the repo. You have three options:
- Disable auto-updates, update manually. Turn off automatic plugin updates in Settings → General (or per-plugin). Update deliberately, then
git addand commit. You get a clean history but you have to stay on top of updates. - Exclude plugins from git. Add
wp-content/plugins/to.gitignore. You lose the ability to reproduce the plugin state from the repo, but you stop tracking vendor code you don’t own. Treat plugins like WordPress core — managed separately, not versioned. - Accept the drift. Only practical if the repo is a snapshot for reference rather than a deploy artifact. Not recommended if you want to actually restore from it.
I went with option 1. Auto-updates are off; updates go through a deliberate commit so there’s a record of what changed and when.
End result
The repo has: one standalone theme with all templates as committed HTML files, all plugins, wp-config.php, and the Apache vhost config. WordPress core isn’t tracked — it updates itself. Any template change made through the block editor now has a file-backed equivalent in source control.
