← All posts

How to Move a WooCommerce Store to HPOS Without Losing Orders

Order receipts moving from an old filing cabinet into a modern database, next to a shopping bag and a checklist

If your WooCommerce store was built before late 2023, there’s a good chance it still keeps every order in wp_posts and wp_postmeta, right next to your blog posts and pages. New stores haven’t worked that way since WooCommerce 8.2, which made High-Performance Order Storage (HPOS) the default.

HPOS moves orders into their own tables. Admin order screens get faster, order queries stop fighting with everything else in wp_postmeta, and the data has a structure that actually fits orders. WooCommerce has also been clear that new features are built for HPOS first.

The switch itself is a setting. The risk is in the plugins and custom code that read orders the old way. Here’s the process I use on client stores.

What actually changes

With the legacy storage, an order is a post of type shop_order, and almost everything about it (totals, addresses, payment method) is a row in wp_postmeta.

With HPOS, orders live in dedicated tables:

  • wp_wc_orders for the order itself
  • wp_wc_order_addresses for billing and shipping
  • wp_wc_order_operational_data for internal order state
  • wp_wc_orders_meta for any extra meta

Code that goes through the WooCommerce order API (wc_get_order(), $order->get_meta(), wc_get_orders()) keeps working either way. Code that reads wp_postmeta directly, or runs a WP_Query for shop_order posts, does not. That’s the code you need to find.

Step 1: Check where you are

In WordPress, go to WooCommerce > Settings > Advanced > Features. You’ll see which order storage is active, whether compatibility mode is on, and a warning if any active plugins haven’t declared HPOS support.

With WP-CLI:

wp wc hpos status

That tells you if HPOS is enabled, if compatibility mode is on, and how many orders are waiting to sync.

Step 2: Make a staging copy

Same rule as any risky change: don’t try this first on the live store. Clone it to staging, turn off outgoing email so customers don’t get test order emails, and switch payment gateways to test mode.

Take a full backup of production too, files and database, and make sure you can restore it.

Step 3: Audit plugins and custom code

Start with the incompatible plugins list on the Features screen. For each one, update it and check the changelog. Most maintained plugins added HPOS support long ago. If one still hasn’t, that tells you a lot about how it’s maintained, and it’s time to look for a replacement.

Then search your custom theme and plugins for the patterns that break:

grep -rnE "shop_order|get_post_meta|update_post_meta|wp_postmeta" \
  wp-content/themes/your-theme wp-content/plugins/your-plugin

Not every hit is a problem, but each one that touches orders needs to change:

  • get_post_meta( $order_id, '_billing_email', true ) becomes wc_get_order( $order_id )->get_billing_email()
  • update_post_meta() on an order becomes $order->update_meta_data() followed by $order->save()
  • A WP_Query with post_type => shop_order becomes wc_get_orders()
  • Direct SQL against wp_posts or wp_postmeta for orders needs a rewrite on top of the order API

Once your own plugin is clean, declare it compatible so WooCommerce stops flagging it:

add_action( 'before_woocommerce_init', function () {
    if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
    }
} );

Step 4: Turn on compatibility mode and sync

On staging, go back to the Features screen and enable compatibility mode first, while the legacy storage is still active. This keeps both sets of tables in sync, so you can switch back at any point.

Compatibility mode migrates orders in the background, but on a store with tens of thousands of orders that takes a while. WP-CLI is much faster:

wp wc hpos count_unmigrated
wp wc hpos sync

Step 5: Verify the data

Before switching anything, check that both storages agree:

wp wc hpos verify_data

If it reports differences, look at a single order in detail:

wp wc hpos diff 12345

wp wc hpos backfill can copy specific fields from one storage to the other to fix mismatches. Don’t move on until verification is clean.

Step 6: Switch to HPOS on staging and test

Now set HPOS as the active order storage, with compatibility mode still on. Then test the paths that make money:

  • Place a test order from product page to thank-you page
  • Check the order shows correctly in admin, with addresses, totals and notes
  • Process a refund and change an order status
  • Check order emails, both customer and admin
  • If you sell subscriptions, run a renewal
  • Check anything that reads orders: shipping labels, accounting sync, exports, CRM integrations

Watch debug.log the whole time. Errors from a plugin still reading wp_postmeta tend to show up here before they show up on screen.

Step 7: Do it on production

When staging is clean, repeat the same steps on production during a quiet hour: compatibility mode on, sync, verify, switch to HPOS. Keep compatibility mode on for a week or two. If something breaks that you missed, you can switch back without losing orders placed in the meantime.

Step 8: Turn off compatibility mode and clean up

Once you’re confident, turn off compatibility mode. Syncing two storages costs a write on every order change, and you no longer need it.

The old order data is still sitting in wp_posts and wp_postmeta. When you’re sure you won’t go back, you can remove it:

wp wc hpos cleanup all

This is destructive, so take a backup first. WooCommerce checks each order before removing it and stops if the legacy copy looks newer. It keeps small placeholder posts so you can still switch storage later if you ever need to.

FAQ

Is HPOS required in WooCommerce?

Not yet for older stores, but it’s been the default for new stores since WooCommerce 8.2, and new WooCommerce features are built with HPOS in mind. Staying on legacy storage gets riskier with each release.

Will switching to HPOS break my plugins?

Only plugins that read or write orders without the WooCommerce order API. The Features screen lists plugins that haven’t declared compatibility, and a search for shop_order and get_post_meta in custom code finds most of the rest.

Can I switch back from HPOS?

Yes, as long as compatibility mode was on so both storages stayed in sync. If orders are pending sync, WooCommerce makes you run wp wc hpos sync before it lets you switch.

How long does the migration take?

The sync itself is usually minutes with WP-CLI, even on large stores. The real time goes into auditing plugins and testing on staging.

Need a hand?

If your store has years of orders and a pile of custom code, I can audit it, fix the incompatible bits, and run the migration with you. Get in touch or see my services.

Agency with several client stores still on legacy storage? Betatech works as your white-label WordPress team, NDA included.

Questions about this post?

Or a store that needs the same kind of work? Send me a message and I’ll reply within one business day.