WooCommerce Development

Guidelines for developing WooCommerce plugins and themes with HPOS (High-Performance Order Storage) compatibility.

Last modified: February 12, 2026

link HPOS Compatibility

All WooCommerce sites in this project use High-Performance Order Storage (HPOS), which stores orders in custom database tables instead of the wp_posts table. This requires specific coding patterns to ensure compatibility.

link ✅ Correct Patterns

link Order Meta Access

Always use WC_Order object methods:

// ✅ CORRECT - HPOS compatible
$order = wc_get_order( $order_id );
$value = $order->get_meta( 'meta_key' );
$order->update_meta_data( 'meta_key', 'value' );
$order->save();

Never use WordPress post meta functions directly:

// ❌ WRONG - Not HPOS compatible
$value = get_post_meta( $order_id, 'meta_key', true );
update_post_meta( $order_id, 'meta_key', 'value' );
delete_post_meta( $order_id, 'meta_key' );

link Order ID Access

// ✅ CORRECT
$order_id = $order->get_id();

// ❌ WRONG - Deprecated
$order_id = $order->id;

link Custom Admin Columns

When adding custom columns to the orders admin screen, you must register both legacy and HPOS hooks:

// Legacy post-based orders
add_filter( 'manage_edit-shop_order_columns', 'add_column' );
add_action( 'manage_shop_order_posts_custom_column', 'render_column', 10, 2 );

// HPOS orders
add_filter( 'manage_woocommerce_page_wc-orders_columns', 'add_column' );
add_action( 'manage_woocommerce_page_wc-orders_custom_column', 'render_column_hpos', 10, 2 );

function render_column( $column_name, $post_id ) {
    $order = wc_get_order( $post_id );
    // Render column content
}

function render_column_hpos( $column_name, $order ) {
    // $order is already a WC_Order object
    // Render column content
}

See examples:

link Automatic Compatibility Checking

link Command Line

Run HPOS compatibility check manually:

composer check-hpos

This scans for:

link Pre-commit Hook

The HPOS check runs automatically on WooCommerce files via husky pre-commit hook configured in package.json:

"lint-staged": {
  "packages/wordpress/plugins/*woocommerce*/**/*.php": [
    "./tools/check-hpos-compat.sh"
  ],
  "packages/wordpress/themes/nettbutikk/**/*.php": [
    "./tools/check-hpos-compat.sh"
  ]
}

link CI/CD

GitHub Actions runs the check on all pull requests. See .github/workflows/lint.yaml.

link Common Pitfalls

link 1. Direct Database Queries

// ❌ WRONG - Queries wp_posts table
$wpdb->get_results("
    SELECT * FROM {$wpdb->posts}
    WHERE post_type = 'shop_order'
");

// ✅ CORRECT - Use WooCommerce APIs
$orders = wc_get_orders( [
    'status' => 'completed',
    'limit'  => -1,
] );

link 2. Order Hooks

// ❌ WRONG - Only works with legacy storage
add_action( 'save_post_shop_order', 'callback' );

// ✅ CORRECT - Works with both
add_action( 'woocommerce_update_order', 'callback' );
add_action( 'woocommerce_new_order', 'callback' );

link 3. Admin Editing

// ❌ WRONG - Only works with legacy post-based editing
add_action( 'woocommerce_process_shop_order_meta', 'callback', 10, 2 );

// ✅ CORRECT - Works with HPOS admin editing
add_action( 'woocommerce_process_shop_order_meta', 'callback', 10, 2 ); // Legacy
add_action( 'woocommerce_update_order', 'callback' ); // HPOS

link Plugin Examples

link Compatible Plugins in This Repo

link External Plugins (Verified Compatible)

link External Integrations

Python integrations in integrations/ use the WooCommerce REST API, which is fully HPOS compatible. No code changes are needed for REST API consumers.

# ✅ REST API works identically with HPOS
response = requests.get(
    f'{site_url}/wp-json/wc/v3/orders',
    auth=(consumer_key, consumer_secret)
)

link Resources

link Migration Checklist

When creating or updating WooCommerce plugins: