Most WooCommerce HPOS (High-Performance Order Storage) migration problems have one of three causes: a plugin that isn't compatible with HPOS, custom code that still reads or writes orders through get_post_meta() and the posts tables, or a sync between the old and new tables that never finished. The fix follows the same routine every time. If live orders are affected, switch storage back to the WordPress posts tables. Then copy the site to staging, find the code that goes around WooCommerce's order API, sync and verify with WP-CLI, and switch again with compatibility mode still on.
As long as both sets of tables are in sync, you don't lose order data. You can switch back and forth without taking the store offline.
What is HPOS and why does switching break things?
HPOS moves WooCommerce orders out of the general WordPress wp_posts and wp_postmeta tables into tables built just for orders. Switching breaks things when plugins or custom code still use the old tables. WooCommerce has made HPOS the default for new installations since version 8.2, but older stores have to switch by hand. On a busy store the gain is real. Cloudways cites analysis of WooCommerce.com where order metadata made up to 97% of wp_postmeta rows, and that bloat slows down the orders screen and every order query.
The catch is that a lot of WooCommerce code was written years ago against the posts tables. Once HPOS is in charge, WooCommerce's own extension recipe book warns that "directly reading from these WordPress tables may mean reading an outdated order". Writing to them can mean updating a copy nobody reads. Almost every symptom below traces back to that.
What are common WooCommerce HPOS migration problems?
The six I'd expect to see are a greyed-out HPOS option, a sync that never reaches zero, blank custom order fields, a missing meta box, wrong order counts and a cleanup error. Here's the likely cause of each and where I'd look first.
| Symptom | Likely cause | Where to look first |
|---|---|---|
| The "WooCommerce orders tables" option is greyed out | An active plugin is flagged as incompatible | The list of incompatible plugins on WooCommerce > Settings > Advanced > Features |
| "Sync X pending orders" never reaches zero | Background jobs stalled, a very large store, or code writing to the old tables after every sync | wp wc hpos count_unmigrated and Tools > Scheduled Actions |
| Custom order fields blank in admin, emails or invoices | Custom code using get_post_meta() or update_post_meta() on orders | Your theme's functions.php and any custom plugins |
| A meta box has vanished from the order edit screen | The box is registered only for the old shop_order screen | add_meta_box() calls in custom code |
| Dashboard widgets, reports or the REST API show wrong counts | A reporting tool or query reading the posts tables, or a known WooCommerce issue with sync turned off (example) | Whatever produces the numbers |
| Cleanup stops with "Data in posts table appears to be more recent" | Something wrote to the old copy of an order after migration | wp wc hpos diff <order_id> |
Is the live store taking orders correctly right now?
If checkouts are failing or orders are going missing on the live store, switch order storage back to the WordPress posts tables first. Debug on staging afterwards. The same goes if the kitchen or warehouse has stopped getting order data. Get the store working, then investigate.
Go to WooCommerce > Settings > Advanced > Features and select "Use the WordPress posts tables". The recipe book confirms this switch is supported. If compatibility mode was on, the posts tables are already up to date and the switch is instant. If it was off, orders placed since the switch only exist in the new tables. The HPOS CLI won't let you disable HPOS while orders are pending sync, so run wp wc hpos sync first.
Don't run cleanup while you troubleshoot
wp wc hpos cleanup deletes order data from the legacy tables. Leave it alone until the store has run on HPOS without problems for a while, because that legacy copy is your rollback.
How do I check plugin compatibility for HPOS?
Start with WooCommerce > Settings > Advanced > Features. It lists active plugins that have declared themselves incompatible with HPOS, and the Plugins screen shows a warning beside them too. But there's a gap. WooCommerce only shows this for plugins that declare WC tested up to in their header. Snippets in functions.php, code-snippet plugins and old custom plugins won't be flagged at all, even when they read orders straight from the database.
So treat that screen as a starting list. An empty list doesn't prove the store is safe. On staging, update every WooCommerce-related plugin to its latest version, then read the changelogs of the ones that touch orders:
- Payment gateways and subscription plugins
- PDF invoice, packing slip and shipping label plugins
- Accounting syncs such as Xero, and CRM or ERP connectors
- Order export, reporting and admin column plugins
- Marketplace plugins like WCFM, where vendors see their own orders
If a vendor has no HPOS support and isn't planning any, you have a choice to make: replace the plugin, or have the part you need written properly.
How do I run the sync and verify orders on staging?
Copy the live database to staging, turn on compatibility mode, sync with wp wc hpos sync, verify with wp wc hpos verify_data and wp wc hpos diff, then test the store with sync on and again with sync off. WooCommerce's guide for large stores lays out a careful process, and it suits small stores just as well. This is how I'd run it:
- 1
Copy production to staging
Use a fresh copy of the live database so real orders, refunds and custom fields get tested. Stop outgoing customer emails and payment webhooks on staging.
- 2
Turn on compatibility mode
Keep 'Use the WordPress posts tables' selected and tick 'Enable compatibility mode'. The posts tables stay in charge while orders are copied.
- 3
Sync with WP-CLI and time it
Run wp wc hpos sync instead of waiting for background jobs. The time it takes tells you how long the production run will need.
- 4
Verify the data
Run wp wc hpos verify_data, then wp wc hpos diff on any order it flags to see exactly which fields differ.
- 5
Switch and test with sync on
Select 'Use the WooCommerce orders tables', then test checkout with every payment method, refunds, renewals, emails, invoices and anything that reads orders.
- 6
Turn sync off and test again
Repeat the same tests with compatibility mode off. Problems that only show up now usually point to code reading the old tables.
The commands are short:
wp wc hpos status
wp wc hpos count_unmigrated
wp wc hpos sync
wp wc hpos verify_data
wp wc hpos diff 12345
Don't panic if a big sync takes a long time. WooCommerce says their 9-million-order test store took about a week to sync. The CLI docs show 999 orders syncing in 14 seconds. Your store is probably somewhere in between.
How do I find custom code that still reads order post meta?
Search your theme and custom plugins for post meta functions, shop_order and raw $wpdb queries. Then move any code that touches orders to WooCommerce's CRUD API: wc_get_order(), get_meta(), update_meta_data() and save(). Most of the real fixing happens here. WooCommerce publishes a regular expression for the audit in the recipe book. A shorter version catches the usual offenders:
grep -rnE "get_post_meta|update_post_meta|add_post_meta|delete_post_meta|get_post\(|wp_update_post|shop_order|wpdb" wp-content/themes/your-theme wp-content/plugins/your-custom-plugin
Expect plenty of false positives. Most hits will be about pages or products. Go through each match and ask one question: is this ID an order? If it is, switch it to the CRUD API, which reads and writes whichever storage is active:
// Before: talks to the legacy tables only
$ref = get_post_meta( $order_id, '_delivery_ref', true );
update_post_meta( $order_id, '_delivery_ref', $new_ref );
// After: works with posts storage and HPOS
$order = wc_get_order( $order_id );
$ref = $order->get_meta( '_delivery_ref' );
$order->update_meta_data( '_delivery_ref', $new_ref );
$order->save();
While you're in there, check these too:
- Meta boxes on the order screen. HPOS uses a new edit screen, so a box registered for
shop_orderdisappears. The recipe book shows how to pick the right screen ID and accept either a post or an order object. - Raw SQL. Reports that join
wp_postsonshop_orderneed rewriting withwc_get_orders(), or a check withOrderUtil::custom_orders_table_usage_is_enabled(). - The compatibility declaration. Once a custom plugin is fixed, declare it compatible with
FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true )so WooCommerce stops blocking the switch.
Don't stop at PHP either. The large-store guide points out that data warehouses, shipping trackers and accounting systems sometimes read the posts tables directly, and no code audit will find those.
I write custom WooCommerce plugins that act on orders, like the delivery radius check and PrintNode kitchen receipt printing for Whitfield Service Station in Dover. That's exactly the kind of code that needs this audit. If one line still called get_post_meta() on an order, receipts would print with blank fields, and you'd likely only find out when a driver ended up at the wrong address.
How do I fix a sync that never finishes or keeps looping?
If the sync sits at "pending" for days, it's usually one of three things: stalled Action Scheduler jobs, a plugin that keeps writing to the legacy copy of orders, or two copies of an order that disagree.
Background jobs aren't running. Compatibility mode syncs in batches through Action Scheduler, which relies on WP-Cron. On a quiet site, or one with cron disabled, those batches can stall. Check Tools > Scheduled Actions for failed or past-due actions, then run wp wc hpos sync from the command line instead.
Something keeps re-dirtying orders. If a plugin writes to the legacy copy after WooCommerce saves an order, the order is flagged out of sync again and the count never drops. Run wp wc hpos diff on a stuck order: the diff shows meta that exists only in the posts table, and the key name usually gives away the plugin. Fix that code first.
The two copies disagree. Once you know which copy is right, reconcile with wp wc hpos backfill. It can move a whole order, or only certain --props and --meta_keys, in either direction. wp wc hpos verify_data --re-migrate also works, but the docs warn it can overwrite orders, so back up first.
There's also a known WooCommerce issue where large stores can't get orders both synced and cleaned up through the CLI. Cleanup leaves lightweight shop_order_placehold posts behind on purpose, so if a cleanup count won't reach zero but your orders verify correctly, I wouldn't chase it. Cleanup is optional.
How do I roll back HPOS safely?
Rolling back is easiest while compatibility mode is still on. The posts tables are already current, so going back is one setting. That's why I wouldn't rush to turn compatibility mode off. Here's roughly how WooCommerce handled it on a high-volume store:
According to the large-store guide, they turned off sync on read after 6 hours, using add_filter( 'woocommerce_hpos_enable_sync_on_read', '__return_false' );, and turned sync off completely after a week. Even then, they ran wp wc hpos sync now and again so they could fall back quickly.
To roll back:
- Sync still on: select "Use the WordPress posts tables" and test a checkout. Done.
- Sync already off: run
wp wc hpos syncso the posts tables catch up with the new tables, check withverify_data, then switch. - Cleanup already run: the posts copy is gone, so you need a full sync back to posts before you can switch. Take a database backup before you touch anything.
What should you ask a developer before an HPOS fix?
Ask how they'll test, audit, cover payments and roll back. How long an HPOS fix takes depends far more on your plugins and custom code than on how many orders you have, so these answers tell you more than a quick estimate does:
- Will you test on a staging copy of the live database, not a blank install?
- How will you audit custom code and non-WordPress systems that read orders?
- Which payment methods, refunds and subscription renewals will you test?
- What's the rollback plan, and when will compatibility mode be turned off?
- Will you hold off on cleanup until the store has been stable for a while?
I'd want the same questions answered on any WooCommerce build. For typical market price ranges on this kind of work, see my post on custom WordPress plugin costs.
A storage change like this belongs in planned maintenance. It isn't a quick click in the settings. I've looked after the IPL Biologicals WooCommerce store on a monthly retainer since 2025, handling catalogue and price updates and store operations. On a store like that, I test any core change on staging first and keep a way back.
Need a hand with HPOS?
If orders are missing, a sync is stuck, or you'd rather not make the switch yourself, send me your store details. Tell me your WooCommerce version, the plugins that touch orders and what you're seeing. I'll look through your custom code and plugins and tell you the safest order to fix things in. For ongoing care, see my maintenance retainers.



