wc-store-api

Build against the WooCommerce Store API (`/wp-json/wc/store/v1`) for shopper-facing products, cart, checkout, Cart/Checkout Blocks, and headless carts. Covers route choice, Nonce header (`wp_create_no

SKILL.md

GitHub

WooCommerce Store API

The Store API is WooCommerce's shopper-facing REST surface. It powers Cart and Checkout blocks and is the right API for product browsing, current-cart reads/writes, current-customer checkout data, and headless cart/checkout flows.

It is not the admin/integration REST API (/wp-json/wc/v3 or /wp-json/wc/v4). Store API routes are public by design and return data for the current shopper/session only. If you need store settings, arbitrary orders/customers by ID, private product data, or back-office CRUD, use authenticated WC REST (wc/v4) or a custom WP REST route with explicit permissions.

Misconception this skill corrects

"Checkout is WooCommerce REST, so I will call /wp-json/wc/v4/orders from the browser."

That leaks the wrong model. WC REST is capability/consumer-key based and exposes admin-style resources. Store API is cart/session based, uses Nonce or Cart-Token for write protection, and is shaped for blocks/headless storefronts. Do not ship WC consumer keys to a browser to make checkout work.

When to use this skill

Trigger when ANY of the following is true:

  • Building or debugging Cart/Checkout Blocks behavior.
  • Calling /wp-json/wc/store/v1/... from JS, a mobile client, or a headless frontend.
  • Adding extension data under extensions.<namespace> to cart items, cart, checkout, or products.
  • Mutating custom cart state from block checkout via /cart/extensions or extensionCartUpdate.
  • Filtering payment-method availability for block checkout with Store API payment requirements.
  • The diff contains woocommerce_store_api_register_endpoint_data, woocommerce_store_api_register_update_callback, woocommerce_store_api_register_payment_requirements, Cart-Token, wc_store_api, or extensionCartUpdate.

Mental model

Need Use
Public products/product filters for storefront UI Store API /wc/store/v1/products
Current shopper cart read/write Store API /wc/store/v1/cart...
Checkout the current cart Store API /wc/store/v1/checkout
Add data to existing Store API responses woocommerce_store_api_register_endpoint_data()
Let block UI update plugin cart state /cart/extensions via woocommerce_store_api_register_update_callback()
Admin/server CRUD for orders, products, refunds, fulfillments, settings WC REST wc/v4
Plugin-specific private endpoint Custom register_rest_route() with permission_callback

All stable Store API resources are under /wp-json/wc/store/v1. Woo also registers /wc/store as an alias to the current v1 routes, but hardcode /wc/store/v1 in clients so versioning is explicit.

Route map

Source-verified in src/StoreApi/RoutesController.php and src/StoreApi/Routes/V1/:

Area Common routes
Cart GET /cart, POST /cart/add-item, POST /cart/update-item, POST /cart/remove-item, POST /cart/apply-coupon, POST /cart/remove-coupon, POST /cart/update-customer, POST /cart/select-shipping-rate, POST /cart/extensions
Cart collections /cart/items, /cart/items/<key>, /cart/coupons, /cart/coupons/<code>
Checkout GET /checkout, POST /checkout, PUT /checkout, POST /checkout/<id>
Order GET /order/<id> for the current shopper/order key flow, not arbitrary admin lookup
Products /products, /products/<id>, /products/<slug>, /products/collection-data
Taxonomies /products/categories, /products/tags, /products/attributes, /products/attributes/<id>/terms, /products/brands
Reviews/batch /products/reviews, /batch

Private routes under /wc/private and experimental/feature-gated agentic checkout routes are not plugin extension contracts. Do not build public plugin behavior on them.

Nonce, Cart-Token, and sessions

Store API writes do not use X-WP-Nonce / wp_rest. They use a header named Nonce whose value is created with:

wp_create_nonce( 'wc_store_api' );

Cart routes send fresh headers with each response:

  • Nonce
  • Nonce-Timestamp
  • Cart-Token
  • Cart-Hash
  • User-ID
  • Cache-Control: no-store

For update methods (POST, PUT, PATCH, DELETE), AbstractCartRoute requires Nonce unless a valid Cart-Token header is present. Missing nonce returns woocommerce_rest_missing_nonce (401); invalid nonce returns woocommerce_rest_invalid_nonce (403).

Headless flow:

curl -i https://store.example/wp-json/wc/store/v1/cart
# Save the Cart-Token response header.

curl -H "Cart-Token: $CART_TOKEN" \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{"id":123,"quantity":1}' \
  https://store.example/wp-json/wc/store/v1/cart/add-item

Same-site browser flow can use cookies + the Nonce header returned by the API. A Cart-Token is usually easier for headless clients because it avoids cookie affinity and also bypasses the nonce requirement for cart/checkout updates.

Store API CORS is stricter than the default WP REST behavior because cart/checkout responses can include shopper data. Cart-Token and Nonce are allowed request headers; only Cart-Token is exposed in CORS responses. A valid cart token can allow access from origins that would otherwise fail origin checks.

Extending response data

Register extension data after woocommerce_blocks_loaded; the Store API container is not ready before then. Use the helper functions rather than instantiating ExtendSchema yourself.

Extensible endpoint identifiers:

Identifier Constant data_callback args
cart CartSchema::IDENTIFIER none
cart-item CartItemSchema::IDENTIFIER $cart_item
checkout CheckoutSchema::IDENTIFIER none
product ProductSchema::IDENTIFIER WC_Product $product

Example: add public product badge data under extensions.myplugin:

use Automattic\WooCommerce\StoreApi\Schemas\V1\ProductSchema;

add_action( 'woocommerce_blocks_loaded', static function (): void {
    woocommerce_store_api_register_endpoint_data( array(
        'endpoint'        => ProductSchema::IDENTIFIER,
        'namespace'       => 'myplugin',
        'data_callback'   => static function ( WC_Product $product ): array {
            return array(
                'badge' => (string) $product->get_meta( '_myplugin_badge' ),
            );
        },
        'schema_callback' => static function (): array {
            return array(
                'badge' => array(
                    'description' => __( 'Short public badge text.', 'myplugin' ),
                    'type'        => array( 'string', 'null' ),
                    'readonly'    => true,
                ),
            );
        },
        'schema_type'     => ARRAY_A,
    ) );
} );

Rules for extension data:

  • Namespace is required and should be your plugin slug.
  • data_callback and schema_callback must return arrays. Returning anything else is logged and becomes empty data for non-admins.
  • Data appears under the endpoint's extensions object, not as a top-level field.
  • Do not mutate cart/order state from a response data_callback; keep it read-only.
  • Do not expose secrets, private settings, arbitrary customer/order lookups, or admin-only product data. Store API is public.

Updating cart state from blocks

Use /cart/extensions for plugin state that the shopper can change in cart/checkout UI: gift wrap, delivery instruction, insurance toggle, pickup choice, custom fee option, etc.

PHP registration:

add_action( 'woocommerce_blocks_loaded', static function (): void {
    woocommerce_store_api_register_update_callback( array(
        'namespace' => 'myplugin',
        'callback'  => static function ( array $data ): void {
            $gift_wrap = ! empty( $data['gift_wrap'] );
            WC()->session->set( 'myplugin_gift_wrap', $gift_wrap );
        },
    ) );
} );

JS call from a checkout/cart block extension:

import { extensionCartUpdate } from '@woocommerce/blocks-checkout';

await extensionCartUpdate( {
    namespace: 'myplugin',
    data: { gift_wrap: true },
    overwriteDirtyCustomerData: {
        shipping_address: false,
        billing_address: false,
    },
} );

/cart/extensions loads the cart, runs your callback with data, recalculates totals, and returns the updated cart response. In WC 10.8, overwriteDirtyCustomerData can be a boolean or an object with shipping_address / billing_address booleans, so extensions can avoid overwriting the shopper's unsaved address edits independently.

Payment requirements

Payment requirements are extra cart-wide support flags. Store API merges your returned strings with the default requirement products and compares them against each gateway's $supports array.

add_action( 'woocommerce_blocks_loaded', static function (): void {
    woocommerce_store_api_register_payment_requirements( array(
        'data_callback' => static function (): array {
            if ( myplugin_cart_requires_saved_card() ) {
                return array( 'tokenization' );
            }
            return array();
        },
    ) );
} );

This filters which gateways are valid for the current Store API cart. It does not register gateway UI. Checkout Block payment UI is still a JS payment-method integration; the PHP gateway class still owns process_payment().

Validation and quantity hooks

For Store API add-to-cart validation, prefer:

add_action( 'woocommerce_store_api_validate_add_to_cart', static function ( WC_Product $product, array $request ): void {
    if ( myplugin_product_is_locked( $product ) ) {
        throw new Exception( __( 'This product cannot be added to the cart.', 'myplugin' ) );
    }
}, 10, 2 );

Quantity constraints shown in Store API cart/item schemas come from QuantityLimits. Filter:

woocommerce_store_api_product_quantity_minimum
woocommerce_store_api_product_quantity_maximum
woocommerce_store_api_product_quantity_multiple_of
woocommerce_store_api_product_quantity_editable

Each receives the value, WC_Product $product, and optional $cart_item.

Product endpoint notes for 10.8

GET /wc/store/v1/products is public and cache-sensitive. WC 10.8 fixed transient bloat caused by arbitrary product IDs in the related query parameter. In 10.8 source, related is an integer product ID, sanitized with absint, and invalid/non-visible products throw woocommerce_rest_product_not_found.

Rules:

  • Pass a single product ID to related, not arrays or comma lists.
  • Do not use Store API products as a private catalog endpoint. Visibility/readability checks still matter.
  • Use WC REST wc/v4/products with proper auth when you need admin/private product fields.

Critical rules

  • Store API is wc/store/v1, not wc/v4. Use Store API for shopper cart/checkout; use WC REST for admin/integration CRUD.
  • Never ship WC consumer keys or secret gateway keys to a public client.
  • Use header Nonce, action wc_store_api; not X-WP-Nonce, action wp_rest.
  • Use Cart-Token for headless cart continuity. Get it from GET /cart, send it back as a request header.
  • Register Store API extension callbacks on woocommerce_blocks_loaded.
  • Keep extension data public and read-only. Use /cart/extensions for mutations.
  • Return arrays from Store API callbacks. Non-array return values are logged and dropped.
  • Namespace extension data. Do not write top-level response fields or generic namespaces like custom.
  • Do not subclass internal Store API route classes for plugin endpoints. Use register_rest_route() for your own routes; use Store API helpers only where WC exposes extension points.
  • Do not disable nonce checks outside local/dev testing. woocommerce_store_api_disable_nonce_check is a development escape hatch, not production configuration.
  • Use Store API rate-limit hooks for public write-heavy Store API flows. woocommerce_store_api_rate_limit_options, woocommerce_store_api_rate_limit_id, and woocommerce_store_api_rate_limit_exceeded affect Store API requests; unrelated custom REST routes need their own limits.

Cross-references

  • Run wc-rest-api-v4 when the task is admin/integration REST, fulfillments, settings, private product/order data, or server-to-server API clients.
  • Run wc-customer-and-sessions when code uses WC()->session, WC()->customer, or needs to understand frontend/REST session bootstrapping.
  • Run wc-payment-gateway when Store API payment requirements intersect with a gateway's $supports and process_payment().
  • Run wc-hpos-compatibility when checkout/order code stores custom order meta.
  • Run wp-rest-api when creating custom plugin endpoints outside Store API.

What this skill does NOT cover

  • Full React payment-method UI registration (registerPaymentMethod, express methods, saved-token components). This skill covers Store API server-side availability and data extension points.
  • Admin REST wc/v4 route details. Use wc-rest-api-v4.
  • Store API internals/private route implementation. Internal classes are useful source references, not a public inheritance contract.
  • GraphQL or the newer dual-code API experiments in WC 10.8.

References