Making Your WooCommerce Plugin HPOS-Compatible: A Practical Migration Checklist

WooCommerce’s High-Performance Order Storage (HPOS) moved orders out of wp_posts and into purpose-built tables. If your plugin still assumes orders are posts, it either breaks on HPOS stores or blocks the merchant from enabling HPOS at all. When I rewrote SL Bank Pricing for WooCommerce for v2, HPOS compatibility was one of the non-negotiables. Here’s the practical checklist.

Declare compatibility explicitly

WooCommerce won’t assume anything. Until you declare compatibility, stores with HPOS enabled see your plugin flagged as incompatible:

use Automattic\WooCommerce\Utilities\FeaturesUtil;

add_action( 'before_woocommerce_init', function () {
    if ( class_exists( FeaturesUtil::class ) ) {
        FeaturesUtil::declare_compatibility(
            'custom_order_tables',
            __FILE__, // your main plugin file
            true
        );
    }
} );

Declaring it is a promise, not a fix — everything below is what makes the promise true.

Stop treating orders as posts

The whole migration boils down to one habit change: go through the CRUD layer, never through WP post functions.

// ❌ Breaks under HPOS
$order  = get_post( $order_id );
$total  = get_post_meta( $order_id, '_order_total', true );
update_post_meta( $order_id, '_slbp_slip_id', $attachment_id );

// ✅ Works everywhere
$order = wc_get_order( $order_id );
$total = $order->get_total();
$order->update_meta_data( '_slbp_slip_id', $attachment_id );
$order->save();

The subtle one is save() — with post meta you were used to writes being immediate. The CRUD object batches changes until you save, and forgetting that produces “my meta disappeared” bugs that only show up on HPOS stores.

Queries: wc_get_orders, not WP_Query

// ❌ post_type => 'shop_order' finds nothing under HPOS
new WP_Query( [ 'post_type' => 'shop_order', 'meta_key' => '_slbp_bank' ] );

// ✅
wc_get_orders( [ 'meta_key' => '_slbp_bank', 'limit' => 50 ] );

Admin screens and hooks that moved

  • The orders list screen id changes from edit-shop_order to woocommerce_page_wc-orders — anything keyed on screen id (meta boxes, columns, assets) needs both.
  • add_meta_boxes_shop_order style hooks need their HPOS counterparts; register against the screen returned by wc_get_page_screen_id( 'shop-order' ).
  • Direct SQL against wp_postmeta for order data must go — the data simply isn’t there anymore.

Test both modes before you ship

WooCommerce → Settings → Advanced → Features lets you flip between storage modes (keep compatibility mode on while testing so data syncs both ways). My rule for the v2 release: every feature that touches an order — the payment-slip upload especially — gets exercised once in legacy mode and once in HPOS mode before tagging. It caught two bugs the unit tests didn’t.

HPOS is the default for new stores now; “we’ll do it later” is merchant-hostile. If you maintain a WooCommerce plugin and want to sanity-check your migration, happy to compare notes.

Leave a Reply

Your email address will not be published. Required fields are marked *