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.

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