WooCommerce

Migrating WooCommerce to High-Performance Order Storage

What High-Performance Order Storage changes, how to check plugin compatibility, and the order of operations for migrating a live store without losing orders.

Abstract artwork of stacked data layers representing WooCommerce order tables
In this article

For most of its life, WooCommerce stored every order as a WordPress post, with every order detail as a row of post meta. It worked, but a store with a hundred thousand orders ended up with millions of meta rows sharing a table with blog posts, pages and every plugin's data. Order searches, admin lists and reports slowed down accordingly.

High-Performance Order Storage, or HPOS, is WooCommerce's fix: dedicated tables built for orders. New stores have used it by default since WooCommerce 8.2, but plenty of older stores are still on legacy storage. This is the runbook I use to move a live store across without losing an order or a night's sleep.

What HPOS moves, and why order queries got slow

The legacy model put each order in wp_posts with a post type of shop_order, and every field — billing address, totals, payment method, customer ID — in wp_postmeta as a separate key-value row. Finding 'all processing orders for this customer' meant joining a huge meta table to itself several times on unindexed values.

HPOS replaces that with four purpose-built tables: wp_wc_orders for the core order record, wp_wc_order_addresses for billing and shipping, wp_wc_order_operational_data for internal state, and wp_wc_orders_meta for anything that is genuinely extra. Columns such as status, customer ID and date are real, indexed columns, so the database can answer common questions directly.

The practical gains show up in the orders screen, in order search and in any code that queries orders by status or customer. It also takes a large amount of write traffic away from the posts tables, which helps the rest of the site during sales.

Auditing plugins before anything else

Plugins that use WooCommerce's own CRUD functions — wc_get_order, wc_get_orders, the WC_Order methods — work with either storage system without changes. The problem is code that skips those functions and reads orders with get_posts, WP_Query or get_post_meta, or with raw SQL against wp_posts.

WooCommerce makes the first pass easy. Under WooCommerce, Settings, Advanced, Features, the order data storage option lists any active plugin that has not declared HPOS compatibility, and it will not let you switch while incompatible plugins are active. Plugin authors declare support through FeaturesUtil::declare_compatibility, so a missing declaration usually means 'untested' rather than 'broken'.

  • Update every WooCommerce extension first. Most popular extensions declared compatibility long ago, and the warning often disappears after updates alone.
  • For each plugin still flagged, check its changelog and support forum for HPOS. If the author has abandoned it, that is worth knowing regardless of this migration.
  • Search your theme and any custom code for shop_order, get_post_meta on order IDs, and SQL that mentions wp_posts. Each hit needs rewriting to use wc_get_order and the order object's getters.
  • Do not tick the box that ignores incompatible plugins on a live store. It exists for testing, not for production.

Running compatibility mode: both tables in sync

Compatibility mode, labelled as synchronising orders to the posts table, is the safety net that makes this migration low-risk. With it on, WooCommerce writes every order to both storage systems: the authoritative copy and a mirror. You can switch which one is authoritative and the other stays current.

Turn it on while legacy storage is still authoritative. WooCommerce then backfills the new tables in the background using Action Scheduler, in batches, so the store keeps trading while it catches up. On a store with years of history this can take hours; watch progress under Tools, Scheduled Actions, or ask WP-CLI with wp wc hpos count_unmigrated.

On large stores I run the backfill from the command line instead with wp wc hpos sync. It is faster, it does not depend on site traffic to trigger the queue, and you can see exactly where it got to if it stops.

The migration itself, on a store that is still taking orders

Take a full database backup and confirm you can restore it. Then do the whole sequence on a staging copy first, including placing test orders, refunding one and letting a subscription renewal run if you sell subscriptions. Staging is where you discover the one plugin that writes a custom meta key straight into wp_postmeta.

On production, wait until the backfill reports zero unmigrated orders, then run wp wc hpos verify_data, which compares the two copies and reports differences. Fix or explain every difference before you continue. Then switch the authoritative storage to High-Performance Order Storage in the same Features screen, leaving compatibility mode on.

Pick a quiet hour, not because the switch is slow — it is instant — but because you want your first live orders on the new storage to happen while you are watching. Place a real order yourself straight afterwards.

Verifying: counts, reports, refunds and renewals

  • Order counts by status in the admin match what you had before the switch, and older orders open with their full history and notes.
  • Analytics and any accounting export produce the same totals for last month as they did before. Reports that disagree point to a plugin still reading the old tables.
  • A refund on a new order and on an old order both complete, restock correctly and reach the payment gateway.
  • Subscription renewals, if you use them, create renewal orders and charge the stored payment method.
  • Integrations that read orders — ERP, shipping labels, invoicing, CRM — still receive new orders, because they are the most likely place for direct database reads to hide.

Turning sync off, and the rollback you give up

While compatibility mode is on, rolling back is a settings change: switch authoritative storage back to WordPress posts and the mirror is already current. That is the main reason to leave sync running for a while — a week or two of real trading, including a weekend and a month-end, is a sensible window.

Sync is not free, though. Every order write happens twice, which costs some of the performance you migrated for. Once you are confident, turn compatibility mode off. From that point the legacy copies stop updating, and rolling back means another full backfill in the other direction.

Leave the old shop_order posts in place for a while after turning sync off. WooCommerce offers a tool to clean them up later, and there is no prize for deleting your fallback on the first day.

Frequently asked questions

What is High-Performance Order Storage in WooCommerce?

It is WooCommerce's dedicated order storage. Instead of saving orders as WordPress posts with many post meta rows, HPOS uses its own indexed tables for orders, addresses and operational data. Order screens, searches and status queries become much faster, especially on stores with many orders.

Is it safe to enable HPOS on a live store?

Yes, if you do it in stages. Back up, test on staging, turn on compatibility mode so both storage systems stay in sync, let the backfill finish and verify the data, then switch. Because sync keeps the old tables current, you can switch back instantly if something misbehaves.

How do I check if my plugins support HPOS?

Go to WooCommerce, Settings, Advanced, Features. The order storage setting lists active plugins that have not declared compatibility. Update them first, then check the changelog or support forum of anything still listed. Custom code that queries shop_order posts or order post meta directly also needs rewriting.

Can I roll back from HPOS to post storage?

Yes. While compatibility mode is on, rolling back is just switching the storage setting back. If you have already turned sync off, you can still roll back, but WooCommerce has to copy every order back into the posts tables first, which takes time on a large store.

How long does the HPOS migration take?

The switch itself is instant; the backfill is what takes time. A store with a few thousand orders syncs in minutes, while one with hundreds of thousands can take hours through the background queue. Running wp wc hpos sync from WP-CLI is usually much faster.

Topics

  • WooCommerce HPOS migration
  • High-Performance Order Storage
  • WooCommerce custom order tables
  • WooCommerce HPOS compatibility
Share