Files
playbook/antigravity-awesome-skills/skills/woo-guard/references/hpos-and-crud.md
T
2026-07-18 00:02:59 +00:00

3.8 KiB

Woo Guard — HPOS and CRUD Reference

Contents

  • Why HPOS breaks legacy code
  • Order access patterns
  • Order meta done right
  • Products, stock, and lookup tables
  • Status transitions
  • Declaring compatibility
  • Violation table for review

Why HPOS breaks legacy code

High-Performance Order Storage moved orders out of wp_posts/wp_postmeta into dedicated tables, and it is the default on new stores. Code that treats orders as posts returns empty results or stale data on HPOS stores — silently, with no error. Code written "from memory" almost always targets the legacy storage, because that is what most training-era tutorials show.

Order access patterns

// Wrong — assumes orders are posts. Fails on HPOS.
$total  = get_post_meta( $order_id, '_order_total', true );
$orders = get_posts( array( 'post_type' => 'shop_order', 'numberposts' => 20 ) );

// Right — storage-agnostic CRUD API.
$order  = wc_get_order( $order_id );
$total  = $order ? $order->get_total() : 0;
$orders = wc_get_orders( array( 'status' => 'wc-processing', 'limit' => 20 ) );

wc_get_orders() takes its own argument schema (not WP_Query args): limit, status, customer_id, date_created, meta_query equivalents via field_query. Never run an unbounded order query — always set a limit (wp-guard covers query discipline in depth when installed).

Order meta done right

$order = wc_get_order( $order_id );
$order->update_meta_data( '_ncs_sync_status', 'queued' );
$order->save(); // persists to whichever storage backend is active

$value = $order->get_meta( '_ncs_sync_status' ); // single value, unserialized

save() is not optional — without it, meta changes exist only in memory. Batched changes: set everything, save once.

Products, stock, and lookup tables

Product meta writes through update_post_meta() skip wc_product_meta_lookup sync, skip woocommerce_update_product-family hooks, and skip cache invalidation — three classes of bugs other plugins will blame on you.

$product = wc_get_product( $product_id );
$product->set_stock_quantity( $new_qty );
$product->set_regular_price( wc_format_decimal( $price ) );
$product->save();

Stock specifically: wc_update_product_stock() and the CRUD setters handle backorders, stock status flips, and concurrency better than any hand-rolled meta math. Direct increments via meta are race-prone on busy stores.

Status transitions

$order->update_status( 'completed', 'Synced to fulfillment.' ); // fires emails + hooks

Never set status by writing meta or post fields — update_status() triggers the woocommerce_order_status_* hooks and customer emails the rest of the store depends on. If suppressing side effects is the goal, that is a design discussion to surface, not a meta write to sneak in.

Declaring compatibility

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 );
		\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__, true );
	}
} );

Declare only what is true — a false custom_order_tables declaration on postmeta-reading code converts a visible warning into an invisible data bug.

Violation table for review

Grep hit Why it fails
get_post_meta( $order_id HPOS: order meta is not postmeta
post_type => 'shop_order' (or shop_order_refund) HPOS: orders are not posts
update_post_meta( $product_id Skips lookup tables, hooks, caches
wp_update_post on order/product IDs Same class of bypass
$wpdb joins on postmeta for order data HPOS + fragile schema coupling
posts_per_page => -1 over products/orders Unbounded query, store-sized blast radius