The WooCommerce REST API is the backbone of every headless WooCommerce implementation I have shipped. WPGraphQL gets the attention for its flexibility, and it deserves some of it, but the REST API remains the most battle-tested, best-documented, and most widely-supported option on the table. If you are building your first production headless store, this is where I would start. Here is what actually matters, in the order it will matter to you.
Authentication
WooCommerce REST API supports three authentication methods, and the mistake I see most often is picking the wrong one for the wrong surface. Match the method to who is calling.
Consumer Key/Secret (Server-to-Server)
Best for server-side rendering and backend operations. Generate keys in WooCommerce > Settings > REST API. Keep these on the server. If a consumer secret ever reaches the browser, treat it as burned and rotate it.
import WooCommerceRestApi from "@woocommerce/woocommerce-rest-api";
const api = new WooCommerceRestApi({
url: "https://your-store.com",
consumerKey: process.env.WC_CONSUMER_KEY,
consumerSecret: process.env.WC_CONSUMER_SECRET,
version: "wc/v3"
});
JWT Authentication (Client-Side)
For customer-facing operations (viewing orders, managing account), use JWT. Install the JWT Authentication plugin on WordPress.
// Login and get token
const auth = await fetch('https://your-store.com/wp-json/jwt-auth/v1/token', {
method: 'POST',
body: JSON.stringify({ username, password })
});
const { token } = await auth.json();
// Use token for authenticated requests
const orders = await fetch('https://your-store.com/wp-json/wc/v3/orders', {
headers: { 'Authorization': Bearer ${token} }
});
Application Passwords (WordPress 5.6+)
Built into WordPress core. Simple but less suitable for public-facing apps.
Essential Endpoints for Headless Stores
Products
GET /wp-json/wc/v3/products # List products
GET /wp-json/wc/v3/products/{id} # Single product
GET /wp-json/wc/v3/products/{id}/variations # Product variations
GET /wp-json/wc/v3/products/categories # Categories
GET /wp-json/wc/v3/products/tags # Tags
GET /wp-json/wc/v3/products/attributes # Attributes (Size, Color, etc.)
Key query parameters:
per_page(max 100),page— paginationcategory— filter by category IDtag— filter by tag IDsearch— keyword searchorderby— date, title, price, popularity, ratingmin_price,max_price— price range filterstock_status— instock, outofstock, onbackorder
Cart (via CoCart or Store API)
WooCommerce’s native REST API doesn’t include cart endpoints. This is the single fact that trips up most first-time headless builds, so plan for it up front. Use one of these:
WooCommerce Store API (Block-based):
GET /wp-json/wc/store/v1/cart
POST /wp-json/wc/store/v1/cart/add-item
POST /wp-json/wc/store/v1/cart/remove-item
POST /wp-json/wc/store/v1/cart/update-item
POST /wp-json/wc/store/v1/cart/apply-coupon
CoCart Plugin:
GET /wp-json/cocart/v2/cart
POST /wp-json/cocart/v2/cart/add-item
DELETE /wp-json/cocart/v2/cart/item/{item_key}
Orders
POST /wp-json/wc/v3/orders # Create order (checkout)
GET /wp-json/wc/v3/orders/{id} # Order details
PUT /wp-json/wc/v3/orders/{id} # Update order
Customers
POST /wp-json/wc/v3/customers # Register
GET /wp-json/wc/v3/customers/{id} # Profile
PUT /wp-json/wc/v3/customers/{id} # Update profile
Building the Checkout Flow
The headless checkout is the most complex part, and it is where I would spend most of your engineering budget. Everything before it is reads; this is where money moves and where a bug becomes a support ticket. Here’s the typical flow:
1. Cart (Store API) → Collect items
- Shipping zones → Calculate shipping
- Payment → Process via gateway
- Create order → POST /orders with all data
- Confirmation → Display order details
Creating an order with payment:
const order = await api.post('orders', {
payment_method: 'stripe',
payment_method_title: 'Credit Card',
set_paid: false, // Payment gateway handles this
billing: {
first_name: 'John', last_name: 'Doe',
address_1: '123 Main St', city: 'New York',
state: 'NY', postcode: '10001', country: 'US',
email: '[email protected]', phone: '555-0123'
},
shipping: { / same structure / },
line_items: [
{ product_id: 93, quantity: 2 },
{ variation_id: 1234, quantity: 1 }
],
shipping_lines: [
{ method_id: 'flat_rate', method_title: 'Standard', total: '5.00' }
]
});
Performance Optimization
1. Field Filtering
Don’t fetch entire product objects when you only need title, price, and image:
GET /wp-json/wc/v3/products?_fields=id,name,price,images
2. Batch Requests
Update multiple products in one call:
await api.post('products/batch', {
update: [
{ id: 1, stock_quantity: 50 },
{ id: 2, stock_quantity: 30 }
]
});
3. Caching Strategy
- Product listings: Cache for 5-10 minutes (ISR in Next.js)
- Single products: Cache for 1-5 minutes
- Cart: Never cache
- Stock levels: Cache for 60 seconds max
4. Pagination
Always paginate. Never fetch all products at once. The response header X-WP-TotalPages tells you how many pages exist.
Common Pitfalls
CORS: WordPress doesn’t enable CORS by default. Add to your theme’s functions.php or use a plugin:
add_action('init', function() {
header('Access-Control-Allow-Origin: https://your-frontend.com');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
});
Rate Limiting: WooCommerce doesn’t have built-in rate limiting, but your hosting provider might. Test under load.
Image URLs: Product images return full WordPress URLs. If your frontend is on a different domain, consider proxying images through your CDN.
REST, Store API, or WPGraphQL: how I would actually choose
People ask me to settle the REST-versus-GraphQL argument as if one has to win. It does not. On a real headless WooCommerce build you almost always end up using two of them together, and the useful question is which does what.
- Reach for the core REST API for catalog reads, admin sync, and server-to-server work — inventory pushes, order back-office, product imports. It is stable, it is boring, and boring is exactly what you want on the pipe that keeps stock levels honest.
- Reach for the Store API for the cart and checkout on the customer’s device. It is session-aware and built for exactly the block-driven flow shoppers touch, which is why the native REST API deliberately leaves carts out.
- Reach for WPGraphQL when your front-end team is drowning in over-fetching and wants one typed query per screen. The cost is another moving part to keep patched. If your data needs are simple,
_fieldsfiltering on REST gets you most of the win without it.
My default recommendation for a first production build: REST for catalog and back-office, Store API for cart and checkout, and leave GraphQL out until a real query problem justifies it. Add tools when they solve a pain you can name, not because the architecture diagram looks more modern with three boxes.
The mental model that keeps headless builds sane
Long before REST, I spent years building multi-user games and interactive shows, back in the Macromedia Director and Flash days. The architecture that survived contact with real users was always the same: the server was the single source of truth, and every client on the screen was a disposable view of that truth. If a machine crashed, you did not lose the game — you reconnected and re-rendered, because the client never owned any state worth protecting.
Headless WooCommerce is the same contract wearing modern clothes. WordPress and WooCommerce are the source of truth: products, prices, stock, orders. Your Next.js or Astro front-end is a view — fast, pretty, and completely replaceable. The teams that get into trouble are the ones who quietly let the front-end become the source of truth for something — a cached price that outlives a sale, a stock number the client “knows better” than the server. Keep the authority where the money is, treat the front-end as a rendering of it, and you can rebuild the entire storefront on a new framework in two years without touching a single order. That disposability is the whole point of going headless, and it is worth protecting on purpose.
Conclusion
The WooCommerce REST API is comprehensive enough to power a full headless storefront. The key gaps — cart management and real-time features — are filled by the Store API and CoCart. Focus on field filtering and caching for performance, and plan your checkout flow carefully. The API is stable, well-documented, and ready for production headless commerce. Start there, add GraphQL only when you can name the problem it solves, and keep the server as the source of truth. Do that and the build stays boring in the best possible way.
Last modified: August 2, 2026
United States / English
Slovensko / Slovenčina
Canada / Français
Türkiye / Türkçe