NextWoo
Order storage

WooCommerce HPOS: moving orders into their own tables without losing one

High-Performance Order Storage takes orders out of the WordPress posts tables. The switch itself is a setting. The work is proving that every plugin, snippet and outside system still finds the orders afterward.

WooCommerce HPOS (High-Performance Order Storage) moves orders out of the WordPress posts tables and into four tables built for them. Stores installed on WooCommerce 8.2 or later already use it. An older store on posts storage switches in three moves: turn on compatibility mode so both copies stay in sync, make HPOS the authoritative storage (the one WooCommerce actually reads and writes) once every order is copied, and keep sync running until checkout, refunds and each order-touching plugin have proved themselves. The risk sits in code that reads wp_posts directly, and WooCommerce's own compatibility screen catches only part of it.

01

Four order tables instead of wp_posts and wp_postmeta

Before HPOS, an order was a WordPress post of type shop_order, and nearly everything about it, from billing email to payment method, sat as separate rows in wp_postmeta next to every product, page and revision on the site. HPOS gives orders their own storage. `wp_wc_orders` holds one row per order, with status, currency, totals, customer ID, billing email and payment method as real columns. `wp_wc_order_addresses` keeps billing and shipping addresses, `wp_wc_order_operational_data` keeps internal values such as the order key and cart hash, and `wp_wc_orders_meta` takes whatever extensions attach. Line items do not move: they stay in `wp_woocommerce_order_items`, as before. The difference that matters is indexes. The orders table is keyed on status, creation date, customer and billing email, so "this customer's completed orders" becomes an index lookup instead of a scan through meta rows. The wp_ prefix is the default; your install may use another.

02

Stores built since October 2023 are already on it

HPOS shipped as stable in WooCommerce 8.2, released in October 2023, and became the default for new installations. A store created after that has written every order to the new tables, so there is nothing to migrate. Stores installed earlier stay on posts storage through every update until somebody switches them. To see which one you have, open WooCommerce → Settings → Advanced → Features and read Order data storage: it says either "WordPress posts storage (legacy)" or "High-performance order storage (recommended)". `wp wc hpos status` gives the same answer from the command line, plus a count of unsynced orders.

03

Where HPOS saves time, and where it saves none

WooCommerce published a benchmark in March 2023, run on a store with about 400,000 orders and a 2 GB postmeta table. Creating 1,000 orders took 78 seconds on posts storage and 15 on HPOS. Ten checkouts went from 1.51 seconds to 0.99. Filtering orders by customer ran about 40 times faster. The test used a development build in a single worker with sync between the storages turned off, so treat those as direction, not as numbers to expect on your host. The gains land in three places: order writes at checkout, the admin order list and its search, and anything that reports on orders. A slow category page gets nothing, because shoppers browsing the catalog never touch the order tables. If the complaint is the storefront, HPOS is the wrong fix. If it is an Orders screen that takes seconds to open, read why the WooCommerce admin is slow first, since order storage is only one of the causes. The benchmark store had 400,000 orders, and a store with a small order history has correspondingly less to gain.

04

What the Features screen flags, and what it cannot see

The compatibility check starts in WooCommerce → Settings → Advanced → Features. When an active plugin is incompatible, the HPOS option is disabled and a "View and manage" link opens the Plugins screen filtered to `plugin_status=incompatible_with_feature&feature_id=custom_order_tables`. `wp wc hpos compatibility-info` prints three lists: compatible, incompatible and uncertain. A plugin gets onto those lists by declaring itself. Its developer calls `FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true )` on the `before_woocommerce_init` hook, and WooCommerce's source treats a WooCommerce plugin that declares nothing as incompatible. That is the safe direction. The blind spot runs the other way. WooCommerce only checks plugins whose header carries "WC tested up to", so a code-snippets plugin, a function in the theme, a generic reporting plugin or an ERP connector that queries the database itself never shows up in any list. Neither does software outside WordPress. WooCommerce's large-store guide names data warehouses, shipping trackers and accounting systems that read the posts tables directly, and tells you to audit them separately. A clean Features screen is the starting condition, nothing more. When an incompatible plugin cannot be updated or replaced, stay on posts storage. Deactivating it just long enough to flip the switch is the worse option by far: it comes back and writes to tables WooCommerce no longer reads. The wider question of which plugins survive an architecture change is in plugin compatibility for headless WooCommerce.

05

Compatibility mode keeps two copies, and the second one costs writes

Compatibility mode is WooCommerce's label for synchronization. While it is on, every order exists in both storages, and changes to the authoritative copy are applied to the backup. Turning it on in an existing store also starts the backfill of old orders: WooCommerce schedules `wc_schedule_pending_batch_process`, which queues `wc_run_batch_process` actions that copy 25 orders at a time. They show up under WooCommerce → Status → Scheduled Actions, and a stuck queue there is an Action Scheduler problem, not an HPOS one. The price is double writes. WooCommerce's guide for large stores reports no noticeable slowdown with sync on and posts still authoritative, and names sync on read as the more resource-hungry half. WooCommerce also refuses to change the authoritative storage while any order is pending sync, which is what stops a half-copied switch.

06

Running the switch on a staging copy first

Copy the production database to staging, with every plugin and custom snippet at its current version, and run the whole sequence there before production sees any of it. Time the sync. WooCommerce's own test store, with 9 million orders, took about a week; the time you measure on staging is the only honest estimate for the live store.

  • Enable compatibility mode with posts still authoritative, then run `wp wc hpos sync` rather than waiting on 25-order batches
  • Run `wp wc hpos verify_data`, and inspect any order it reports with `wp wc hpos diff <order-id>`
  • Test checkout with every payment method, a refund on each, and renewals if WooCommerce Subscriptions is installed
  • Switch Order data storage to HPOS with sync still on and repeat the tests, then turn sync off and repeat them again
  • On production, switch during a quiet sales period and stay online afterward; both directions are hot switches with no downtime
  • Stop sync on read with the `woocommerce_hpos_enable_sync_on_read` filter returning false, then untick compatibility mode. WooCommerce did the first after 6 hours on its high-volume store and the second after one week
