WooCommerce High-Performance Order Storage (HPOS): how to switch safely

How to find what blocks the switch to WooCommerce's own order tables, copy and check every order, and make the switch with an undo button still in place.

Founder of RoundBorders · WordPress since 2012

Published 11 min read

This post changes where WooCommerce keeps your orders. Take a database backup you've checked you can restore, and run every step on a staging copy before the live store. If you'd prefer someone else did it, the end of the post says what we charge.

The short answer

Fix or replace anything that is not HPOS-compatible first. Then turn on compatibility mode, let WooCommerce copy every order into its new tables, check the copy, and switch to High-performance order storage with sync still on. Keep sync on for about a week of real orders as your undo button, then turn it off.

  1. 01Check which storage the store uses
  2. 02Find everything that isn’t compatible
  3. 03Back up, then rehearse on a staging copy
  4. 04Turn on compatibility mode and let the orders copy
  5. 05Check the copy
  6. 06Switch, with sync still on
  7. 07Keep sync on for a week, then turn it off
  8. 08If something breaks, switch back
  9. 09Months later: clean up the old order copies

Rather hand it over? Get a fixed price for the whole job, from $249.

High-Performance Order Storage (HPOS) moves your WooCommerce orders out of the WordPress posts tables and into tables built only for orders. Stores created on WooCommerce 8.2 (October 2023) or later have it on already. An older store is probably still on the old storage, which WooCommerce’s own settings screen now labels “legacy”. The switch is safe when it’s done in the right order: clear out anything that isn’t compatible, let WooCommerce copy every order across, check the copy, switch while the two copies stay in sync (so going back is one click), and only then turn the old copy off. This post is that order.

Plain English On the old storage, an order is a WordPress “post”, the same kind of row as a blog post, and every detail of it (the billing address, the total, the payment method) is a separate row in wp_postmeta, a table it shares with every plugin on the site. HPOS gives orders four tables of their own: wp_wc_orders, wp_wc_order_addresses, wp_wc_order_operational_data and wp_wc_orders_meta. WooCommerce’s published figures are up to 5x faster order creation, up to 1.5x faster checkout and up to 40x faster order search in the admin.

The steps below assume a store with real order history. On a brand-new store with no orders, the switch is a single setting and none of this applies.

Step 01 Check which storage the store uses

Go to WooCommerce → Settings → Advanced → Features and find the section called Order data storage. It has two options, “WordPress posts storage (legacy)” and “High-performance order storage (recommended)”, and under them a checkbox: “Enable compatibility mode (Synchronize orders between High-performance order storage and WordPress posts storage).”

There’s a quicker tell. Open WooCommerce → Orders and look at the address bar: admin.php?page=wc-orders means HPOS, edit.php?post_type=shop_order means the old posts storage.

With WP-CLI (WooCommerce 8.6 or later) one command answers all of it:

Terminal
wp wc hpos status

It prints four lines: whether HPOS is enabled, whether compatibility mode is on, how many orders are out of sync, and how many old order copies are waiting to be cleaned up.

Already on High-performance?

Nothing to switch. If compatibility mode is still ticked from an earlier switch, go to Step 07 and finish the job.

Still on posts storage?

Carry on with Step 02. Don't touch the setting yet.

Step 02 Find everything that isn’t compatible

This is the step that decides whether the switch is an afternoon or a project. Three places to look.

Plugins. Every plugin that works with WooCommerce is supposed to declare whether it supports HPOS. If any active plugin declares that it doesn’t, or declares nothing at all, WooCommerce counts it as incompatible, greys out the “High-performance order storage” option, and shows a warning such as ⚠ 1 Incompatible plugin detected in the Order data storage section. The View and manage link next to it opens the list. On the command line (WooCommerce 9.1 or later):

Terminal
wp wc hpos compatibility-info

It sorts your WooCommerce plugins into compatible, incompatible and uncertain. “Uncertain” means the plugin never said either way, and for HPOS that blocks the switch the same as “incompatible”. For each one on the list, update it first (most plugins added support years ago), then look for a current replacement, then ask its developer. One special case: if the WooCommerce Legacy REST API plugin is active, WooCommerce warns that it is not compatible with HPOS. Something on the site, often an old app or integration, still uses that API, so find out what before you switch.

Your own code. A theme or a small custom plugin that reads orders as posts won’t be on any list, because it never declared anything. Search for the tell-tale names from the wp-content folder:

