You shipped a custom WordPress theme. The client logs in, tweaks the header color, adds a widget, and maybe even edits a template file straight from the Appearance Editor. Meanwhile, you’re pushing structural changes from your local Git branch. What you get: merge conflicts, overwritten work, and a production site that drifts further from your canonical codebase every single week.
This isn’t a discipline problem. It’s a systems problem. Clients will always need to make small, immediate changes. Your job is to build a version-control workflow that absorbs that reality without breaking the theme’s integrity. Here’s the exact stack and strategy I use to keep client-edited themes under Git control—without locking them out of the tools they expect.
Why Standard Git + WordPress Theme Workflows Break
In a typical software project, the production environment is a read-only mirror of the repository. WordPress flips that assumption. The theme directory is live, writable, and exposed to both the WordPress admin UI and FTP-based editing if the client has hosting access. A standard git push to production will either overwrite client changes or fail because of uncommitted modifications on the server.
The core tension sits between two types of change:
- Structural changes — PHP logic, template architecture, custom post type registrations, and CSS framework updates. These come from you, the developer, and belong in version control.
- Cosmetic and content-adjacent changes — Color tweaks, font choices, widget placements, and minor template text edits. These come from the client and often happen through the Customizer, block editor settings, or direct file edits.
If you treat every production file as sacred, you’ll spend hours manually reconciling diffs. If you lock the client out of the theme editor entirely, you’ll get support tickets asking why they can’t change their own site. The solution is a deliberate separation of concerns baked into the theme architecture and deployment pipeline.
Architect the Theme for Safe Client Edits
Before touching Git, you need a theme structure that limits where client edits can land. This isn’t about restricting freedom—it’s about defining clear boundaries so both sides know which files are “developer territory” and which are “client territory.”
Use a Child Theme for Client-Exposed Files
Ship your core theme as a parent. Give the client a child theme that inherits everything but overrides only specific files: style.css, functions.php, and perhaps a handful of template parts. The parent theme stays under strict Git control. The child theme becomes the client’s sandbox.
This works because WordPress’s template hierarchy checks the child theme first. If the client edits header.php in the child theme, that version loads. Your parent header.php remains untouched. When you push an update to the parent, the child’s override still takes precedence—but only for the files the client actually modified. Everything else pulls from your updated parent.
For CSS, encourage the client to use the Customizer’s Additional CSS panel or a child theme style.css that enqueues after the parent. Never let them edit the parent’s main stylesheet directly. A single misplaced edit there can cascade into layout breaks that are a pain to trace.
Lock Down Template Editing in Production
WordPress 5.9+ allows template editing through the Site Editor for block themes. For classic or hybrid themes, the Theme File Editor under Appearance remains a tempting trap. Disable it. Add this to your parent theme’s functions.php or a must-use plugin:
define( 'DISALLOW_FILE_EDIT', true );
This removes the editor from the admin menu entirely. Clients can still use the Customizer, widgets, and block settings—just not raw PHP files. If they need template-level changes, they go through the child theme or a controlled process you define.
Set Up a Git Repository That Respects Production Reality
Your local Git repo is the source of truth for the parent theme. The production server is a hybrid: it contains your deployed parent theme plus the client’s child theme and any database-stored settings. The goal is to version-control what you own while tracking what the client changes.
Repository Structure
I recommend a monorepo for the project, not just the theme. This lets you version-control custom plugins, mu-plugins, and even deployment scripts alongside the theme. A typical layout:
project-root/
├── themes/
│ ├── parent-theme/ ← Your Git-managed code
│ └── child-theme/ ← Client sandbox (tracked but not deployed from Git)
├── plugins/
├── mu-plugins/
└── deploy/
The child theme lives in the repo for reference and local development, but you never push it to production from your machine. Instead, production’s child theme is treated as a client-owned artifact. You pull it down periodically to audit and commit changes back to the repo for history.
Gitignore Strategy
Your .gitignore should exclude anything the client or WordPress core can modify on the server. For the parent theme, that’s minimal—maybe just node_modules and build artifacts. For the child theme, you might ignore everything except a baseline you initially provisioned. The key is to never let a git pull on production overwrite client work.
On the production server, the parent theme directory is a Git working copy, but it’s set to a detached HEAD or a specific tag. You deploy via git fetch and git checkout <tag>, not git pull. This avoids merge conflicts with any stray files. The child theme directory is not a Git working copy at all—it’s just a folder you sync manually or via a controlled script.
Deployment Pipeline That Preserves Client Edits
Pushing code to a live site where files can change independently requires a one-way sync, not a bidirectional merge. Here’s the deployment sequence I use:
- Tag a release in your local repo:
git tag v1.2.0and push the tag. - On production, fetch tags:
git fetch --tags. - Checkout the tag for the parent theme directory:
git checkout v1.2.0. This updates only tracked files and leaves untracked or ignored files alone. - Run a post-deploy script that compares the production child theme against your repo’s child-theme baseline. If the client added custom templates or CSS, the script logs the differences and optionally commits them to a separate branch in your repo for auditing.
This approach means your parent theme updates are atomic and conflict-free. The client’s child theme modifications survive because they live outside the Git working copy’s tracked scope. You get a clean audit trail of what the client changed, and you can merge those changes back into the development branch if they’re worth keeping permanently.
Handling Database-Stored Theme Modifications
Not all client edits land in files. The Customizer saves settings—colors, typography, layout options—to the wp_options table under theme_mods_<theme-slug>. These are part of the theme’s runtime configuration but exist entirely outside the file system. Version-controlling them requires a different tactic.
Export Theme Mods as JSON
Write a small WP-CLI script or a custom plugin that exports theme mods to a JSON file on demand. Run it before each deployment and commit the JSON to your repo as a snapshot of the client’s Customizer state. If a deployment accidentally resets these settings, you can re-import them with a companion WP-CLI command.
This also gives you a diffable artifact. When the client says “the site looks different after your update,” you can compare the pre- and post-deployment JSON to see if a Customizer setting was overwritten or if a CSS specificity change in your update caused a visual shift.
Use Theme Mods Sparingly in the Parent Theme
If your parent theme stores layout-critical values in theme mods, a client can break the design by changing a number field from 4 to 40. Reserve theme mods for truly user-facing options—logo, accent color, typography scale—and validate them aggressively in the theme’s output. For structural settings like grid breakpoints or container widths, hardcode them in the parent theme or pull from a controlled options page that clients can’t access.
Child Theme Versioning and Client Collaboration
The child theme is the client’s playground, but it still needs version control for your sanity. I treat the production child theme as a separate Git repository—or at least a separate branch—that the client never touches directly. Instead, I pull a copy of their live child theme into my local environment periodically, review the changes, and commit them with clear messages like “Client added custom header template 2024-03-15.”
This gives you a full history of client edits without exposing them to Git. If they break something, you can diff against the last known-good state and revert selectively. It also means you can test your parent theme updates against their actual child theme overrides before deploying, catching conflicts in a staging environment rather than on the live site.
Automate the Pull with a Cron Job
Set up a weekly cron on the production server that zips the child theme and pushes it to a private cloud storage bucket or a dedicated Git branch via a deploy key. You pull that snapshot into your local environment, review the diff, and decide what to keep. This keeps the audit trail current without requiring manual FTP downloads every time you need to check on client changes.
When the Client Edits Parent Theme Files Anyway
Despite your best architecture, a client with admin access might still edit a parent theme file directly—especially if they have hosting-level access and use FTP. You need a detection mechanism, not a prevention mechanism, because prevention at the file-system level on shared hosting is fragile.
Integrity Checking with Checksums
After each deployment, generate a checksum manifest of all parent theme files. Store it outside the theme directory. On a regular cron job, compare the current files against the manifest. If a mismatch is found, log it and optionally email you. This turns a silent overwrite into a detectable event.
You can also use Git’s own integrity checking on the production parent theme directory. Since the directory is a Git working copy, git status will show modified tracked files. A simple script that runs git status --porcelain and alerts on unexpected changes gives you near-real-time visibility into client tampering.
Recovering from a Client Edit That Broke the Site
Even with checksums and alerts, you’ll eventually get the panicked email: “I changed something and now the site is down.” Your recovery process should be fast and not rely on sifting through Git history while the client watches.
First, if the parent theme is the problem, git checkout <last-good-tag> in the parent theme directory restores it instantly. The child theme is trickier because it’s not under Git control on the server. Keep a known-good backup of the child theme in a separate directory on the server—just a dated copy of the entire child theme folder from the last audit. A simple shell script can swap the broken child theme with the backup in seconds.
For Customizer-related breakage, the WP-CLI command wp option get theme_mods_<child-slug> retrieves the current settings. You can compare that to your pre-deployment JSON snapshot and selectively reset individual mods without wiping everything.
FAQ
Should I completely disable the WordPress file editor?
Yes, for the parent theme. Add define( 'DISALLOW_FILE_EDIT', true ); to your wp-config.php or a must-use plugin. This removes the Appearance → Theme File Editor and Plugins → Plugin File Editor menus. It doesn’t affect the Customizer, block editor, or media uploads, so clients can still manage visual settings and content. If they need to edit template files, they should do it through a child theme you’ve set up for them.
How do I handle version control when the client uses a page builder?
Page builders like Elementor or Beaver Builder store most design data in the database, not theme files. Your theme’s job is to provide a clean canvas and well-structured hooks. Version-control the parent theme as usual. For the builder data, use a migration plugin or WP-CLI to export builder settings and layouts to JSON files, then commit those to your repo as design snapshots. This gives you a history of page-level changes without touching the live database.
Can I use a staging site to let clients preview theme updates before they go live?
Absolutely, and it’s a practice I recommend. Push your parent theme update to a staging environment first, then sync the client’s child theme and Customizer settings from production to staging. Let the client review the staging site. Once approved, you deploy the parent theme tag to production and re-import the Customizer settings if needed. This catches conflicts between your new code and their customizations before they affect the live site. For more on staging workflows, see What to Fix First When a New WordPress Site Says Nothing Found for related debugging patterns.