07

The wp wc hpos commands, and the two that rewrite data

Every HPOS command now lives under `wp wc hpos`. The older `wp wc cot` namespace has been deprecated since WooCommerce 8.9.0, so a tutorial telling you to run `wp wc cot sync` predates the current tools.

  • `status` reports whether HPOS and compatibility mode are on, with unsynced orders and orders subject to cleanup
  • `count_unmigrated` counts orders pending sync, and `sync` copies them from the active storage to the other
  • `verify_data` compares both storages, and its `--re-migrate` flag overwrites every order that differs
  • `diff <id>` shows one order's differences, and `backfill <id> --from=posts --to=hpos` moves a whole order or chosen fields
  • `cleanup` deletes legacy order data from the posts tables once HPOS is on and sync is off
08

Back up before cleanup or re-migrate

`cleanup` and `verify_data --re-migrate` change order data permanently. Run either one only after a database backup you have actually restored once, and only after the same command has finished clean on a staging copy. Once the legacy rows are gone, returning to posts storage means waiting for a full backfill from the HPOS tables, so the cleanup belongs at the end of the project, weeks after the switch.

09

Code that still reads orders from wp_posts

HPOS breaks things quietly. Code that calls `get_post_meta()` on an order ID, runs `WP_Query` for `shop_order`, or sends its own SQL to wp_posts keeps working while sync is on, because the posts copy is still current. Turn sync off and the same code reads stale orders or writes changes WooCommerce never reads. The fix is WooCommerce's own API, which works on either storage: `wc_get_order()`, `$order->update_meta_data()` followed by `$order->save()`, and `wc_get_orders()` for queries. Admin customizations fail more visibly. The HPOS order list lives at `admin.php?page=wc-orders` instead of `edit.php?post_type=shop_order`, so columns added through `manage_shop_order_posts_columns` disappear and old bookmarks open the wrong screen. A read-only query shows which state you are in: with HPOS authoritative and sync off, `SELECT post_type, COUNT(*) FROM wp_posts WHERE post_type LIKE 'shop_order%' GROUP BY post_type` lists new orders as `shop_order_placehold`, ID placeholders with no order data behind them.

10

Going back to posts storage

Rollback is a settings change, as long as sync stayed on. With compatibility mode enabled, select "WordPress posts storage (legacy)" under Order data storage and save; WooCommerce switches at once. If sync was already off, turn compatibility mode back on and wait until no order is pending, because WooCommerce blocks the switch until then. `wp wc hpos disable` refuses for the same reason and tells you to run `wp wc hpos sync` first.

11

What our audit checks before you flip the setting

The free store audit reads your Features screen and the `compatibility-info` output, lists custom code and outside systems that touch orders directly, and tells you whether your order history makes the switch worth scheduling now. It does not perform the migration. Running it on staging, watching the sync and holding the rollback window open fits under the Care Plan, from $150/mo; the rest is on pricing.

Frequently asked questions

Still have questions?

Reach out and we'll get back to you within 24 hours.

Contact us
Is HPOS enabled by default in WooCommerce?

Yes, for stores installed on WooCommerce 8.2 (October 2023) or later. Older stores stay on posts storage until someone switches them under WooCommerce → Settings → Advanced → Features.

How do I check HPOS plugin compatibility?

Open WooCommerce → Settings → Advanced → Features and follow View and manage, or run wp wc hpos compatibility-info. Neither sees custom code, snippets or plugins without a WC tested up to header.

What are the HPOS tables called?

wp_wc_orders, wp_wc_order_addresses, wp_wc_order_operational_data and wp_wc_orders_meta, with your own table prefix. Order line items stay in wp_woocommerce_order_items.

How long should HPOS sync stay on after migration?

Until checkout, refunds and every plugin that touches orders have run on HPOS without problems. On its own high-volume migration WooCommerce kept sync on for a week.

Does HPOS make WooCommerce faster?

For order writes, admin order search and reporting, yes: WooCommerce's benchmark measured about five times faster order creation. Catalog and product pages do not change.

Can I delete old order data after the HPOS migration?

wp wc hpos cleanup removes it once sync is off. It is destructive, so take a backup you have restored, run it on staging first, and expect a full backfill if you ever roll back.

Related reading
  • Slow WooCommerce admin

    WooCommerce admin slow? Check Analytics imports, orders on posts tables, autoloaded options, Action Scheduler, Heartbeat and plugins that call home.

  • Action Scheduler

    WooCommerce Action Scheduler: why actions go past due, how to read the logs, a server cron with WP-CLI, and the 31-day cleanup that keeps the tables small.

  • Maintenance and support

    Maintenance for a live WooCommerce store: tested backups, staged updates with rollback, uptime and checkout monitoring, security response and speed watch.

See how many sales your store is losing

Start with a free speed audit. You'll get your store's real numbers and an honest recommendation — even if it's "you don't need us".

Anything you already suspect — checkout feels slow on phones, the category pages crawl, you were told the theme is the problem. Guesses are useful; we check them.

No sales call — you'll get a written report with your store's numbers.

By requesting an audit you accept our Privacy policy and Terms of service. We may publish an anonymised summary of the findings as an example of our work. We never name your business without your written consent.