WooCommerce HPOS migration problems: how to fix them

WooCommerce HPOS migration problems? Fix missing orders, stuck syncs and plugin conflicts with staging checks, custom code audits and a safe rollback.

Mohit Goyal12 min read
WooCommerce HPOS migration problems: how to fix them

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.

SymptomLikely causeWhere to look first
The "WooCommerce orders tables" option is greyed outAn active plugin is flagged as incompatibleThe list of incompatible plugins on WooCommerce > Settings > Advanced > Features
"Sync X pending orders" never reaches zeroBackground jobs stalled, a very large store, or code writing to the old tables after every syncwp wc hpos count_unmigrated and Tools > Scheduled Actions
Custom order fields blank in admin, emails or invoicesCustom code using get_post_meta() or update_post_meta() on ordersYour theme's functions.php and any custom plugins
A meta box has vanished from the order edit screenThe box is registered only for the old shop_order screenadd_meta_box() calls in custom code
Dashboard widgets, reports or the REST API show wrong countsA 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 migrationwp 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. 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. 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. 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. 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. 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. 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:

bash
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:

bash
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:

php
// 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_order disappears. 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_posts on shop_order need rewriting with wc_get_orders(), or a check with OrderUtil::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:

Rendering diagram…

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:

  1. Sync still on: select "Use the WordPress posts tables" and test a checkout. Done.
  2. Sync already off: run wp wc hpos sync so the posts tables catch up with the new tables, check with verify_data, then switch.
  3. 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.

Frequently asked

Will switching to HPOS delete my WooCommerce orders?+

No. Switching to HPOS copies WooCommerce orders into the new order tables, and WooCommerce only lets you switch once all orders are synced. The legacy copy stays in the WordPress posts tables until you run wp wc hpos cleanup, but with compatibility mode off, new orders are written only to the HPOS tables.

How do I switch WooCommerce back from HPOS to the posts tables?+

Select 'Use the WordPress posts tables' under WooCommerce > Settings > Advanced > Features. If compatibility mode was off, run wp wc hpos sync first, because WooCommerce won't disable HPOS while orders are pending sync.

Why is the HPOS option greyed out in WooCommerce?+

The WooCommerce HPOS option is greyed out when at least one active plugin has declared itself incompatible with HPOS. Update that plugin, replace it, or have the part you need rewritten, then check the Features screen again.

How long does a WooCommerce HPOS sync take?+

It depends on your order count and server speed. WooCommerce's CLI docs show 999 orders syncing in 14 seconds, while WooCommerce's 9-million-order test store took about a week.

Do existing WooCommerce stores have to switch to HPOS?+

HPOS has been the default for new WooCommerce installations since version 8.2, but existing stores have to switch manually. Switch once your plugins and custom code are HPOS-compatible.

Should I run wp wc hpos cleanup after migrating to HPOS?+

Cleanup is optional. It removes order data from the legacy WordPress posts tables, which means losing your quick rollback, so only run it once the store has been stable on HPOS for a while.

Mohit Goyal

Written by Mohit Goyal

Senior Web Developer in Gurugram, India. 100+ fast, SEO-friendly sites shipped in 4+ years with Next.js, React and WordPress, including the procol.ai rebuild. Send me your next project.

Keep reading

WooCommerce Apple Pay not showing? How to fix it

WooCommerce Apple Pay not showing? Fix Stripe domain registration, express checkout settings, device rules, caching and CSP blocks.

10 min read

WooCommerce PayPal payments not working: how to fix it

WooCommerce PayPal payments not working? Check sandbox mode, webhooks, currency, caching, CSP and plugin clashes to get checkout taking money again.

11 min read

Let's build

Have a site that needs to be fast, rank and convert?

Open to Work. Usually replies within a day.