MS-M

WooCommerce HPOS - what it is, whether to enable it, and how to migrate without losing orders

In the WooCommerce admin, under Settings → Advanced → Features, there is a field called "Order data storage" with two options: "WordPress posts storage (legacy)" and "High-performance order storage (recommended)". The second one is HPOS. If your store launched before the end of 2023, there is a fair chance it still runs on the first one - and that someone told you to "just switch it over" without ever explaining what happens to your orders when you do.

This post opens a WooCommerce knowledge base series on my blog: writing for technical store owners and developers, with no sales pitch buried in the middle. Everything below I verified on WooCommerce 11.0.1 and 10.6.0 (September 2026). The difference between those two versions matters, because in 10.7 WooCommerce changed one thing that most HPOS guides still do not mention.

WooCommerce HPOS - orders moved out of one overflowing storage bin into four organised drawers

Short answer (for the impatient)

HPOS (High-Performance Order Storage) is a way of storing WooCommerce orders in separate, dedicated database tables instead of the generic WordPress post tables (wp_posts and wp_postmeta). Since WooCommerce 8.2 (October 2023) it is the default in every new installation. It is worth enabling in any store where the plugins and the custom code are compatible with it. It is not worth forcing when the store runs a business-critical plugin without compatibility, or when nobody has time to test the switch on a copy of the store.

If you run a store and you do not write code, the sections on what it changes, what happened in 10.7 and when not to enable it are enough. If you are a developer, the rest is for you.

How WooCommerce stored orders until 8.2 - and why it hurt

WooCommerce started life as a WordPress plugin, and WordPress is a content publishing system. So for years an order was a "post" of type shop_order in the wp_posts table, and every one of its properties - address, total, payment method, status, anything a plugin added - was a separate row in wp_postmeta. One order means one row in the posts table and several dozen rows in the meta table.

At 50 thousand orders, wp_postmeta holds millions of rows, and sitting next to your orders in there is meta for products, pages, images and everything any plugin has ever saved. Every filter of the order list by customer, every search by email address and every report means digging through that same bucket.

You know the symptoms from the admin: the order list takes seconds to load, exports time out, yearly reports have to be run overnight. That is not WooCommerce's fault, it is the price of building a store on the foundations of a blogging system. The database is in fact one of the causes I covered in the post on why a WooCommerce store is slow.

What HPOS changes - four tables instead of one bucket

HPOS moves orders into four dedicated tables, documented by WooCommerce: wp_wc_orders (the order itself), wp_wc_order_addresses (addresses), wp_wc_order_operational_data (operational data such as the cart hash or the WooCommerce version) and wp_wc_orders_meta (metadata from plugins). Each one has its own indexes, designed around the queries a store actually runs.

The numbers from WooCommerce's March 2023 benchmark, run on a test store with 400 thousand orders: order creation roughly 5 times faster, filtering by customer ID up to 40 times faster, searching by metadata roughly 10 times faster. Those numbers need context. In a store with five thousand orders, the customer at checkout will not notice the difference. Your back office team will, and so will anyone running reports, exports and integrations with external systems. The more orders you have, the bigger the gap.

What HPOS does not change matters just as much. It does not speed up a product page or a category listing, because products still live in wp_posts. It does not fix bad hosting and it does not replace caching. If your store is slow on the front end, HPOS is not the answer.

One practical benefit that rarely gets written about: once orders have their own tables, you can back up and restore just the orders without touching the rest of the database.

Compatibility mode and synchronisation - where the confusion starts

Most HPOS problems do not come from the switch itself, they come from what sits underneath it. In practice the settings give you three states.

First: posts storage only, the old way. Second: HPOS only. Third: HPOS with compatibility mode enabled, where WooCommerce writes every order to both places at once. One table is then "authoritative" (the one the store reads from) and the other is a copy that changes are mirrored into.

HPOS compatibility mode - the old and the new order storage joined by a stream of synchronised data

Compatibility mode exists so that plugins reading orders the old way (through get_post_meta or a WP_Query on the shop_order post type) keep working during the transition. The price is twofold: a double write on every order change, and the risk of the two copies drifting apart when something writes to the wrong table.

Two things developers ask about most often on the forums. First: when HPOS is authoritative, entries of type shop_order_placehold show up in wp_posts. That is not a bug, it is a reservation of the order number so no other post takes the same ID. Second: WooCommerce will not let you switch storage while there are orders pending synchronisation. A "cannot switch" message usually means you have to wait, or run the synchronisation manually.

What changed in WooCommerce 10.7 - sync on read turned off

This is the part most HPOS guides still skip. Up to version 10.7, compatibility mode worked in both directions. Writing from HPOS to the posts tables (sync on write) is one direction. The other, less known one is sync on read: on every order read, WooCommerce checked whether anyone had changed the data the old way in wp_postmeta, and if so, overwrote HPOS with it.

