HPOS and Block Checkout: what store owners need to know

What HPOS and Block Checkout change in WooCommerce, how to check your plugins before you switch, and the safe way to move your orders without losing data.

Open the main file of any plugin we make at Themology and you’ll find the same call: FeaturesUtil::declare_compatibility(). It tells WooCommerce the plugin works with High-Performance Order Storage. In most of them, a second call says it works with the Checkout block too.

I care about those few lines more than most features, because WooCommerce reads them before it lets a store switch.

If a plugin hasn’t declared HPOS support, WooCommerce treats it as incompatible. The HPOS option on the Features screen goes gray, and a warning names the plugin that’s holding the store back. I don’t want our plugin to be that name.

So this is the guide I’d give any store owner who sees HPOS and Block Checkout in their settings and wonders whether to touch them. What each one changes, how to check your plugins, and how to move without losing an order.

TLDR

HPOS moves WooCommerce orders out of the WordPress posts tables into their own indexed tables. It’s the default for new stores since WooCommerce 8.2. Block Checkout rebuilds checkout as blocks on top of the Store API, and it’s the default since 8.3. Check your plugins under WooCommerce > Settings > Advanced > Features, turn on compatibility mode first so both tables stay in sync, test on staging, and only then switch.

What HPOS changes#

HPOS stands for High-Performance Order Storage. Before it, WooCommerce kept every order in the same two tables WordPress uses for blog posts and pages: wp_posts and wp_postmeta. An order was one row in wp_posts with post_type set to shop_order, plus one row in wp_postmeta for every field: billing name, email, total, payment method, and so on.

That works for a small store. It gets slow as orders pile up, because orders share a table with everything else, and every order query has to join meta rows back together by key.

HPOS gives orders their own tables:

TableWhat it stores
wp_wc_ordersCore order data: status, currency, totals, customer ID, billing email, dates.
wp_wc_order_addressesBilling and shipping addresses, one row per address.
wp_wc_order_operational_dataInternal flags, such as whether stock was reduced or a download granted.
wp_wc_orders_metaCustom order meta that extensions add.

The old way (posts storage)#

wp_posts
├── ID: 1234
├── post_type: 'shop_order'
├── post_status: 'wc-completed'
└── post_date: '2024-01-15'

wp_postmeta (one row per field)
├── _billing_first_name: 'John'
├── _billing_last_name: 'Doe'
├── _billing_email: 'john@example.com'
├── _order_total: '99.99'
└── ... (and many more)

The new way (HPOS)#

wp_wc_orders
├── id: 1234
├── status: 'wc-completed'
├── total_amount: 99.99
├── customer_id: 456
├── billing_email: 'john@example.com'
└── date_created_gmt: '2024-01-15'

wp_wc_order_addresses
├── order_id: 1234
├── address_type: 'billing'
├── first_name: 'John'
└── last_name: 'Doe'

The core fields are columns now, not rows. WooCommerce indexes the ones stores filter by most. In the 11.2.1 schema, wp_wc_orders has keys on status, date_created_gmt, customer_id with billing_email, transaction_id, and type, status and date together.

Two newer HPOS features sit under WooCommerce > Settings > Advanced > Features, both off by default:

HPOS Data Caching: caches order data in the datastore. WooCommerce recommends it for stores that already run a persistent object cache.

HPOS Full text search indexes: builds full text indexes for order search. It’s still marked experimental.

I’d leave the second one alone on a live store until it loses that label.

What Block Checkout changes#

The classic checkout is a PHP template behind the [woocommerce_checkout] shortcode. Every time a shopper changes their address or shipping method, it sends an update_order_review request and swaps in fresh chunks of HTML.

Block Checkout is built from blocks and React. It talks to the Store API, WooCommerce’s public REST API for the cart and checkout, and updates only the parts of the page that changed.

You edit it like any other page. These are some of the inner blocks you can move, hide or style:

BlockWhat it shows
Contact InformationEmail field.
Shipping AddressAddress form with validation.
Billing AddressSeparate billing address, or same as shipping.
Shipping MethodsRates for the address entered.
Pickup OptionsLocal pickup locations.
Payment OptionsEvery payment method the store offers.
Express PaymentExpress buttons such as Apple Pay and Google Pay, when your gateway provides them.
Order SummaryCart items, coupon form, fees, taxes and totals.
Order NoteA note from the customer.
Terms and ConditionsThe checkbox and link to your terms.

The Store API can also limit how often someone hits checkout, which helps against card testing bots. It’s off by default. You can turn it on under Features as “Rate limit Checkout”, or read the rate limiting docs for the finer controls.

The classic shortcode still works. If you’re not ready, you can keep it, and I explain how near the end.

The catch is your plugins#

HPOS and Block Checkout are both fine on a clean store. The risk is the plugins you already run.

For HPOS, a plugin can be in one of three states:

