Headless WooCommerce with Next.js and the Store API

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.

Leave Comment

Your email address will not be published. Required fields are marked *