Terminal
grep -rnE "shop_order|_billing_|_shipping_|_order_total|postmeta" themes mu-plugins

Add the folder of any custom plugin written for the store. A hit isn’t always a problem, but every hit that reads or writes an order through get_post_meta(), update_post_meta(), WP_Query or direct SQL has to change. The fix is to go through WooCommerce’s order functions, which work on either storage:

PHP
// Before: reads the posts storage only.
$phone = get_post_meta( $order_id, '_billing_phone', true );
update_post_meta( $order_id, '_delivery_slot', 'am' );

// After: works on both storages.
$order = wc_get_order( $order_id );
$phone = $order->get_billing_phone();
$order->update_meta_data( '_delivery_slot', 'am' );
$order->save();

Once a custom plugin has been converted and tested, it declares support so WooCommerce stops blocking the switch. This goes in the plugin’s main file:

PHP
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 );
	}
} );

Declaring support for code that still reads wp_postmeta doesn’t make it work. It only removes the warning, and the code goes on reading data that stops being updated in Step 07.

Anything outside WordPress. An accounting connector, a warehouse system, a reporting dashboard or an export script that queries the database directly will keep reading the posts tables after the switch. WooCommerce’s guide for large stores calls this out separately because no plugin list will ever show it. Ask whoever set each one up whether it reads the database directly or goes through the WooCommerce REST API. The REST API keeps working after the switch; direct queries don’t.

Nothing on the list?

Step 03. The hard part is already done.

A plugin with no fix, or custom code to rewrite?

Stop here until it's sorted. Switching anyway is how orders go missing from a report or an integration with no error anywhere.

Step 03 Back up, then rehearse on a staging copy

Take a full database backup and check that you can restore it. Then make a staging copy of the live store with today’s orders and run Steps 04 to 07 there first, exactly as you will on the live store.

On staging, test the things that touch orders:

  • a checkout with every payment method you offer, including wallet buttons
  • a refund through each gateway
  • a subscription purchase and a renewal, if you sell subscriptions
  • the order emails, to you and to the customer
  • your exports, reports and anything from Step 02 that reads orders

Run the tests once with compatibility mode on and once with it off. Note how long the sync took on staging, because that tells you how long to expect on the live store. If checkout itself misbehaves on staging, the usual causes of a broken WooCommerce checkout are worth ruling out before you blame the storage.

Step 04 Turn on compatibility mode and let the orders copy

On the live store, back on Settings → Advanced → Features, keep “WordPress posts storage (legacy)” selected, tick “Enable compatibility mode”, and save. Nothing changes for customers. WooCommerce creates the new tables and starts copying every existing order into them in the background, while new orders keep being written to both places.

The background copy runs as small batches in Action Scheduler (look for wc_schedule_pending_batch_process and wc_run_batch_process under WooCommerce → Status → Scheduled Actions), and the Features page shows a “Currently syncing orders” line with the number still to go. On a store with a few thousand orders, that finishes on its own. On a big store, or one whose queue is already slow, run the copy from WP-CLI instead:

Terminal
wp wc hpos sync --batch-size=500

It shows a progress bar and can be stopped and started again; it picks up where it left off. Run it inside screen or tmux so a dropped connection doesn’t stop it. For scale, WooCommerce’s large-store guide says nine million orders took about a week to migrate. To see how many are left at any point:

Terminal
wp wc hpos count_unmigrated

Watch out If the Scheduled Actions screen won’t load or the “pending” number doesn’t move for hours, the queue itself is stuck, and the sync is waiting behind it. That is its own problem, with its own fix: cleaning up Action Scheduler safely.

Pending is at zero?

Step 05. Check the copy before you trust it.

Stuck, or errors in the output?

Leave the setting on posts storage and fix the cause first. Interrupting the sync is safe; switching with orders still pending is not.

Step 05 Check the copy

WooCommerce won’t even let you switch while orders are out of sync: the option stays greyed out with the message “You can switch order data storage only when the posts and orders tables are in sync”. That checks the count, not the content, so compare the two copies as well:

Terminal
wp wc hpos verify_data --verbose

It reads every order from both storages and reports any that differ. To see exactly what differs on one order:

Terminal
wp wc hpos diff 1234

