Skip to content
WooRescueHQ

WooCommerce / HPOS

WooCommerce HPOS: Common Problems With Custom Code

Short answer

With HPOS authoritative, orders live in wp_wc_orders and related tables, not in wp_posts and wp_postmeta. Code that reads or writes orders through post functions, WP_Query or SQL on the posts tables stops working — often silently, returning empty values. Rewrite it with the order API (wc_get_order(), wc_get_orders(), $order->get_meta(), $order->update_meta_data() plus $order->save()), update admin hooks for the new orders screen, and declare HPOS compatibility in your plugins.

Symptoms

  • Order meta read by custom code comes back empty after HPOS is enabled.
  • Custom columns, filters or meta boxes are missing from the new orders screen.
  • Exports, reports or integrations stop including new orders.
  • Values saved by custom code don't appear on the order, or disappear after the next save.
  • WooCommerce shows a notice that active plugins are incompatible and won't let you switch to HPOS.

Most common causes

Pattern Why it breaks
get_post_meta( $order_id, '_my_key', true ) Reads wp_postmeta, which is not the source of truth with HPOS
update_post_meta( $order_id, … ) Writes to the posts storage; HPOS never sees it, or sync overwrites it
new WP_Query( [ 'post_type' => 'shop_order' ] ) / get_posts() Queries wp_posts, where new orders may not exist
$wpdb queries on wp_posts / wp_postmeta for orders Hard-coded legacy schema
wp_insert_post() / wp_update_post() on orders Bypasses the order data stores
manage_edit-shop_order_columns only Legacy admin screen hooks; the HPOS screen uses different ones
add_meta_box( …, 'shop_order' ) Wrong screen id for the HPOS edit screen

Diagnosis

1. Check which storage is authoritative

WooCommerce → Settings → Advanced → Features shows whether orders are stored in HPOS tables or legacy posts tables, and whether compatibility mode keeps both in sync. While sync is on, legacy code may appear to work for reading, which hides the problem until sync is turned off.

2. Search the code

Search the theme, custom plugins, mu-plugins and snippet plugins:

Bash
$ grep -rnE "get_post_meta|update_post_meta|delete_post_meta|wp_insert_post|wp_update_post" wp-content/themes/my-theme wp-content/plugins/my-plugin
$ grep -rnE "shop_order|wp_posts|wp_postmeta|post_type *=" wp-content/themes/my-theme wp-content/plugins/my-plugin

Each hit needs a decision: does it touch orders? Product and page code is unaffected.

3. Check extensions

WooCommerce lists incompatible plugins on the Features settings screen. For each, check whether an update declares compatibility; if not, ask the vendor or plan a replacement.

4. Test on staging with sync on, then off

Enable HPOS on staging with compatibility mode, run the critical flows — checkout, payment notifications, emails, admin edits, exports, integrations — then switch synchronisation off and run them again. Code that only works with sync on is not compatible.

Logs and technical checks

  • The Features screen: authoritative storage, sync status, incompatible plugins.
  • WooCommerce → Status → Logs: fatal-errors after switching storage.
  • Order notes and meta on test orders, compared before and after the switch.
  • Read-only comparisons of the same order in both storages while sync is on.

Solutions

Read and write orders through the order API

PHPmy-plugin/includes/orders.php
// Read
$order = wc_get_order( $order_id );
if ( ! $order ) {
    return;
}
$po_number = $order->get_meta( '_po_number' );

// Write — changes are not stored until save() is called
$order->update_meta_data( '_po_number', sanitize_text_field( $value ) );
$order->save();

// Query
$orders = wc_get_orders( [
    'status'     => [ 'wc-processing' ],
    'limit'      => 50,
    'meta_query' => [
        [ 'key' => '_po_number', 'compare' => 'EXISTS' ],
    ],
] );

Support both admin screens

PHPmy-plugin/includes/admin.php
use Automattic\WooCommerce\Utilities\OrderUtil;

// Columns: legacy screen and HPOS screen
add_filter( 'manage_edit-shop_order_columns', 'myplugin_add_column' );
add_filter( 'manage_woocommerce_page_wc-orders_columns', 'myplugin_add_column' );

add_action( 'manage_shop_order_posts_custom_column', function ( $column, $post_id ) {
    myplugin_render_column( $column, wc_get_order( $post_id ) );
}, 10, 2 );
add_action( 'manage_woocommerce_page_wc-orders_custom_column', function ( $column, $order ) {
    myplugin_render_column( $column, $order );
}, 10, 2 );

// Meta box on whichever order edit screen is active
add_action( 'add_meta_boxes', function () {
    add_meta_box( 'myplugin-box', 'Purchase order', 'myplugin_render_box', wc_get_page_screen_id( 'shop-order' ), 'side' );
} );

function myplugin_render_box( $post_or_order ) {
    $order = $post_or_order instanceof WP_Post ? wc_get_order( $post_or_order->ID ) : $post_or_order;
    // …
}

Declare compatibility in your own plugins

PHPmy-plugin/my-plugin.php
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 );
    }
} );

What not to do

  • Do not declare compatibility to silence the warning before checking the code.
  • Do not turn off compatibility mode right after switching: keep it as a rollback path until the store has run correctly for a while.
  • Do not "fix" missing values by copying meta between tables with SQL.

When to contact an expert

Bring in an engineer when the audit finds order access spread across many files, when an essential extension has no compatible version, when order data already differs between storages, or when the store is large enough that the migration itself needs planning.

Frequently asked questions

How do I know whether my code is HPOS-compatible?

Search it for direct order access: get_post_meta and update_post_meta on order ids, WP_Query or get_posts with shop_order, wp_insert_post for orders, and SQL against wp_posts or wp_postmeta for orders. Code that only uses wc_get_order, wc_get_orders and order object methods is usually compatible.

Why does my custom column disappear from the orders list?

The HPOS orders screen is a different admin page with different hooks. Columns registered with manage_edit-shop_order_columns need the manage_woocommerce_page_wc-orders_columns equivalents as well.

Do I need to declare compatibility?

Yes, for your own plugins. WooCommerce uses the declaration to decide whether HPOS can be enabled. Declare it only after the code has been checked.

Can I still run SQL against orders?

Prefer wc_get_orders(). If you truly need SQL, get the table names from WooCommerce instead of hard-coding wp_posts, and remember the schema differs between storages.

$ describe the problem

Have a WooCommerce problem?Request a diagnosis.

Tell us what is failing, what changed recently and the business impact. We review every request and recommend the appropriate next step.

Request a diagnosis See services and prices

Never send passwords, API keys or card data through the form.

Diagnosis from€299

Request a diagnosis