Building a Client-Ready Theme Update Dashboard
For clients who want more control without direct file access, build a simple theme update interface into the WordPress admin. This isn’t about letting them edit code—it’s about giving them a safe way to trigger updates and see what changed.
Create a custom admin page that displays the current parent theme version, the latest available version from your Git tags, and a changelog pulled from your repository’s release notes. Add a one-click update button that runs your deployment script behind the scenes. This gives the client agency without exposing them to Git or the file system.
Under the hood, the button triggers a server-side script that fetches the latest tag, checks it out in the parent theme directory, and logs the update. If the child theme has conflicts, the script can pause and notify you rather than breaking the site. This turns theme updates into a managed service rather than a surprise.
Long-Term Maintenance: When the Client’s Child Theme Becomes the De Facto Theme
Over years, a client’s child theme can accumulate so many overrides that it essentially becomes the primary theme. At that point, the parent-child relationship is more about update safety than code inheritance. Your version-control strategy needs to adapt.
Consider promoting the child theme to a standalone theme in your repo, with the original parent becoming a library or framework that you version separately. This flips the dependency: the client’s theme imports your framework as a Composer package or Git submodule, and updates are explicit version bumps they approve. It’s more work to set up, but for long-lived sites with heavy customization, it eliminates the tension entirely.