As of WooCommerce 10.7, released on 14 April 2026, sync on read is disabled by default. Sync on write stays. In the February 2026 advisory WooCommerce spells out the reason: sync on read let data changed outside the official API overwrite current data in HPOS, which caused order statuses to roll back, among other things.

Who does this affect? Only stores where three conditions hold at the same time: HPOS is enabled, compatibility mode is enabled, and some plugin or custom snippet writes order data directly to wp_postmeta instead of going through wc_get_order() and $order->save(). If any one of those does not hold, the change does not affect you.

If it does, the symptom is insidious: nothing breaks. The store runs, orders come in, and the data quietly drifts. A status in the admin that differs from the one in the report, an empty custom field on orders coming from the ERP integration, order notes that "disappeared". You find out a month later, when the numbers stop adding up.

What to do about it: check that both storages agree (wp wc hpos verify_data, described below), fix the code that writes the old way, and turn compatibility mode off for good. As a temporary bridge, WooCommerce left a filter:

add_filter( 'woocommerce_hpos_enable_sync_on_read', '__return_true' );

Treat it as time to do the repair, not as the repair. WooCommerce is signalling the direction clearly: compatibility mode was a bridge, and bridges get dismantled.

How to check whether your store is ready

Three steps, easiest first.

The plugin list in the admin. WooCommerce → Settings → Advanced → Features, next to the "Order data storage" field there is a "View and manage" link. You get the same view at wp-admin/plugins.php?plugin_status=incompatible_with_feature&feature_id=custom_order_tables. It shows the plugins that have not declared HPOS compatibility.

No declaration is not the same as incompatible. A plugin on that list may be abandoned (then look for a replacement) or work perfectly well and simply lack the one line its author should have added. If you maintain your own plugin or theme, that line looks like this:

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

In the stores I look after, the popular plugins on the Polish market - couriers, invoicing, payments - have compatibility declared. The problems are mostly plugins nobody has updated in years, and custom snippets in functions.php.

Incompatibility works in the other direction too. This is something not even the WooCommerce documentation covers, and I ran into it in my own admin on version 11.0.1. Trying to switch storage from HPOS back to "WordPress posts storage" produced a warning: "1 incompatible plugin detected (Flexible Shipping PRO)". A plugin can declare that it supports HPOS exclusively, and in doing so block the return to the old storage. That is a legitimate decision by its author, but it has a consequence: enabling HPOS in a store with a plugin like that is effectively a one-way decision. Going back would mean temporarily deactivating your shipping plugin, which in a live store is a separate decision of its own. The takeaway: check the "View and manage" list under both storage settings, not just before you enable HPOS.

Custom code. In your child theme and your own plugins, look for get_post_meta, update_post_meta, wp_insert_post, WP_Query with post_type => 'shop_order', and direct $wpdb queries against wp_posts in an order context. Each one has a replacement:

The old way HPOS compatible
get_post( $order_id ) wc_get_order( $order_id )
get_post_meta( $order_id, '_key', true ) $order->get_meta( '_key' )
update_post_meta( $order_id, '_key', $val ) $order->update_meta_data( '_key', $val ); $order->save();
new WP_Query( [ 'post_type' => 'shop_order' ] ) wc_get_orders( [ ... ] )
$wpdb->get_results( "... FROM wp_posts ..." ) wc_get_orders() or OrderUtil

How to rewrite order queries so they work against both storages is the subject of a separate post in this series.

How to enable HPOS without losing orders - the procedure

An honest caveat first: in the stores where I have enabled HPOS, the switch simply worked. This is not a high-risk operation. Data is copied, not moved, and the posts tables stay where they are. The procedure below exists not because something usually goes wrong, but because when it does, you want a way back.

Version A: small and mid-size store, from the admin

  1. A full database backup, with a restore test on the side. A backup nobody has restored is an assumption, not a backup.
  2. A copy of the store (staging), and the whole procedure there first. Without staging, do it only in a store that can afford a moment of downtime outside order hours.
  3. Under Features, tick "Enable compatibility mode". WooCommerce will start copying orders into the HPOS tables in the background, through Action Scheduler. Wait until the pending orders counter drops to zero.
  4. Switch storage to "High-performance order storage". Leave compatibility mode on while you test, a week or two.
  5. Testing: placing an order, changing a status, a refund, emails, exports, issuing an invoice, creating a shipment, external integrations (ERP, order management), reports.
  6. Turn compatibility mode off once everything works. After the 10.7 change this is not a cosmetic step, it is how you close the data drift risk.

Version B: large store, via WP-CLI

At hundreds of thousands of orders, synchronising through Action Scheduler takes hours and loads the database in the background. WooCommerce has a set of WP-CLI commands for exactly this, plus a separate guide for large stores. In older write-ups you will see the name wp wc cot - that is the same set under its previous, retired name.