The command also has a --re-migrate option that copies the failed orders again. WooCommerce’s own note on it is the rule to follow: use it only if the store has never run with HPOS as the main storage, or after you’ve checked the reported differences yourself, otherwise stale data can overwrite newer data. Before the first switch, that’s safe.

No WP-CLI? Open a handful of orders from different years, including a refunded one and one with a note, and keep their order numbers. You’ll compare them again after the switch.

Step 06 Switch, with sync still on

Pick a quiet hour, and make sure someone can watch the store for the rest of the day. Then select “High-performance order storage (recommended)” and save. Leave compatibility mode ticked.

From this moment WooCommerce reads and writes orders in its new tables, and keeps copying every change back to the posts tables. That copy is your undo button: if anything goes wrong, switching back is instant, with no orders to recover.

Then repeat the important tests on the live store:

  • place a real order with each payment method and refund it
  • open the orders you noted in Step 05 and compare them
  • check that the order emails arrive
  • run your exports and check anything from Step 02 that reads orders
All of it works?

Step 07. Keep watching for a week before you remove the undo button.

Something broke?

Switch back now, it takes one click while sync is on. Step 08 has the details.

Step 07 Keep sync on for a week, then turn it off

Leave compatibility mode on while the store takes a week of normal orders, renewals and refunds. WooCommerce’s large-store guide turned sync off after one week on a high-volume store, and saw no noticeable slowdown from keeping it on. Read the support inbox closely that week: a missing tracking number or a blank column in an export is how a problem shows up.

When the week is clean, untick “Enable compatibility mode” and save. From now on only the new tables are written. WooCommerce still creates a placeholder row in the posts table for each new order (post type shop_order_placehold) so order numbers never collide; those are expected.

Two notes for this step:

  • WooCommerce’s large-store guide also turns off “sync on read” first with a code filter. Since WooCommerce 10.7 (April 2026) that’s already off by default, so on a current version there is nothing to add. If the store is older than 10.7, update WooCommerce before you start.
  • Turning sync off doesn’t remove the way back. It just means going back first needs a fresh copy in the other direction, which takes as long as Step 04 did.

Step 08 If something breaks, switch back

While compatibility mode is still on, select “WordPress posts storage (legacy)” and save. That’s the whole rollback, and nothing is lost, because every order was being written to both places.

If you had already turned compatibility mode off:

  1. Tick “Enable compatibility mode” and save. WooCommerce starts copying the orders taken since you turned it off back into the posts tables.
  2. Wait for the pending number to reach zero (or run wp wc hpos sync).
  3. Select “WordPress posts storage (legacy)” and save.

From the command line, wp wc hpos disable does the same final switch, and refuses with “There are orders pending sync” until the copy is complete. That refusal is a safety catch, not a bug. Then go back to Step 02: whatever broke is almost always a plugin or a piece of code that reads orders the old way.

Step 09 Months later: clean up the old order copies

After the switch, the old copies of every order still sit in wp_posts and wp_postmeta, and on a store with years of orders they’re a large share of the database. WooCommerce has a command that removes them, and it only runs when HPOS is the main storage and compatibility mode is off:

Terminal
wp wc hpos cleanup all

Don’t rush it. This is the step that removes your way back, so wait until the store has run for months without a reason to roll back, take a fresh backup the day you run it, and run it off-peak; it works in batches of 500 orders by default. wp wc hpos status shows how many orders are waiting to be cleaned up.

Where I’d stop and hand it over: when Step 02 turns up a plugin with no compatible version and no replacement, custom code that reads orders through get_post_meta() or raw SQL, or an outside system you can’t see into. That isn’t a settings change anymore. It’s rewriting the code that touches your orders and proving nothing was lost, and that is what our WooCommerce extension development covers, tested the same way as Steps 03 and 06 before the switch goes live.

From the RoundBorders team

Rather not move the orders yourself?

Send the store’s address and what the Features page flags. We go through every plugin and piece of custom code that blocks the switch and send a fixed price, usually within a few hours.

  • From $249
  • Same-day diagnosis
  • Tested with a real order
No passwords needed to quote

Write as much as you like: what's happening, what you want, links, errors. The more we know, the more exact your fixed price.

How we work on WooCommerce stores

Share this post

Know a store owner with the same problem? Send it their way.

Bishoy Awad

Written by

Founder of RoundBorders. Building and fixing WordPress sites since 2012; our work is listed in the WooCommerce, Pressable and Jetpack partner directories.

Sources

Found a mistake? Write to me.