WooCommerce 9.8 (March 2026) made High-Performance Order Storage the only supported path for new installs and pushed the legacy posts-table sync into deprecation. If your NetSuite integration was written before mid-2024, there’s a good chance it’s still reading orders from wp_posts and wp_postmeta. It probably still works. It also probably won’t survive the 10.0 release later this year.
What this post covers: a checklist of every place HPOS quietly changes the contract for a NetSuite sync, with the code patterns to migrate each one. Written from the migration we’ve done across 14 mid-market stores in the last 18 months.
What HPOS actually changed
Until HPOS, every WooCommerce order was a custom post type. Order line items lived in wp_woocommerce_order_items; everything else — totals, billing address, shipping, payment method, refund references — was rows in wp_postmeta. It worked, but querying it at any scale was painful.
HPOS splits orders into four dedicated tables: wp_wc_orders, wp_wc_order_addresses, wp_wc_order_operational_data, and wp_wc_orders_meta. Reads are 30–50× faster on large stores. The catch is that direct SQL against wp_posts for orders no longer returns anything once compatibility-mode sync is disabled — which is the default in 9.8.
The seven places integrations break
1. Direct wp_posts queries in the cron worker
The classic pattern — “every five minutes, find orders with status processing and no _netsuite_synced meta, push to NetSuite” — is almost always implemented as a raw SQL join against wp_posts and wp_postmeta. After HPOS, that query returns zero rows.
// Before (breaks under HPOS)
$order_ids = $wpdb->get_col("
SELECT p.ID FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} m
ON p.ID = m.post_id AND m.meta_key = '_netsuite_synced'
WHERE p.post_type = 'shop_order'
AND p.post_status = 'wc-processing'
AND m.meta_id IS NULL
LIMIT 50
");
// After
$order_ids = wc_get_orders([
'status' => 'processing',
'limit' => 50,
'return' => 'ids',
'meta_query' => [[
'key' => '_netsuite_synced',
'compare' => 'NOT EXISTS',
]],
]);
The HPOS-safe path goes through wc_get_orders() and the CRUD layer. Slower per call than raw SQL, but it’s the only API that survives both backends.
2. get_post_meta() on order IDs
Anywhere your code does get_post_meta($order_id, '_netsuite_id', true) needs to become $order->get_meta('_netsuite_id'). The post-meta call returns null on HPOS stores. WooCommerce does not warn you.
3. The save_post hook
Many integrations trigger a sync on save_post_shop_order. That hook no longer fires on HPOS stores. Replace with:
add_action('woocommerce_after_order_object_save', function($order) {
// push to NetSuite
});
// And for status changes specifically:
add_action('woocommerce_order_status_changed', function($order_id, $from, $to, $order) {
// ...
}, 10, 4);
4. Refund handling
Refunds used to be child posts of the order. Under HPOS they’re rows in wp_wc_orders with type = 'shop_order_refund' and a parent_order_id column. If your NetSuite credit-memo flow walks get_children(), swap it for $order->get_refunds().
5. Custom order statuses
Custom statuses still work, but the wc- prefix handling changed. The wp_wc_orders.status column stores the unprefixed value (awaiting-ns, not wc-awaiting-ns). Code that filters statuses by string prefix needs adjusting.
6. Bulk meta updates
“Mark these 5,000 orders as synced” with a single SQL UPDATE wp_postmeta is gone. The HPOS meta table is wp_wc_orders_meta, but you should not touch it directly — its schema is allowed to change. Use the data store: WC_Data_Store::load('order')->update_meta() in a batched job.
7. Reporting and admin filters
Custom admin columns and filters added via manage_edit-shop_order_columns only render on the legacy screen. The HPOS orders screen uses woocommerce_shop_order_list_table_columns. If your NetSuite plugin adds a “Sync status” column for ops, you’ll need both filters for a transition period.
⚠ The silent-failure trap: WooCommerce can run in “compatibility mode” where both tables stay in sync. It’s a migration aid, not a permanent home. Every store we’ve seen leave it on permanently eventually hits a race condition where the legacy table is hours behind. Migrate the code, then turn compatibility off.
A safe migration order
- On a staging clone, enable HPOS with compatibility mode on. Run a full daily sync. Diff the NetSuite side against production — you’ll catch most of the API mismatches here.
- Replace every
get_post_metaon order IDs with$order->get_meta(). Grep for it; this is the single most common bug. - Move cron workers to
wc_get_orders(). Benchmark — they’ll be slower per query but more predictable. - Replace
save_posthooks with the WooCommerce equivalents. - Turn compatibility mode off in staging. Run another full sync cycle. If it still passes, schedule production.
- In production: enable HPOS with compatibility on, observe for 7 days, then disable compatibility.
The cost of waiting
WooCommerce 10.0 is on the roadmap for Q4 2026 and the project has said the legacy storage will be removed in a subsequent release. The integrations we’ve migrated proactively took 2–4 weeks of focused work. The ones we’ve migrated under pressure — store offline, NetSuite out of sync, finance ringing — have taken twice that and cost real revenue.
If your NetSuite ↔ WooCommerce code was written before mid-2024 and nobody has audited it for HPOS, that audit is the highest-leverage hour of engineering work you’ll do this quarter.