Summary Workflow Checklist
Here’s the end-to-end process I use on every client project where the client has production access:
- Ship a parent theme with a pre-built child theme skeleton.
- Disable the file editor in wp-config.
- Deploy parent theme tags via
git checkout, nevergit pull. - Export theme mods as JSON before each deployment.
- Run integrity checks on parent theme files weekly.
- Pull the production child theme into a local audit branch regularly.
- Provide a one-click update dashboard for non-technical clients.
This stack keeps your Git history clean, your client empowered, and your production theme stable—even when someone edits a file at 11 p.m. on a Friday.

FAQ
What if the client insists on editing the parent theme directly?
Explain the risks in terms they care about: future updates will overwrite their changes, and security patches will be delayed because you can’t safely deploy. Then offer a compromise: move their desired edits into the child theme for them. Most clients agree once they understand the cost of maintaining a forked parent theme. If they still refuse, set up the integrity monitoring and treat the parent theme as a fork in your repo, merging their changes into a client-specific branch.
How do I version-control block theme templates when clients use the Site Editor?
Block theme templates are stored as HTML files in the theme’s templates and parts directories. When a client edits them in the Site Editor, WordPress saves the modified templates to the database, not the file system. Your file-based templates remain untouched. To version-control these edits, export the database-saved templates using WP-CLI or a custom script, convert them to HTML files, and commit them to a client-templates directory in your repo. This gives you a history of their Site Editor changes without touching your canonical template files.
Is it safe to use Git on a production server with shared hosting?
Yes, if you limit Git to the parent theme directory and use a detached HEAD or tag checkouts. Most shared hosts allow shell access or Git via SSH. If not, you can deploy via a script that downloads a tarball of the tagged release from your Git host (GitHub, GitLab, Bitbucket) and extracts it to the theme directory. The key is to never run git pull on a directory where uncommitted changes might exist. Always use a clean checkout of a specific tag.