Skip to content
HexCorp.io

HexCorp / Guides / Deploys & staging

How-to

Push WordPress staging to live without losing orders

Never copy the staging database over production: it deletes every order, customer, subscription renewal and form entry created since you cloned the site. Push code with git or rsync, push content as a reviewed list of specific posts, menus and settings, and leave the tables production writes to untouched. Here is the checklist, the WP-CLI version, and when a full copy is actually fine.

Never copy the staging database over production. It deletes every order, customer account, subscription renewal, stock change, comment and form entry created since the day you cloned the site, and the “push to live” button on most hosts does exactly that. The safe way has three lanes: push code with git or rsync, push content as a reviewed list of specific posts, menus and settings, and never push data the live site writes to (orders, users, sessions, logs). This guide gives you the checklist, the WP-CLI version for a small site, and the one case where a full copy is actually fine.

Why the push button eats orders

A WordPress site is one database. Pages, products, orders, customers, settings, plugin state and sessions all live in the same handful of tables. A host’s “push staging to production” copies the whole database (or all of it except a short exclusion list) and runs a search-replace on the domain. From the moment you cloned staging, production kept writing: every order is a row in wp_posts or wc_orders, every new customer a row in wp_users, every stock change a row in wp_postmeta. The copy replaces those tables with the staging versions, which do not have those rows. The orders are gone. Backups exist, but restoring a backup brings back the old content too, and now you are hand-merging.

Two more traps hide in the same operation. WordPress stores some settings as serialised PHP arrays that embed string lengths; a naive search-replace that changes a domain’s length corrupts them. And post IDs are assigned on insert, so a page created on staging as ID 412 may collide with an order that got ID 412 on production in the meantime. Any plugin that stored a reference to 412 now points at the wrong thing.

What changes on production while you work

Make this list for your own site before every deploy, because it is the list of things a full copy will destroy:

  • WooCommerce orders, refunds, order notes, and the stock levels they changed
  • Subscription renewals and payment tokens
  • User registrations, password changes and profile edits
  • Comments, reviews and form submissions stored in the database
  • Scheduled actions (Action Scheduler), cron state, transients and sessions
  • Logs, analytics plugins’ tables, and anything an integration wrote (CRM sync IDs, shipping labels)
  • Content edited directly on production by a client who “just fixed a typo”

That last one is the silent killer of agency deploys. Ask before you push, and diff the production copy of any post you are about to overwrite.

The three lanes

Code

Theme, child theme, custom plugins and mu-plugins go through version control. Commit on staging, pull on production (or rsync the directories). Never edit code on production, never copy wp-content wholesale, and exclude uploads, which is data, not code. Third-party plugins update themselves; keep the versions in a lock file or a note so staging and live match.

Content

Pages, posts, products, menus, widgets, theme options and plugin settings are the things you actually changed on staging. They have to be moved as objects, selected by hand, with their media, and with a stable identity so a second push updates the same object instead of creating a duplicate. This is the lane that has no good built-in tool, which is why people reach for the full copy.

Data

Orders, customers, sessions, logs and everything in the list above never move from staging to production. If staging needs realistic data, copy production down to staging, anonymised, as often as you like. The arrow only points one way.

The deploy checklist

  1. Freeze the change list. Write down every object you changed on staging: page titles, product SKUs, the menu, the three options in the theme customizer. If you cannot list it, you are not ready to push it. (10 minutes)
  2. Back up production. Database and uploads, and confirm you can restore it somewhere else. Not optional, even for a one-page change. (5 to 15 minutes depending on size)
  3. Diff production first. For every object on the list, open the production copy and check nobody edited it since you cloned. If they did, merge by hand before you push. (5 minutes)
  4. Deploy code via git pull or rsync, with uploads excluded. Clear the object cache and the page cache. (2 minutes)
  5. Put the site in maintenance mode only if the content push changes checkout. Most content pushes do not need it; a maintenance page costs you real orders. (1 minute)
  6. Push content as objects, in dependency order: media first, then the posts and products that reference it, then menus that reference the posts, then options. Match on a stable key (slug, SKU, option name), never on ID. (5 to 30 minutes by hand; seconds with a tool)
  7. Replace URLs inside pushed content only, serialisation-aware. Do not run a site-wide search-replace on production.
  8. Smoke-test the money paths: load the home page, a product, add to cart, reach checkout, and place a test order if the checkout changed. Check that today’s orders are still in the orders list. (5 minutes)
  9. Record the deploy: what moved, when, by whom, and the backup’s name. The next person to ask “what changed on Tuesday” will thank you.
  10. Refresh staging from production afterwards so the next cycle starts from the truth.

Doing it by hand with WP-CLI

For a small site with a handful of changed posts, WP-CLI gets you most of the way:

  • Export the specific posts from staging: wp export --post__in=412,418,431 --with_attachments. This writes a WXR file including the media those posts use.
  • Import on production with wp import file.xml --authors=skip. The importer matches existing posts by slug and type, which is close to stable identity, and creates the attachments it does not already have.
  • Options and menus do not export. Copy options by name: wp option get my_theme_options --format=json on staging, wp option update … --format=json on production. Rebuild menus by hand or script them with wp menu.
  • Run wp search-replace 'https://staging.example.com' 'https://example.com' only on the imported posts, using --include-columns=post_content and a --dry-run first.

This works, and it is what we did for years. Its limits are the reason the tool below exists: the importer cannot update a page that already exists when the slug changed, it cannot express “this option depends on that page’s ID”, it gives you no dry run, and nobody reviews a WXR file before importing it.

The patch approach

The model that holds up is the one software has used for decades: a patch. Staging computes the set of objects that differ from production, by stable identity (slug, SKU, option name) rather than ID, restricted to an allowlist of object types you have decided are content (pages, products, menus, named options) with hard exclusions for everything that is data (orders, users, sessions). The patch is a readable list: “update page Pricing, create product SKU-2201, set option theme_mods keys a, b, c”. Someone reviews it. Production runs a dry run and reports what would change. Then it applies in dependency order, with media first, and writes a changelog entry.

That is what StagePatch does. The free plugin builds the patch, enforces the allowlist and exclusions, keeps identity stable across environments, shows the plan, and applies it safely with WP-CLI or a patch bundle you carry over by hand. Pro pushes the batch from staging to live over a signed REST connection with the dry run shown inline, so the whole checklist above becomes: review the patch, click push, check the smoke test.

When a full database copy is fine

Before launch, when production has no orders and no users but you, copy away; it is the fastest route and nothing is lost. The same is true for a brochure site with no forms storing to the database, no comments and no users besides editors, as long as you check that list honestly. The moment a site takes an order, accepts a registration or stores a form entry, the push button becomes a delete button, and the three lanes are the only safe way to deploy.

The tool for this

Deploy as a reviewable patch

StagePatch is a staging deployment plugin for WordPress, made for agencies and developers who maintain client WordPress sites. Free + Pro from $79/yr

Free download

StagePatch v1.0.0

Where should we send the setup guide? The download starts right away; the email has the link again, the quick start and what Pro adds.

Skip, just download

GPL-licensed. Your email is stored on hexcorp.io and never sold or shared.

See StagePatch

Benton · Founder, HexCorp

18 years in marketing technology, most recently running it for a multi-location business. HexCorp builds the tools I kept wishing existed: tracking you can prove, deploys you can review, and plugins that respect the people who use them.