wp wc hpos status              # whether HPOS and compatibility mode are on, how many orders are pending
wp wc hpos count_unmigrated    # just the number of orders left to synchronise
wp wc hpos sync                # batched synchronisation, faster than Action Scheduler
wp wc hpos verify_data         # compares every order across both storages
wp wc hpos diff 100126         # differences for a single order, as a readable table
wp wc hpos backfill 100126 --from=posts --to=hpos --props=status   # repairing a single field
wp wc hpos enable --with-sync  # enables HPOS with compatibility mode
wp wc hpos disable             # back to posts storage (only when nothing is pending sync)
wp wc hpos cleanup all         # removes the old data from wp_postmeta (destructive, do it last)

The order for a large store: status, enable --with-sync, sync outside peak hours, verify_data, testing as in version A, turning compatibility mode off, and only after weeks of quiet operation cleanup all, which removes order metadata from the old tables. The cleanup command will refuse to remove an order by itself if its version in the posts table looks newer than the one in HPOS - that is a safety catch, not an error.

Rollback

When something does not work: make sure compatibility mode is on, wait for synchronisation (or run wp wc hpos sync), and switch storage back to "WordPress posts storage". With one caveat, the one I described above: if any plugin supports HPOS exclusively, the admin will block the return until you deactivate it.

When NOT to enable HPOS (or not yet)

Every post in this series has a section like this, because without one it would be an instruction manual rather than an answer.

Enabling HPOS in WooCommerce - a switch on a panel with four indicator lights, one of them not lit yet

Do not enable HPOS when a plugin that is critical to your sales - payments, shipping, invoicing, the ERP integration - is on the list without compatibility and its author does not respond. Replacement first, HPOS second.

Wait when the store contains custom code nobody knows, for example after taking it over from another developer. The careful migration I described in the post on rebuilding a store without losing SEO or order history starts with understanding what is in the store, not with flipping switches.

Wait when you have neither staging nor a backup with a restore test. Enabling HPOS is not risky in itself, but there is no point doing it blind.

You can comfortably wait when the store takes a few hundred orders a year, everything works and nobody is complaining about the admin. The gain is real, but small. The natural moment will come on its own: a major update, a theme change, a rebuild.

And do not do it the week before Black Friday or in the middle of a sale.

"Not yet" is not "never". WooCommerce is pointing in a clear direction, every new installation starts on HPOS, and the 10.7 change says outright that the transition period is ending.

Summary

HPOS is not an experiment, it is the way WooCommerce stores orders from here on. The risk does not sit in the switch, it sits in code that writes to orders the old way - and in plugins that will not let you go back once HPOS is on. After 10.7, compatibility mode is a bridge to be dismantled, not a setting you can forget about.

If you would rather not walk through the plugin list and the custom code yourself, book a free consultation. We will check whether the store is ready and what needs fixing before the switch.

FAQ

HPOS (High-Performance Order Storage) is a way of storing orders in four dedicated database tables instead of the generic WordPress post tables. In the admin it appears as "High-performance order storage". It gives you faster order search, filtering and reporting, particularly in stores with large numbers of orders.

Since WooCommerce 8.2 (October 2023) yes, but only in new installations. A store launched before that stays on WordPress posts storage until someone deliberately switches it in the settings.

No. Data is copied into the new tables, and the posts tables stay untouched until you deliberately run the cleanup command. The real risk is not loss but drift between the two storages, when some plugin or snippet writes orders the old way.

Compatibility mode makes WooCommerce write every order to the HPOS tables and the posts tables at the same time. It was built for the transition period, so that older plugins would keep working. It costs you a double write and should be turned off once testing is done.

As of version 10.7 (April 2026), compatibility mode no longer overwrites HPOS data with changes made the old way in the posts tables (sync on read). This only concerns stores with HPOS enabled, compatibility mode enabled and code writing directly to wp_postmeta. The old behaviour can be restored temporarily with the woocommerce_hpos_enable_sync_on_read filter.

In the WooCommerce admin, under Settings, Advanced, Features, click "View and manage" next to the order storage field. You will see the plugins with no declared compatibility. A missing declaration does not always mean the plugin is broken, but it does mean nobody has confirmed otherwise.

Some plugins, Flexible Shipping PRO for example, declare support for HPOS exclusively and block the return to WordPress posts storage. To go back you have to deactivate such a plugin temporarily, or stay on HPOS. That is why it is worth checking the incompatible plugin list under both storage settings before you switch.

The order admin, search, reports and exports yes, measurably so at thousands of orders. Product and category pages no, because products are still stored in the post tables. HPOS is also not a substitute for good hosting or caching.

Back to blog

Want to start a project?

Let's Talk

Get in touch

Your data is controlled by Marcin Siemieniuk-Morawski and used solely to reply to your message. Details in the privacy policy.

You can also email me directly at: kontakt@ms-m.pl