WooCommerce’s Store API (introduced in WooCommerce 6.9) is the modern, stable REST API for building headless storefronts — it handles cart, checkout, products, and authentication without requiring WordPress cookies, making it ideal for Next.js or Nuxt front ends.
Problem: A Next.js or other JavaScript front end needs to display WooCommerce products, handle cart operations, and process checkout — but the classic WooCommerce REST API is verbose and not optimised for headless use.
Solution: Use the WooCommerce Store API (/wp-json/wc/store/v1/) — it is purpose-built for headless and powers WooCommerce Blocks. Fetch products at build time with getStaticProps, manage cart state with SWR or React Query against the cart endpoint, and submit checkout via a POST to /wc/store/v1/checkout.
The examples below fetch products from the Store API in a Next.js component, manage the cart with nonce authentication, and register a custom Store API route to expose additional product data.
// lib/woo-store-api.js — Store API client for Next.js
const BASE = process.env.NEXT_PUBLIC_WC_URL + '/wp-json/wc/store/v1';
// 1. Fetch products (no auth required)
export async function getProducts( params = {} ) {
const qs = new URLSearchParams( { per_page: 12, ...params } ).toString();
const res = await fetch( `${BASE}/products?${qs}`, { next: { revalidate: 60 } } );
if ( ! res.ok ) throw new Error( `Products fetch failed: ${res.status}` );
return res.json();
}
// 2. Get or create a cart — Store API uses a nonce + cart token
export async function getCart( cartToken = null ) {
const headers = { 'Content-Type': 'application/json' };
if ( cartToken ) headers['Cart-Token'] = cartToken;
const res = await fetch( `${BASE}/cart`, { headers, credentials: 'include' } );
// The response includes a 'Cart-Token' header for stateless carts
const token = res.headers.get( 'Cart-Token' );
const data = await res.json();
return { cart: data, cartToken: token };
}
// 3. Add item to cart
export async function addToCart( productId, quantity = 1, cartToken ) {
const res = await fetch( `${BASE}/cart/add-item`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cart-Token': cartToken,
},
body: JSON.stringify( { id: productId, quantity } ),
} );
return res.json();
}
Extend the Store API with a custom route to expose product metadata:
get( ExtendSchema::class );
$extend->register_endpoint_data( [
'endpoint' => ProductSchema::IDENTIFIER,
'namespace' => 'myplugin',
'data_callback' => function( $product ) {
return [
'lead_time' => get_post_meta( $product->get_id(), '_lead_time_days', true ) ?: 0,
'video_url' => get_post_meta( $product->get_id(), '_demo_video_url', true ) ?: '',
];
},
'schema_callback' => function() {
return [
'lead_time' => [
'description' => 'Delivery lead time in days',
'type' => 'integer',
'readonly' => true,
],
'video_url' => [
'description' => 'Demo video URL',
'type' => 'string',
'readonly' => true,
],
];
},
] );
} );
NOTE: The Store API is the same API used internally by WooCommerce Blocks — so extending it benefits both your headless front end and any Gutenberg checkout blocks on the same site.