StateWhat it means
CompatibleThe plugin declared it works with HPOS.
IncompatibleThe plugin declared it doesn’t.
UncertainThe plugin hasn’t declared anything. WooCommerce treats it as incompatible.

This is the declaration a plugin adds:

// Plugin declares HPOS compatibility
add_action( 'before_woocommerce_init', function() {
    if ( class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) ) {
        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
            'custom_order_tables',
            __FILE__,
            true  // true = compatible, false = incompatible
        );
    }
});

Block Checkout works the other way around. A plugin that declares nothing is assumed compatible with cart_checkout_blocks, so you won’t get a warning. That’s why a checkout add-on can look fine in settings and still be missing from the page. You only find out by testing.

When I tested CAPTCHA plugins on a store with Block Checkout and HPOS turned on, for my WooCommerce CAPTCHA plugin comparison, the results varied more than you’d expect.

Plugins to check first#

The big ones have supported HPOS for a while: WooCommerce Subscriptions, Memberships, Bookings, and the main Stripe and PayPal gateways. These are the ones I’d check first:

  • Order tracking plugins.
  • Custom order export tools.
  • Older PDF invoice plugins.
  • Anything that reads orders with direct wp_posts queries or WP_Query instead of wc_get_orders().
  • Integrations that still use the Legacy REST API. It left WooCommerce core in version 9.0, lives on as a separate plugin, and WooCommerce’s own settings screen warns that it isn’t compatible with HPOS.

How to check your plugins#

  1. Go to WooCommerce > Settings > Advanced > Features.
  2. Look at the Order data storage section.
  3. Read any warning under it. It lists the plugins WooCommerce considers incompatible.

Or use WP-CLI, which gives you the full list in one go:

# Show compatible, incompatible and undeclared plugins
wp wc hpos compatibility-info

# Show whether HPOS and sync are on
wp wc hpos status

compatibility-info has been there since WooCommerce 9.1, and status since 8.6.

How to move to HPOS safely#

Step 1: Back up and check compatibility#

Take a database backup. Run wp wc hpos compatibility-info and fix or replace anything on the incompatible list.

Step 2: Turn on compatibility mode first#

Don’t switch in one step. Go to WooCommerce > Settings > Advanced > Features and tick “Enable compatibility mode (synchronizes orders to the posts table)”.

WooCommerce then copies your orders into the new tables in the background, 25 orders per batch, through Action Scheduler. You can watch the wc_schedule_pending_batch_process and wc_run_batch_process actions under WooCommerce > Status > Scheduled Actions.

Once the sync finishes, choose “High-performance order storage (recommended)” and save. Compatibility mode keeps both tables updated, so you can switch back to “WordPress posts storage (legacy)” instantly if something breaks.

Step 3: Test what touches orders#

  • Create a new order.
  • Edit an existing order.
  • Process a refund.
  • Search for orders by customer email.
  • Export orders to CSV.
  • Check every order-related plugin.
  • Confirm webhooks still fire.

Step 4: Finish the move#

When you’re confident, turn compatibility mode off. You can then remove the old order data from the posts tables with wp wc hpos cleanup all. Only do that once you’re sure you won’t go back, because that’s what makes switching back easy.

The full checklist for extension developers is in WooCommerce’s HPOS upgrade recipe book.

Speed up order screens even more#

HPOS helps with orders. It’s one piece of the picture, and I cover the rest in why most WooCommerce stores feel slow, from cart fragments to autoloaded options. For order screens specifically, two free plugins are worth a look.

Index WP MySQL For Speed#

Index WP MySQL For Speed adds better database keys to WordPress tables, including WooCommerce’s wp_wc_orders_meta and wp_woocommerce_order_itemmeta. Its own docs say the gain shows most on larger sites. On big tables, run it from WP-CLI to avoid timeouts:

wp plugin install index-wp-mysql-for-speed --activate
wp index-mysql enable --all

Back up first. wp index-mysql disable --all puts the default keys back.

Fast Woo Order Lookup#

WooCommerce’s order search runs a LIKE '%term%' match, which gets slow with a lot of orders. Fast Woo Order Lookup builds a trigram index table in the background and searches that instead. The trade-off is disk space: its FAQ puts the table at about 5 to 10 KiB per order.

Object caching#

A persistent object cache keeps repeated query results in memory between requests. It’s also what HPOS Data Caching is meant to sit on.

OptionGood fit
Redis Object CacheMost hosts that offer Redis.
MemcachedHosts that offer Memcached instead.
SQLite Object CacheShared hosting without Redis.

Block Checkout: switching tips#

Check if you’re already on it#

Edit your Checkout page. If you see a Checkout block with inner blocks, you’re on Block Checkout. If you see a shortcode or a “Classic Shortcode” placeholder, you’re on the classic one.

In code, WooCommerce has a helper for it:

