WooCommerce as a headless e-commerce backend
WooCommerce is the most widely used e-commerce plugin on WordPress, with more than 5 million active installations. In headless mode, WooCommerce keeps its role of managing products, orders, customers, and payments, while the Next.js frontend takes over catalog display and the purchase journey.
5M+
active installations
WooCommerce is the most deployed WordPress e-commerce plugin
2
available APIs
REST API v3 (admin) and Store API (frontend/cart)
100+
payment gateways
Payment extensions compatible with WooCommerce
The two WooCommerce APIs
WooCommerce exposes two distinct APIs, each suited to a specific use.
Store API: the API designed for headless
The Store API is the recommended API for the headless frontend. Unlike the REST API v3, which is administration-oriented, the Store API natively handles the cart, checkout, and catalog display without requiring authentication keys for public operations. It is maintained by the WooCommerce team and is part of the WooCommerce Blocks plugin.
API authentication
REST API v3 (administration operations)
// lib/woocommerce.ts — REST API v3 client
const WC_API_URL = process.env.WOOCOMMERCE_API_URL; // https://admin.your-site.com/wp-json/wc/v3
const WC_KEY = process.env.WOOCOMMERCE_CONSUMER_KEY;
const WC_SECRET = process.env.WOOCOMMERCE_CONSUMER_SECRET;
export async function fetchWooCommerce(endpoint: string) {
const credentials = Buffer.from(`${WC_KEY}:${WC_SECRET}`).toString('base64');
const response = await fetch(`${WC_API_URL}/${endpoint}`, {
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json',
},
});
return response.json();
}
Store API (frontend operations)
// lib/store-api.ts — Store API client
const STORE_API_URL = process.env.NEXT_PUBLIC_STORE_API_URL;
// https://admin.your-site.com/wp-json/wc/store/v1
export async function fetchStoreAPI(endpoint: string, options?: RequestInit) {
const response = await fetch(`${STORE_API_URL}/${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
credentials: 'include', // sends session cookies (cart)
});
return response.json();
}
Displaying the product catalog
// app/boutique/page.tsx — catalog page
async function getProducts() {
const products = await fetch(
`${process.env.WOOCOMMERCE_API_URL}/products?per_page=24&status=publish`,
{
headers: {
'Authorization': `Basic ${Buffer.from(
`${process.env.WOOCOMMERCE_CONSUMER_KEY}:${process.env.WOOCOMMERCE_CONSUMER_SECRET}`
).toString('base64')}`,
},
next: { revalidate: 3600 }, // Revalidate every hour
}
).then(res => res.json());
return products;
}
export default async function BoutiquePage() {
const products = await getProducts();
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{products.map((product: any) => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
Cart management
The cart is the central element of the purchase journey. In headless mode, two approaches are possible.
Approach 1: Client-side cart (recommended for performance)
The cart state is managed in the browser (Context API, Zustand, or Redux). This approach offers maximum responsiveness because no network request is needed to add or remove a product.
// stores/cart-store.ts — client-side cart management with Zustand
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
type CartItem = {
id: number;
name: string;
price: number;
quantity: number;
image: string;
};
type CartStore = {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: number) => void;
updateQuantity: (id: number, quantity: number) => void;
clearCart: () => void;
total: () => number;
};
export const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
addItem: (item) => set((state) => {
const existing = state.items.find((i) => i.id === item.id);
if (existing) {
return {
items: state.items.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
),
};
}
return { items: [...state.items, { ...item, quantity: 1 }] };
}),
removeItem: (id) => set((state) => ({
items: state.items.filter((i) => i.id !== id),
})),
updateQuantity: (id, quantity) => set((state) => ({
items: state.items.map((i) => (i.id === id ? { ...i, quantity } : i)),
})),
clearCart: () => set({ items: [] }),
total: () => get().items.reduce((sum, i) => sum + i.price * i.quantity, 0),
}),
{ name: 'cart-storage' }
)
);
Approach 2: Server-side cart (via Store API)
The cart is managed by WooCommerce via the Store API. Each operation (add, remove) makes a request to the backend. This approach guarantees real-time inventory synchronization.
// Add a product to the cart via the Store API
async function addToCart(productId: number, quantity: number = 1) {
return fetchStoreAPI('cart/add-item', {
method: 'POST',
body: JSON.stringify({ id: productId, quantity }),
});
}
// Get the cart contents
async function getCart() {
return fetchStoreAPI('cart');
}
Checkout funnel
The checkout is the most sensitive step of the e-commerce journey. The Store API exposes a dedicated endpoint that handles order creation, tax calculation, and shipping fees.
// app/checkout/actions.ts — server action for checkout
'use server';
export async function createOrder(formData: {
billing: BillingAddress;
shipping: ShippingAddress;
cartItems: CartItem[];
}) {
const response = await fetch(
`${process.env.WOOCOMMERCE_API_URL}/orders`,
{
method: 'POST',
headers: {
'Authorization': `Basic ${Buffer.from(
`${process.env.WOOCOMMERCE_CONSUMER_KEY}:${process.env.WOOCOMMERCE_CONSUMER_SECRET}`
).toString('base64')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
payment_method: 'stripe',
billing: formData.billing,
shipping: formData.shipping,
line_items: formData.cartItems.map((item) => ({
product_id: item.id,
quantity: item.quantity,
})),
}),
}
);
return response.json();
}
Payment gateway integration
Stripe
Stripe is the most common gateway in headless. The integration uses Stripe Elements on the frontend and the Stripe webhook on the WooCommerce side to confirm payments.
Install the WooCommerce Stripe Gateway plugin
Install and configure the plugin in WooCommerce. Enter your Stripe API keys (publishable key and secret key) in the plugin settings.
Create a Payment Intent on the server
During checkout, create a Payment Intent via the Stripe API from a Next.js API route. The amount matches the WooCommerce order total.
Integrate Stripe Elements on the frontend
Use @stripe/react-stripe-js to display the credit card form. The PaymentElement component handles card input and validation.
Confirm payment and create the order
After Stripe confirms the payment, create the WooCommerce order via the REST API. The Stripe webhook updates the order status automatically.
Product variations and inventory
Variable products (sizes, colors) are exposed by the REST API v3 via the /products/{id}/variations endpoint. Each variation has its own SKU, price, and stock.
// Fetch the variations of a product
async function getProductVariations(productId: number) {
return fetchWooCommerce(`products/${productId}/variations?per_page=100`);
}
Inventory synchronization is automatic: WooCommerce decrements stock when an order is created. In headless mode, the stock displayed on the frontend is updated on each ISR revalidation or on direct API call.
Taxes and shipping fees
WooCommerce natively handles tax and shipping calculation according to configured geographic zones. The Store API returns these calculations in the cart response:
{
"totals": {
"subtotal": "4500",
"tax_total": "900",
"shipping_total": "590",
"total": "5990",
"currency_code": "EUR"
}
}
Amounts are expressed in cents. The frontend must format them for display (5990 cents = €59.90).
Performance for large catalogs
Strategies for large catalogs
For stores with more than 1,000 products, combine ISR with API-side pagination. Pre-generate the most-viewed product pages (top 100-200) with generateStaticParams and let the rest be generated on demand (dynamic fallback). Use revalidate: 3600 to refresh product data every hour.
// app/produits/[slug]/page.tsx — static generation of popular products
export async function generateStaticParams() {
// Pre-generate only the 200 most popular products
const products = await fetchWooCommerce(
'products?per_page=200&orderby=popularity&_fields=slug'
);
return products.map((p: any) => ({ slug: p.slug }));
}
Comparison with other headless commerce solutions
How to build a headless WordPress site?
Article suivantMultilingual and internationalization in headless