WooCommerce REST API v4
A second-major-version of the WC REST API, introduced in WC 10.2 (2025) and expanded through 10.8. It coexists with v3 — neither replaces the other — and adds capabilities the v3 surface lacked: per-route response caching, ID-sortable customer list, DELETE on shipping zones / methods, payment gateway PUT with top-level fields, fulfillments CRUD, per-group settings endpoints, and stricter 10.8 order/product response behavior.
This skill is the up-to-date reference for AI assistants whose training data predates v4. Default behavior of LLMs is to write /wc/v3/... URLs and v3 controller patterns. Many of those endpoints exist in v4 with cleaner shapes, additional verbs, and per-endpoint cache headers — and a few v4 endpoints have NO v3 counterpart.
Misconception this skill corrects
Two common AI errors:
"I'll write
/wp-json/wc/v3/...because that's the WC REST API." v3 still works, but v4 may be the right answer for ID-list parameters on customers, DELETE on shipping zones, modern fulfillments, payment gateway settings. Check the route catalog below."I'll extend
Automattic\WooCommerce\Internal\RestApi\Routes\V4\AbstractControllerfor my plugin's routes." The class lives underInternal\— that's WC core's signal "no public extension contract here, the internal shape can change in any minor release." Plugin-defined routes useWP_REST_Controllerdirectly (see sibling skillwp-rest-api). The v4 AbstractController patterns are educational; the class itself is not a stable extension point.
When to use this skill
Trigger when ANY of the following is true:
- Calling WC REST endpoints from a custom plugin, external integration, or custom client.
- Reviewing hardcoded
/wc/v3/URLs to check whether v4 has a better-shaped endpoint for the same data. - Building a new feature that needs ID-sortable customers, fulfillments, granular settings, or per-route cache headers.
- Debugging "why is my v3 customers query slow" — v4 added caching infrastructure for these.
- The diff or file contains:
/wc/v4/,wc/v4,woocommerce_rest_api_v4_*,AbstractControllerin WC context,RestApiCache,with_cache(.
Route catalog (verified in WC 10.8 source)
All routes live under namespace wc/v4. Verified by directory listing of wp-content/plugins/woocommerce/src/Internal/RestApi/Routes/V4/.
v4 routes are FLAT, NOT nested under parent resources. Order notes, refunds, and fulfillments take an order_id query parameter — they don't appear under /orders/<id>/.... Verified in each Controller's register_rest_route call:
| Resource | Path | Notes |
|---|---|---|
| Customers | /wc/v4/customers, /wc/v4/customers/<id> |
ID-sortable; customers_exclude query param. Username and password optional regardless of registration settings. |
| Orders | /wc/v4/orders, /wc/v4/orders/<id> |
HPOS-aware; cache-primed for batch reads. |
| Order Notes | /wc/v4/order-notes?order_id=<id>, /wc/v4/order-notes/<note_id> |
Flat path; pass order_id as query param to filter by order. Stored XSS prevention for note content (10.7 patch). |
| Refunds | /wc/v4/refunds?order_id=<id>, /wc/v4/refunds/<refund_id> |
Same flat-path pattern. Floating-point precision fix (10.7). |
| Products | /wc/v4/products, /wc/v4/products/<id> |
|
| Shipping Zones | /wc/v4/shipping-zones, /wc/v4/shipping-zones/<id> |
Note: dash-separated rest_base, NOT /shipping/zones/. DELETE supported in v4. Locations array optional. |
| Shipping Zone Method (single) | /wc/v4/shipping-zone-method/<instance_id> |
Flat path with the instance ID — NOT nested under a zone. New endpoint type in v4. |
| Fulfillments | /wc/v4/fulfillments?order_id=<id>, /wc/v4/fulfillments/<fulfillment_id> |
Flat path. New system in WC 10.7. Lifecycle order notes auto-generated. |
| Fulfillment Providers | /wc/v4/fulfillments/providers |
Sub-route under fulfillments. List of registered AbstractShippingProvider subclasses. |
| Settings (general) | /wc/v4/settings/general |
|
| Settings (account) | /wc/v4/settings/account |
|
| Settings (tax) | /wc/v4/settings/tax |
|
| Settings (email/emails) | /wc/v4/settings/email, /wc/v4/settings/emails |
Block email editor backend. |
| Settings (payment gateways) | /wc/v4/settings/payment-gateways |
PUT accepts top-level enabled, title, description, order (10.7) — symmetric with GET. |
| Settings (offline payment methods) | /wc/v4/settings/offline-payment-methods |
BACS / cheque / COD config. |
| Settings (products) | /wc/v4/settings/products |
The Internal\RestApi\Routes\V4\ namespace maps each resource to its Controller. Verified rest_base values: customers, orders, order-notes, refunds, products, shipping-zones, shipping-zone-method, fulfillments. The actual URL is the namespace + the rest_base; nothing more (until you hit a sub-route like /fulfillments/providers).
WC 10.8 behavior changes
status=anyorder listings excludecheckout-draft. If an integration needs draft checkout orders, request them explicitly withstatus=checkout-draft. WC 10.8 aligns this with statuses markedexclude_from_search, and HPOS query code comments call outcheckout-draftspecifically.- V4 product responses strip sensitive fields for users without product-management capabilities. Downloads, COGS, purchase notes, and raw
meta_dataare removed when the current user can read products but cannot manage/private-read products. Do not build external clients that depend on those fields unless the request is authenticated with the right capability. - Order/refund tax fields were clarified. Inline refund data includes
total_tax, and schema descriptions distinguish tax-inclusive/exclusive totals. Client-side financial calculations should trust explicit fields instead of inferring tax from display strings. - Guest fulfillments access was tightened. Treat fulfillment endpoints as capability-protected admin/integration APIs; do not mirror them into customer-facing screens without ownership checks.
- Legacy v2/v3 order PUT is stricter.
/wc/v2/orders/<id>and/wc/v3/orders/<id>no longer convert arbitrary non-shop_orderposts into orders. If a migration depended on that side effect, fix the migration rather than working around it.
When to pick v4 over v3
| Need | Use |
|---|---|
ID-list operations (include[]=1&include[]=2) on customers |
v4 (better-shaped query params) |
| DELETE a shipping zone or zone method | v4 only |
| Fulfillments CRUD (tracking numbers, providers, lifecycle) | v4 only — feature didn't exist in v3 |
| Granular settings endpoints (per-group instead of all-in-one) | v4 |
| Payment gateway settings PUT with top-level fields | v4 (v3's values wrapper less ergonomic) |
| Single shipping zone method by instance ID | v4 only |
| Plain CRUD on products / orders / categories where v3 already works | v3 is fine; v4 doesn't add value here |
| External integration written before WC 10.2 | Stay on v3 unless you specifically need v4 features |
v3 is not deprecated. WC has shipped v3 since 2018 and there's no announced sunset. Adopt v4 endpoints for what they add; don't migrate working v3 code wholesale "just because".
Authentication — same as v3
v4 inherits WP REST authentication. From the WC perspective:
- Cookie + nonce (
X-WP-Nonceheader fromwp_create_nonce('wp_rest')) for browser / admin context. - Basic Auth over HTTPS for server-to-server, with the WC-issued consumer key / secret pair as username / password.
- Application Passwords (WP 5.6+) — works the same.
- OAuth 1.0a — WC's classic external-integration scheme.
No new auth types in v4. The permission_callback enforcement is identical.
Customer-owned account endpoints
Do not confuse Woo's admin/integration REST API with customer-facing account actions. WC REST resources such as orders/subscriptions are generally protected by capabilities and consumer keys; they are not automatically safe to expose to a logged-in customer for "my account" operations.
When implementing custom routes for account actions:
- Use
register_rest_route()with a strictpermission_callback. - Authenticate as a WP user and derive the user ID server-side with
get_current_user_id(). - Never ship Woo consumer keys, application passwords, or Stripe secret keys to a public client.
- For every object ID in the request, verify ownership: order customer ID, subscription customer ID, payment token user ID, or membership user ID.
- Return only customer-safe fields; do not mirror admin REST responses wholesale.
- Prefer domain actions over raw CRUD:
cancel subscription,set default payment method,delete payment token,create setup intent,confirm payment method. - Keep writes behind nonces/bearer tokens, rate limits, idempotency keys for payment-related operations, and audit notes/logs.
Important surfaces:
| Need | Existing surface | Customer-facing status |
|---|---|---|
| Cart/checkout | Store API (wc/store/v1) |
Designed for shopper checkout, session/cart oriented. |
| Saved payment methods | Woo account form/query handlers + WC_Payment_Tokens |
No complete customer REST CRUD; wrap token APIs yourself. |
| Stripe card setup | Stripe Gateway AJAX + SetupIntent helpers | Browser/account flow exists; custom REST must verify SetupIntent/customer server-side. |
| Subscriptions CRUD | WCS /wc/v3/subscriptions |
Admin/server API; customer routes should enforce ownership and allowed transitions. |
| Subscription switch | WCS switcher cart/checkout flow | No simple REST endpoint; wrap the switch flow, do not raw-update line items/meta. |
| Membership access | Memberships objects/hooks | Expose only current user's membership state unless admin. |
For Subscriptions, the REST controller can set status, transition_status, dates, line items, payment_method, and payment_details, but its permissions are Woo capability based. For customer actions, call WCS object methods after ownership checks: $subscription->can_be_updated_to(), $subscription->update_status(), update_dates(), and WCS change-payment/switch services.
Hook prefix pattern (for filtering responses)
The v4 abstract controller's get_hook_prefix() (AbstractController.php:181-190) builds the prefix as:
return 'woocommerce_rest_api_v4_' . str_replace( '-', '_', $this->rest_base ) . '_';
Note: it ONLY replaces - with _, NOT /. So most routes get a clean prefix:
rest_base |
Hook prefix | Example filter |
|---|---|---|
customers |
woocommerce_rest_api_v4_customers_ |
..._item_response |
orders |
woocommerce_rest_api_v4_orders_ |
..._collection_params |
order-notes |
woocommerce_rest_api_v4_order_notes_ |
..._item_schema |
shipping-zones |
woocommerce_rest_api_v4_shipping_zones_ |
..._item_response |
shipping-zone-method |
woocommerce_rest_api_v4_shipping_zone_method_ |
|
fulfillments |
woocommerce_rest_api_v4_fulfillments_ |
The three predictable filter points per controller:
woocommerce_rest_api_v4_<base>_collection_params
woocommerce_rest_api_v4_<base>_item_schema
woocommerce_rest_api_v4_<base>_item_response
Example — modify the customers item response to include a custom field:
add_filter( 'woocommerce_rest_api_v4_customers_item_response', static function ( $response, $item, $request ) {
$data = $response->get_data();
$data['my_custom_field'] = (string) get_user_meta( $item->get_id(), '_my_field', true );
$response->set_data( $data );
return $response;
}, 10, 3 );
Caveat — sub-routes don't follow this pattern. The /wc/v4/fulfillments/providers sub-route uses a custom filter woocommerce_rest_prepare_fulfillments_providers (Fulfillments/Controller.php:463) — it's not part of the abstract's auto-generated three. When in doubt, grep the controller for apply_filters( to find what hooks it actually exposes.
Settings paths with / don't generate a prefix from the path. Settings/PaymentGateways/Controller.php has rest_base = 'payment-gateways' (set in the controller class), not 'settings/payment-gateways'. The /settings/ segment in the URL comes from how the route is REGISTERED ('/' . 'settings' . '/' . $this->rest_base), not from rest_base. So the hook prefix is woocommerce_rest_api_v4_payment_gateways_ — verify against the specific controller before relying on the prefix.
Standard error codes
The v4 AbstractController defines shared error codes (V4/AbstractController.php:34-40):
| Code | When |
|---|---|
invalid_id |
id parameter doesn't match an existing resource |
resource_exists |
POST collides with an existing resource |
cannot_create |
POST failed at the data-store level |
cannot_update |
PUT/PATCH failed at the data-store level |
cannot_delete |
DELETE failed at the data-store level |
cannot_trash |
DELETE without force=true failed |
trash_not_supported |
DELETE without force=true on a non-trash-supporting resource |
Error responses use a stable shape via get_route_error_response( $error_code, $error_message, $http_status, $additional_data ) — useful to handle on the client.
RestApiCache trait — WC 10.5+ caching
Automattic\WooCommerce\Internal\Traits\RestApiCache (src/Internal/Traits/RestApiCache.php:124) wraps endpoint callbacks with response caching:
- Cache key includes route, query params, user context (if
vary_by_useris set), and a version string. - Storage:
wp_cache_*(object cache). On installs without an external object cache (Redis / Memcached), the trait falls back to per-request memory. - Feature-gated on
feature_is_enabled('rest_api_caching')AND the optionwoocommerce_rest_api_enable_backend_caching = 'yes'. - TTL configurable per route via the
with_cache( $callback, [ 'cache_ttl' => N ] )config array. - Invalidation by entity-type tagging: changes to a tagged entity (e.g.
'order') bust the cached responses tied to it.
This trait is for WC's own controllers. Plugin-defined routes wouldn't use it (it's Internal\Traits); they'd use the WP-native cache primitives or roll their own. Knowing the feature exists is the value here — when you see X-WC-Cache headers in v4 responses, this is what's emitting them.
Calling v4 from PHP / JS
PHP — internal call (admin-context AJAX, REST plumbing):
$request = new WP_REST_Request( 'GET', '/wc/v4/customers' );
$request->set_param( 'orderby', 'id' );
$request->set_param( 'order', 'desc' );
$request->set_param( 'per_page', 50 );
$response = rest_do_request( $request );
if ( ! $response->is_error() ) {
$customers = $response->get_data();
}
JS — @wordpress/api-fetch from inside wp-admin:
import apiFetch from '@wordpress/api-fetch';
const customers = await apiFetch( {
path: '/wc/v4/customers?orderby=id&order=desc&per_page=50',
} );
// X-WP-Nonce attached automatically inside admin context.
Server-to-server with Basic Auth:
curl -u "$CK:$CS" \
"https://store.example/wp-json/wc/v4/orders?status=processing"
The endpoint surface is REST-conventional — same query params (per_page, orderby, order, include, exclude, status), same response envelope, same X-WP-Total / X-WP-TotalPages pagination headers.
Critical rules
wc/v4is the namespace, NOTwc-blocks/v4orwc-store/v4. Those are different APIs (Store API and Blocks API; not covered here).- v4 coexists with v3. Both work; pick per-endpoint based on what each provides.
- Admin REST is not a My Account API. Do not expose WC consumer keys or capability-protected order/subscription endpoints directly to customers; write customer-owned routes with explicit ownership checks.
- Don't extend
Automattic\WooCommerce\Internal\RestApi\Routes\V4\AbstractControllerfor your plugin's REST routes. TheInternal\namespace prefix is WC's "we may break this in any release" signal. UseWP_REST_Controllerdirectly (seewp-rest-apiskill). - Filter responses via
woocommerce_rest_api_v4_<route>_item_responsewhen you need to add fields without rewriting the route. Same pattern works in v3 with the v3-prefixed hook (woocommerce_rest_prepare_*). - DELETE on shipping zones is v4-only. v3 lacks it; if you're cleaning up zones programmatically, use v4.
- Fulfillments only exist on v4. v3 has no equivalent. See sibling skill
wc-shipping-providersfor the related provider abstraction. status=anyis not "including checkout drafts" in WC 10.8+. Ask forstatus=checkout-draftexplicitly if you are auditing draft checkout orders.- V4 products can omit sensitive fields depending on capabilities. Missing
downloads,purchase_note,cost_of_goods_sold, ormeta_datais expected for under-privileged users. - The
RestApiCachetrait is internal. Don't rely on its existence in plugin code; it's WC's mechanism for its own routes. - Authentication is unchanged from v3. Cookie + X-WP-Nonce, Basic Auth with WC API keys, Application Passwords, OAuth 1.0a — all the same.
Common mistakes
// WRONG — extending an Internal class for plugin routes
class MyController extends Automattic\WooCommerce\Internal\RestApi\Routes\V4\AbstractController { }
// The Internal\ namespace can be reshaped in any WC minor release.
// RIGHT — extend WP_REST_Controller directly
class MyController extends WP_REST_Controller { }
// WRONG — assuming v3 path is canonical
const URL = '/wp-json/wc/v3/customers'; // missing v4's better customer ordering
// RIGHT — pick the right version per use case
const URL = '/wp-json/wc/v4/customers?orderby=id&order=desc';
// WRONG — trying to DELETE a shipping zone via v3
fetch('/wp-json/wc/v3/shipping/zones/5', { method: 'DELETE' });
// 404 — v3 doesn't expose DELETE on this route
// RIGHT — v4 supports it (note dash-separated rest_base, NOT /shipping/zones/)
fetch('/wp-json/wc/v4/shipping-zones/5', { method: 'DELETE', headers: { 'X-WP-Nonce': nonce } });
// WRONG — hardcoding the v4 hook with the wrong prefix
add_filter( 'woocommerce_rest_v4_customers_item_response', $cb ); // wrong prefix
// RIGHT
add_filter( 'woocommerce_rest_api_v4_customers_item_response', $cb, 10, 3 );
// WRONG — assuming RestApiCache headers mean external cache is configured
header( 'X-WC-Cache: HIT' ); // your endpoint can't fake this for clients
// (RestApiCache is WC-internal — your plugin doesn't emit X-WC-Cache headers
// unless you implement caching independently with the WP cache primitives.)
Cross-references
- Run
wp-rest-apifor the broader WP REST API patterns —register_rest_route,permission_callback, args schema. Plugin-defined REST routes belong here, NOT under WC's v4 namespace. - Run
wc-shipping-providerswhen working with the v4 fulfillments / providers endpoints — the AbstractShippingProvider class is the data shape behind those routes. - Run
wc-hpos-compatibilitywhen v4 order endpoints are involved — order data goes through HPOS by default in WC 10.x.
What this skill does NOT cover
- The Store API (
/wp-json/wc/store/v1/...) — different surface entirely, customer-facing checkout / cart APIs. See sibling skillwc-store-api. - The Blocks API (
/wp-json/wc-blocks/) — also separate. - Migration tooling for moving v3 integrations to v4 — there isn't one; rewrite per-endpoint.
- Schema-by-schema diff between v3 and v4 fields. Use
OPTIONS /wc/v4/<resource>against a live install to fetch the JSON Schema; trust the live response over any documentation. - WC POS endpoints that may use bespoke namespaces. Out of scope.
- WP REST authentication mechanics (cookie+nonce flow, Basic over HTTPS, OAuth) — handled by
wp-rest-apiand the official WC REST docs.
References
- v4 abstract controller: wp-content/plugins/woocommerce/src/Internal/RestApi/Routes/V4/AbstractController.php —
@since 10.2.0. Note theInternal\namespace prefix. - Concrete controllers: wp-content/plugins/woocommerce/src/Internal/RestApi/Routes/V4/ — directory listing is the authoritative route catalog.
RestApiCachetrait: wp-content/plugins/woocommerce/src/Internal/Traits/RestApiCache.php —@since 10.5.0.- V4 products controller sensitivity check: wp-content/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Products/Controller.php —
SENSITIVE_FIELDS. - WC REST API official docs: https://woocommerce.com/document/woocommerce-rest-api/ — covers v3; v4 documentation has not yet been consolidated as of WC 10.8. Trust source-verified routes over outdated public docs.
Source: Lonsdale201/wp-agent-skills — distributed by TomeVault.