A Staging and Deployment Workflow for WordPress
How to set up local, staging and production environments for WordPress, what to version control, and how to handle the database that makes it awkward.

WordPress makes it very easy to edit files on the production server, which is why so many sites have no deployment process at all. It works right up until a change breaks the site and nobody knows what it replaced.
A workflow doesn't have to be elaborate. Here's one that's proportionate for most projects, and the WordPress-specific problem at the heart of it.
The three environments
- Local — on the developer's machine, where the actual work happens. Fast, disposable, and where WP_DEBUG stays on.
- Staging — a copy of production where changes are tested before release. It must match production's PHP version, plugin versions and configuration, or it proves nothing.
- Production — the live site. Nobody edits code here.
- Some teams add a fourth for client review separate from technical testing. Three is enough for most projects, and one more than many have.
The direction of flow
This is the rule that makes everything else coherent. Code moves up: local to staging to production. Content moves down: production to staging to local.
It follows from where each thing is authored. Code is written by developers on their machines. Content is written by editors on the live site. Any workflow that tries to move content upward ends up overwriting real content with test data, or losing a week of posts.
The consequence is that staging's database is a periodic copy of production's, and anything configured on staging has to be redone on production — or, better, expressed as code so it deploys.
What goes in version control
- The theme, in full.
- Your own plugins, including the site-specific plugin holding custom post types and business logic.
- A dependency manifest for third-party plugins, so their versions are pinned and reproducible.
- Configuration as code: anything you'd otherwise click through in the admin and forget.
- Build configuration, linter rules, and the deploy script.
- Not in version control: wp-content/uploads, wp-config.php with real credentials, WordPress core if you manage it through a dependency manager, and node_modules.
The database problem
This is what makes WordPress harder to deploy than most applications: content and configuration live in the same database. Posts sit alongside plugin settings, menu structures, widget configuration and theme options.
So you can't simply push the database from staging to production — you'd overwrite every post published since the last sync. And you can't simply not push it, because your new feature's settings are in there.
The practical answers are all about reducing what lives in the database. Define post types, taxonomies and fields in code rather than through UI plugins. Store settings in constants or configuration files where you can. Register menus and widget areas in code. What remains is content, which only flows downward, and a short list of things to configure by hand on release.
Refreshing staging
Staging drifts from production, and stale staging gives false confidence. Refresh it regularly — before any significant piece of work, at minimum.
- Copy the production database down, then search-and-replace the URLs. Use a tool that handles serialised data correctly; a plain SQL find-and-replace corrupts serialised arrays.
- Copy the uploads directory, or use a plugin that pulls media on demand so you don't move gigabytes.
- Immediately disable outbound email on staging, or a content sync will send real order emails to real customers. This is a genuinely common and embarrassing accident.
- Disable payment gateways, or put them in test mode.
- Noindex the whole staging site and put it behind HTTP authentication.
- Turn off any integration that writes to a live third-party system.
Deploying
The deploy itself should be one command or one button, and it should do the same thing every time.
- Pull the code at the target commit.
- Install dependencies at pinned versions.
- Run the asset build.
- Clear caches — object cache, page cache, CDN.
- Run any database migrations your code needs.
- Deploy to staging first, always, even for a one-line change.
- Take a backup immediately before the production deploy, so rollback is a restore rather than an investigation.
Handling plugin updates
Plugin updates are the awkward case, because the natural way to do them is clicking Update in the admin — which changes files on production outside your deployment process.
The clean approach is to manage plugins as dependencies: update the version in the manifest locally, test, commit, and deploy. Production's admin update button is then disabled, with DISALLOW_FILE_MODS.
That's proportionate for a large site with a team. For a small site, a pragmatic middle ground is to update on staging first, verify, then update on production through the admin, and accept that plugin versions aren't in version control. The important part is the staging step, not the mechanism.
Configuration per environment
- Keep environment-specific values — database credentials, API keys, the site URL, debug flags — out of version control and in environment variables or an untracked local config file.
- Define an environment constant so code can behave differently: debug output on locally, error logging only in production.
- Make the environment visible in the admin. A coloured admin bar saying STAGING prevents a whole category of mistake.
- Keep a checklist of things that must be set manually on a new environment, because there are always a few.
Proportionate for a small site
Not every site needs a full pipeline. For a small brochure site, the minimum worth having is: the theme in a Git repository, a staging site your host provides, updates tested there before production, and a backup before each change.
That's an afternoon to set up and it removes the two worst failure modes — an untested update breaking the live site, and nobody knowing what the code used to be.
Scale up from there when the site or the team justifies it, not before.
Frequently asked questions
Should I put the whole WordPress install in Git?
Usually not. Version the theme, your own plugins and a dependency manifest for third-party ones; leave core and uploads out. Committing uploads makes the repository enormous and it's content, not code — content lives in backups and flows downward from production.
How do I move the database between WordPress environments?
Downward only — production to staging to local — with a URL search-and-replace using a tool that handles serialised data correctly. A plain SQL find-and-replace corrupts serialised arrays. And disable outbound email on staging immediately after, or you'll send real emails to real customers.
How do I handle plugin updates in a Git-based workflow?
Manage plugins as pinned dependencies: update the version locally, test, commit and deploy, with the admin's update button disabled via DISALLOW_FILE_MODS. For a smaller site, updating on staging first and then on production through the admin is a reasonable compromise — the staging step is the part that matters.
Do I really need a staging site?
If anything on the site matters, yes. It's where you find out that an update breaks the checkout, before customers do. Most managed hosts include one, and the cost of not having one is that you either update recklessly or stop updating — and both of those end badly.
Why does my staging site send emails to real customers?
Because copying the production database brings real customer records with it, and nothing on the staging site knows it isn't production. Disable outbound email as the first thing you do after any database sync, and put payment gateways in test mode at the same time.
Topics
- WordPress staging
- WordPress deployment
- WordPress git workflow
- WordPress environments