---
title: "Migrate WooCommerce to HPOS Safely: A Step-by-Step Guide"
description: "A safe way to switch an older WooCommerce store to High-Performance Order Storage: check plugins, fix custom code, sync with WP-CLI, verify, and clean up."
url: https://adnanshawkat.me/blog/migrate-woocommerce-to-hpos/
---

[← All posts](https://adnanshawkat.me/blog/)

# How to Move a WooCommerce Store to HPOS Without Losing Orders

5 November 2026 · WooCommerce · WordPress · Performance · Maintenance

![Order receipts moving from an old filing cabinet into a modern database, next to a shopping bag and a checklist](https://adnanshawkat.me/images/blog/woocommerce-hpos-migration.webp)

If your WooCommerce store was built before late 2023, there’s a good chance it still keeps every order in `wp_posts` and `wp_postmeta`, right next to your blog posts and pages. New stores haven’t worked that way since WooCommerce 8.2, which made High-Performance Order Storage (HPOS) the default.

HPOS moves orders into their own tables. Admin order screens get faster, order queries stop fighting with everything else in `wp_postmeta`, and the data has a structure that actually fits orders. WooCommerce has also been clear that new features are built for HPOS first.

The switch itself is a setting. The risk is in the plugins and custom code that read orders the old way. Here’s the process I use on client stores.

## What actually changes

With the legacy storage, an order is a post of type `shop_order`, and almost everything about it (totals, addresses, payment method) is a row in `wp_postmeta`.

With HPOS, orders live in dedicated tables:

-   `wp_wc_orders` for the order itself
-   `wp_wc_order_addresses` for billing and shipping
-   `wp_wc_order_operational_data` for internal order state
-   `wp_wc_orders_meta` for any extra meta

Code that goes through the WooCommerce order API (`wc_get_order()`, `$order->get_meta()`, `wc_get_orders()`) keeps working either way. Code that reads `wp_postmeta` directly, or runs a `WP_Query` for `shop_order` posts, does not. That’s the code you need to find.

## Step 1: Check where you are

In WordPress, go to **WooCommerce > Settings > Advanced > Features**. You’ll see which order storage is active, whether compatibility mode is on, and a warning if any active plugins haven’t declared HPOS support.

With WP-CLI:

```
wp wc hpos status
```

That tells you if HPOS is enabled, if compatibility mode is on, and how many orders are waiting to sync.

## Step 2: Make a staging copy

Same rule as any risky change: don’t try this first on the live store. Clone it to staging, turn off outgoing email so customers don’t get test order emails, and switch payment gateways to test mode.

Take a full backup of production too, files and database, and make sure you can restore it.

## Step 3: Audit plugins and custom code

Start with the incompatible plugins list on the Features screen. For each one, update it and check the changelog. Most maintained plugins added HPOS support long ago. If one still hasn’t, that tells you a lot about how it’s maintained, and it’s time to look for a replacement.

Then search your custom theme and plugins for the patterns that break:

```
grep -rnE "shop_order|get_post_meta|update_post_meta|wp_postmeta" \
  wp-content/themes/your-theme wp-content/plugins/your-plugin
```

Not every hit is a problem, but each one that touches orders needs to change:

-   `get_post_meta( $order_id, '_billing_email', true )` becomes `wc_get_order( $order_id )->get_billing_email()`
-   `update_post_meta()` on an order becomes `$order->update_meta_data()` followed by `$order->save()`
-   A `WP_Query` with `post_type => shop_order` becomes `wc_get_orders()`
-   Direct SQL against `wp_posts` or `wp_postmeta` for orders needs a rewrite on top of the order API

Once your own plugin is clean, declare it compatible so WooCommerce stops flagging it:

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

## Step 4: Turn on compatibility mode and sync

On staging, go back to the Features screen and enable compatibility mode first, while the legacy storage is still active. This keeps both sets of tables in sync, so you can switch back at any point.

Compatibility mode migrates orders in the background, but on a store with tens of thousands of orders that takes a while. WP-CLI is much faster:

```
wp wc hpos count_unmigrated
wp wc hpos sync
```

## Step 5: Verify the data

Before switching anything, check that both storages agree:

```
wp wc hpos verify_data
```

If it reports differences, look at a single order in detail:

```
wp wc hpos diff 12345
```

`wp wc hpos backfill` can copy specific fields from one storage to the other to fix mismatches. Don’t move on until verification is clean.

## Step 6: Switch to HPOS on staging and test

Now set HPOS as the active order storage, with compatibility mode still on. Then test the paths that make money:

-   Place a test order from product page to thank-you page
-   Check the order shows correctly in admin, with addresses, totals and notes
-   Process a refund and change an order status
-   Check order emails, both customer and admin
-   If you sell subscriptions, run a renewal
-   Check anything that reads orders: shipping labels, accounting sync, exports, CRM integrations

Watch `debug.log` the whole time. Errors from a plugin still reading `wp_postmeta` tend to show up here before they show up on screen.

## Step 7: Do it on production

When staging is clean, repeat the same steps on production during a quiet hour: compatibility mode on, sync, verify, switch to HPOS. Keep compatibility mode on for a week or two. If something breaks that you missed, you can switch back without losing orders placed in the meantime.

## Step 8: Turn off compatibility mode and clean up

Once you’re confident, turn off compatibility mode. Syncing two storages costs a write on every order change, and you no longer need it.

The old order data is still sitting in `wp_posts` and `wp_postmeta`. When you’re sure you won’t go back, you can remove it:

```
wp wc hpos cleanup all
```

This is destructive, so take a backup first. WooCommerce checks each order before removing it and stops if the legacy copy looks newer. It keeps small placeholder posts so you can still switch storage later if you ever need to.

## FAQ

### Is HPOS required in WooCommerce?

Not yet for older stores, but it’s been the default for new stores since WooCommerce 8.2, and new WooCommerce features are built with HPOS in mind. Staying on legacy storage gets riskier with each release.

### Will switching to HPOS break my plugins?

Only plugins that read or write orders without the WooCommerce order API. The Features screen lists plugins that haven’t declared compatibility, and a search for `shop_order` and `get_post_meta` in custom code finds most of the rest.

### Can I switch back from HPOS?

Yes, as long as compatibility mode was on so both storages stayed in sync. If orders are pending sync, WooCommerce makes you run `wp wc hpos sync` before it lets you switch.

### How long does the migration take?

The sync itself is usually minutes with WP-CLI, even on large stores. The real time goes into auditing plugins and testing on staging.

## Need a hand?

If your store has years of orders and a pile of custom code, I can audit it, fix the incompatible bits, and run the migration with you. [Get in touch](https://adnanshawkat.me/contact/) or see my [services](https://adnanshawkat.me/services/).

Agency with several client stores still on legacy storage? [Betatech](https://betatech.co) works as your white-label WordPress team, NDA included.

[← Previous post**How to Update PHP on WordPress and WooCommerce Without Breaking Your Site**](https://adnanshawkat.me/blog/update-php-version-wordpress-woocommerce/) [Next post →**Rebuy Is Off the Shopify App Store: What Happened and What to Do**](https://adnanshawkat.me/blog/rebuy-removed-from-shopify-app-store/)

## Questions about this post?

Or a store that needs the same kind of work? Send me a message and I’ll reply within one business day.

[Contact me →](https://adnanshawkat.me/contact/) [See services](https://adnanshawkat.me/services/)
