High-Performance Order Storage is the biggest change to WooCommerce’s data layer since the plugin launched. Orders move out of wp_posts and into dedicated database tables. For most stores, that means faster order screens and cleaner queries.
It also means anything that touches order data the old way can break.
We have migrated client stores to HPOS since it became the default for new installs. Some migrations took twenty minutes. One took three weeks, because a custom shipping integration read order meta directly from wp_postmeta in eleven different places. This guide covers what we check before every migration, what actually breaks, and how to get back out if something goes wrong.
What HPOS actually changes
Before HPOS, WooCommerce stored orders as a custom post type. Every order was a row in wp_posts with type shop_order, and every piece of order data โ billing address, totals, customer notes โ lived in wp_postmeta. A store with 50,000 orders can carry two million postmeta rows for orders alone.
HPOS replaces that with four purpose-built tables:
wp_wc_ordersโ core order data: status, totals, customer ID, dateswp_wc_order_addressesโ billing and shipping addresseswp_wc_order_operational_dataโ internal flags and versioningwp_wc_orders_metaโ remaining order meta that has no dedicated column
Columns instead of meta rows. Proper indexes instead of meta_key lookups. On the stores we have measured, order list screens in wp-admin loaded 2โ4ร faster after migration, and that gap widens as order count grows.
Why bother migrating
Three practical reasons, in the order they usually matter to store owners:
- Admin speed. The orders screen stops crawling once you pass tens of thousands of orders. Your team feels this daily.
- Database health. Order data stops inflating
wp_postmeta, which speeds up every other query that touches that table โ including ones that have nothing to do with orders. - The direction of travel. HPOS is the default for new WooCommerce installs. Plugin developers test against it first now. Staying on legacy storage means living on the less-tested path.
If your store has a few hundred orders and everything works, there is no urgency. The gains are real but small at that scale. Past 10,000 orders, the case gets strong.
What breaks: the three failure patterns
Almost every HPOS problem we have seen falls into one of three buckets.
1. Direct database queries against wp_posts or wp_postmeta
This is the big one. Any code that queries order data with SQL, WP_Query, or get_post_meta() assumes orders live in the posts table. After migration, they do not.
The pattern that breaks:
// BREAKS under HPOS โ reads from wp_postmeta directly.
$phone = get_post_meta( $order_id, '_billing_phone', true );
$orders = new WP_Query( array(
'post_type' => 'shop_order',
'post_status' => 'wc-processing',
) );
The pattern that survives:
// WORKS everywhere โ CRUD methods resolve storage internally.
$order = wc_get_order( $order_id );
$phone = $order->get_billing_phone();
$orders = wc_get_orders( array(
'status' => 'processing',
'limit' => 50,
) );
If your custom code has used wc_get_order() and the CRUD getters since WooCommerce 3.0, you are probably fine. If it predates that, or a past developer took shortcuts, audit before you migrate.
2. Plugins without HPOS compatibility declarations
Plugins are supposed to declare HPOS compatibility explicitly:
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
);
}
} );
WooCommerce blocks HPOS activation while incompatible plugins are active, and lists them under WooCommerce โ Settings โ Advanced โ Features. That screen is your first audit stop. An abandoned plugin that never declared compatibility will sit in that list forever, blocking you โ even if it would actually run fine.
3. Code that assumes order IDs are post IDs
Subtler and nastier. Under HPOS, orders keep their IDs on migration, but new orders draw IDs from the new table’s sequence. Code that passes an order ID to a generic post function โ get_post(), get_edit_post_link(), direct joins on wp_posts.ID โ gets back nothing, or worse, a different object that happens to share the ID.
We found one plugin logging order changes by writing to wp_posts.post_modified. Under HPOS it silently updated an unrelated page. That is the kind of bug that never throws an error.
The pre-migration audit
Run these checks before touching anything. Thirty minutes here saves a bad week.
- Check the compatibility screen. WooCommerce โ Settings โ Advanced โ Features. Every plugin listed as incompatible needs a decision: update it, replace it, or drop it.
- Grep custom code. Search your theme and custom plugins for
get_post_meta,update_post_meta, and'post_type' => 'shop_order'(and its variations). Every hit against an order is a rewrite. - List your integrations. Anything that reads or writes orders from outside โ ERP connectors, accounting syncs, shipping platforms, custom reports pulling SQL. These live outside the plugin compatibility system and will not appear on any warning screen.
- Check your database size. The migration copies every order. A store with 500,000 orders needs disk headroom for the copy and time for the batch process.
How the migration runs
WooCommerce migrates orders in the background using Action Scheduler, in batches. The switch itself lives at WooCommerce โ Settings โ Advanced โ Features.
The sequence we use on client stores:
- Full backup. Database and files. Not optional.
- Enable compatibility mode first. Choose “High-performance order storage” with “Enable compatibility mode (synchronizes orders to the posts table)” checked. WooCommerce writes to both storage systems in parallel.
- Let the backfill finish. On a large store this takes hours. Watch progress under WooCommerce โ Status โ Action Scheduler, searching for
wc_schedule_pending_batch_processactions. - Run in compatibility mode for one to two weeks. Everything works, but you have a live safety net. Test every order flow: manual orders, refunds, subscriptions renewals if you run them, every integration.
- Switch compatibility mode off. Only after every workflow has been exercised. This is the point of real commitment โ after this, the posts table goes stale.
On the command line, the same migration runs via WP-CLI, which we prefer for large stores because it is not tied to web request cycles:
wp wc hpos status
wp wc hpos enable --with-sync
Testing checklist
During the compatibility-mode window, work through this list. Every item has caught a real problem for us at least once:
- Place a test order through the full checkout, including payment
- Issue a partial refund and a full refund
- Create a manual order in wp-admin and edit its address
- Confirm order confirmation and admin notification emails send
- Run your accounting or ERP sync and verify the order arrives intact
- Print a shipping label through your fulfilment integration
- Check any custom order reports return the same numbers as before
- If you run subscriptions: force a renewal and confirm it processes
Rolling back
While compatibility mode is on, rollback is genuinely easy: switch the setting back to “WordPress posts storage”. Both tables are current, so nothing is lost.
After you have disabled compatibility mode, rollback means re-enabling sync in the other direction and letting the backfill run again. Orders placed while the posts table was stale get copied back. It works, but it is slow on big stores โ which is exactly why we hold the safety net open for two weeks rather than two hours.
When not to migrate yet
Honest list. Stay on legacy storage for now if:
- A business-critical plugin is incompatible and has no HPOS-ready alternative
- Your store depends on custom SQL reporting nobody on your team can rewrite
- You are mid-peak-season โ never change the data layer in November if December pays your year
None of these mean never. They mean sequence the work: fix the blocker, then migrate.
Where this fits in the bigger performance picture
HPOS fixes order storage, not everything else. If your store is slow, the causes usually stack: bloated autoloaded options, missing object cache, a theme running queries in loops. We wrote a separate WooCommerce performance guide covering the full stack, and a speed optimisation walkthrough for store owners who want the checklist version.
If you would rather have someone else own the migration โ audit, staging test, rollback plan, the boring careful parts โ that is exactly the work our WooCommerce development service exists for. And if you want ongoing eyes on your store after the switch, our care plans include update testing on staging before anything touches production.