// True when the store's checkout page uses the Checkout block
if ( \Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils::is_checkout_block_default() ) {
    // Block Checkout
}

Common issues and fixes#

Custom fields not showing: fields added with woocommerce_checkout_fields or classic templates don’t carry over. Block Checkout has its own additional checkout fields API. Add this to your child theme’s functions.php or a code snippets plugin like WPCode:

// Adds an optional gift message field to the order section of Block Checkout
add_action( 'woocommerce_init', function() {
    woocommerce_register_additional_checkout_field(
        array(
            'id'       => 'my-store/gift-message',
            'label'    => 'Gift message',
            'location' => 'order', // contact, address or order
            'type'     => 'text',
        )
    );
} );

Payment gateway not appearing: the gateway needs a Block Checkout integration. That’s a class that extends AbstractPaymentMethodType, registered on the woocommerce_blocks_payment_method_type_registration hook. If your gateway doesn’t have one, ask its developer or check for an update. WooCommerce’s payment method integration guide is what they’ll need.

Checkout hooks not firing: hooks like woocommerce_before_checkout_form live in the classic checkout/form-checkout.php template. Block Checkout doesn’t load that template, so they never run. WooCommerce keeps a list of hook alternatives for the common ones.

Keeping the classic checkout for now#

Not ready to switch? WooCommerce’s steps for reverting to the classic checkout are:

  1. Edit your Checkout page.
  2. Open the List View and select the Checkout block.
  3. Click the Transform button at the left of the block toolbar.
  4. Choose “Classic Shortcode”.
  5. Save.

The Cart and Checkout blocks work together, so WooCommerce recommends reverting both.

Preparation checklist#

Before enabling HPOS#

  • Back up your database.
  • List every installed plugin.
  • Run wp wc hpos compatibility-info or read the Features screen warning.
  • Test on staging first.
  • Turn on compatibility mode instead of switching directly.
  • Run order search tests.
  • Confirm order editing works.
  • Check order exports.
  • Test refunds and cancellations.
  • Confirm webhooks fire.

Before switching to Block Checkout#

  • Review your current checkout customizations.
  • Check payment gateway support.
  • Test every custom checkout field.
  • Check email notifications.
  • Test the order confirmation page.
  • Check the page on a phone.
  • Confirm tracking pixels fire.
  • Test express payment buttons.

What’s default today#

New stores get both. HPOS has been the default for new installs since WooCommerce 8.2, and the Cart and Checkout blocks since 8.3, released in November 2023. Older stores keep whatever they had until someone switches.

Posts storage is still there. WooCommerce labels it “legacy” on the Features screen, but you can still pick it. The classic checkout shortcode still works too.

Frequently asked questions#

Is HPOS required now?#

Not for existing stores. It’s the default for new stores since WooCommerce 8.2. Existing stores can stay on posts storage, which WooCommerce now labels “legacy” but still supports.

Will I lose order data?#

No. The sync copies orders into the new tables. It doesn’t delete anything from the old ones, and compatibility mode keeps both updated while you test.

Can I switch back to posts storage?#

Yes, while compatibility mode is on. With both tables in sync, switching back is instant. Once you turn sync off and clean up the old data, the easy way back is gone, so keep a backup from before the cleanup.

Do I need HPOS for Block Checkout?#

No. They’re separate features. You can run either one without the other.

What if a plugin isn’t compatible?#

  1. Check for a plugin update.
  2. Ask the developer whether HPOS support is planned.
  3. Keep posts storage with compatibility mode on until it’s ready.
  4. Look for an alternative that declares support.

How long does migration take?#

It depends on how many orders you have and how fast your server is. WooCommerce syncs 25 orders per batch in the background, and each batch schedules the next. For large stores, WP-CLI is faster, and it lets you set the batch size:

# For large stores
wp wc hpos sync --batch-size=500

500 is also the command’s default.

Will Block Checkout affect my SEO?#

No. The checkout page keeps its URL, and WooCommerce already tells search engines not to index the cart, checkout and My Account pages. Your checkout was never meant to rank.

Can I customize Block Checkout’s appearance?#

Yes. Use the editor, your theme’s theme.json, the Site Editor on a block theme, or plain CSS. The inner blocks use standard WordPress block styling.

Where I’d start#

If you only do one thing today, run wp wc hpos compatibility-info on a staging copy, or open the Features screen and read the warning. That one list tells you whether HPOS is a ten-minute switch or a conversation with a plugin developer.

That list is also why our plugins declare support from the start. Bouncer, Evenzo and Emboss declare both HPOS and Block Checkout. Raffle for WooCommerce declares HPOS and handles Block Checkout through the Store API.

For the security side of Block Checkout, I’d read how to stop fake orders in WooCommerce and the WooCommerce security checklist next. And if you have a question about either feature, you can contact us or check WooCommerce’s HPOS documentation.

We didn't just write about it. We built something for each.