Skip to main content

Shopify Storefront API: Headless Checkout, Cart, Customer 2026

Build headless Shopify storefronts with the Storefront GraphQL API: Cart API, checkout hand-off, Customer Account OAuth, and real production patterns.

E
ECOSIRE Research and Development Team
|May 4, 20267 min read1.3k Words|

Headless Shopify is now the default architecture for any merchant doing more than $5M GMV who wants brand control over the front end. The Storefront API exposes products, collections, the Cart object, and the customer account surface to a web or mobile client without going through Shopify's hosted theme.

Headless Shopify is now the default architecture for any merchant doing more than $5M GMV who wants brand control over the front end. The Storefront API exposes products, collections, the Cart object, and the customer account surface to a web or mobile client without going through Shopify's hosted theme. With API version 2026-01, Cart fully replaced the legacy Checkout object, and the new Customer Account API replaced the password-based customer endpoints. This guide is the production playbook we use when migrating clients to headless Hydrogen, Next.js, or native mobile.

Key Takeaways

  • The Storefront API uses a public token (X-Shopify-Storefront-Access-Token) and a separate cost bucket from the Admin API
  • The Cart object is the only supported model in 2026 — checkoutCreate is deprecated and removed
  • Hand-off to checkout uses cart.checkoutUrl, which carries identity, discounts, and attributes intact
  • Customer Account API uses OAuth + PKCE — no more customerAccessTokenCreate with password
  • Storefront rate limit is 60 requests/sec per IP for unauthenticated, 200 req/sec with public token
  • Hydrogen, Next.js with @shopify/hydrogen-react, and SwiftUI/Compose all share the same query schema

Storefront API vs Admin API

Different surface, different auth, different cost bucket.

AspectStorefront APIAdmin API
AudienceEnd shoppersMerchant/staff
TokenPublic Storefront Access TokenPer-shop OAuth/admin token
AuthToken in header, optional Customer Access TokenOAuth + scopes
Cost modelRequest-based (60-200/sec)Calculated cost (2,000 bucket)
MutationsCart, Customer, Metaobject createEverything
PII accessCustomer's own data onlyShop-wide

Never put an Admin API token in client-side code. Storefront tokens are designed to be public and only expose a deliberately limited surface.

The Cart API

A cart is a long-lived object (typically 7 days) that holds line items, attributes, discount codes, buyer identity, and delivery preferences. You create it once and update it on every interaction.

mutation CartCreate {
  cartCreate(input: {
    lines: [{ merchandiseId: "gid://shopify/ProductVariant/123", quantity: 1 }]
    buyerIdentity: { countryCode: US }
  }) {
    cart {
      id
      checkoutUrl
      cost { totalAmount { amount currencyCode } }
      lines(first: 100) { nodes { id quantity merchandise { ... on ProductVariant { id title } } } }
    }
    userErrors { field message code }
  }
}

Subsequent line additions, removals, and quantity changes use cartLinesAdd, cartLinesRemove, cartLinesUpdate. Each returns the full updated cart, so there's no need to re-fetch.

Cart attributes

Attributes are arbitrary key-value pairs you attach to a cart that flow through to the order. Useful for gift messages, delivery instructions, B2B PO numbers, or referral source tracking.

mutation {
  cartAttributesUpdate(
    cartId: "gid://shopify/Cart/abc"
    attributes: [{ key: "gift_message", value: "Happy birthday!" }]
  ) { cart { id attributes { key value } } userErrors { field message } }
}

Hand-off to checkout

In headless, you do not build the checkout UI. You build everything up to it, then redirect to cart.checkoutUrl. Shopify hosts the checkout (PCI-compliant, fraud-protected, supports Shop Pay, Apple Pay, Google Pay). On checkout completion, the customer redirects back to your site if you configure a post-purchase redirect.

Common mistake: trying to render checkout in an iframe. Shopify blocks this with frame-ancestors CSP. The supported flow is full-page redirect, period.

function CheckoutButton({ cart }) {
  return (
    <a href={cart.checkoutUrl} className="btn btn-primary">
      Checkout — {formatMoney(cart.cost.totalAmount)}
    </a>
  );
}

Customer accounts in 2026

The customerAccessTokenCreate mutation (password + email) was removed in API version 2025-04. The Customer Account API is its replacement: OAuth 2.0 with PKCE, runs on a separate domain (shopify.com/authentication), and supports passwordless via Shop ID, magic link, and SMS.

// Step 1: Redirect to authorization endpoint
const params = new URLSearchParams({
  client_id: process.env.SHOPIFY_CUSTOMER_CLIENT_ID,
  scope: 'openid email customer-account-api:full',
  response_type: 'code',
  redirect_uri: `${process.env.SITE_URL}/auth/callback`,
  state: csrfToken,
  code_challenge: pkceChallenge,
  code_challenge_method: 'S256',
});
window.location.href =
  `https://shopify.com/${shopId}/auth/oauth/authorize?${params}`;

// Step 2: Server exchanges code for access token
const tokenRes = await fetch(`https://shopify.com/${shopId}/auth/oauth/token`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code,
    client_id: process.env.SHOPIFY_CUSTOMER_CLIENT_ID,
    redirect_uri: `${process.env.SITE_URL}/auth/callback`,
    code_verifier: pkceVerifier,
  }),
});

Once you have the access token, you call the Customer Account API directly with Bearer <token>. Endpoints include customer { orders, addresses, paymentMethods } and mutations for profile updates and address book management.

Caching strategy

The Storefront API has aggressive cache hints. Use them:

  • Product and collection queries are safely cacheable for 60 seconds at the edge.
  • Cart queries must never be cached (per-user state).
  • Customer queries must never be cached.

In Next.js App Router, this lines up with the cache directives:

export async function getProduct(handle: string) {
  return shopifyFetch({
    query: PRODUCT_QUERY,
    variables: { handle },
    cache: 'force-cache',
    next: { revalidate: 60, tags: [`product:${handle}`] },
  });
}

Tag-based revalidation lets you invalidate single products via webhook (products/update) instead of busting the entire cache.

Multi-currency and Markets

If the merchant runs Shopify Markets, every Storefront API request must include the buyer's country. Prices, taxes, and even product visibility can vary.

query Product($handle: String!, $country: CountryCode!) @inContext(country: $country) {
  productByHandle(handle: $handle) {
    title
    priceRange { minVariantPrice { amount currencyCode } }
  }
}

The @inContext directive is mandatory. Without it, you get the shop's primary market pricing, which can be wrong by 20-40% in regional markets. For more on this, see our Shopify Markets pricing and tax guide.

Performance benchmarks

On a Plus shop with 5,000 products and 30,000 variants, our typical headless site (Next.js 16, RSC, edge runtime) hits these numbers:

MetricValue
TTFB (cached product page)45 ms
TTFB (uncached product page)320 ms
Time to LCP0.9 s on 4G
Largest Storefront query18 KB response
Cart-add latency~210 ms

The biggest wins came from edge caching collection queries, server-rendering above-the-fold content, and lazy-loading variant selectors.

Common pitfalls

  1. Forgetting buyerIdentity.countryCode: prices wrong outside primary market.
  2. Server-side cart: don't store cart ID in session — use httpOnly cookies. Cart IDs are stable for 7 days.
  3. Inventory at cart time: inventory is checked at cart-line-add and again at checkout. A product can sell out between the two; handle the cart.lines[].merchandise.availableForSale field on every render.
  4. Customer auth on edge runtimes: PKCE requires crypto.subtle, which is available on edge runtimes — but you must not store the verifier in cookie-only sessions. Use a server-side store keyed by state.

Frequently Asked Questions

Can I bypass Shopify's checkout entirely?

No. Only Shopify Plus merchants on Headless plans, with manual approval, get access to the Cart Permissions API and even then they hand off to a Shopify-hosted checkout. PCI compliance and fraud protection are bundled with the platform.

How do I migrate from Checkout to Cart?

Replace checkoutCreate with cartCreate, replace checkoutLineItemsAdd with cartLinesAdd, etc. The data shapes are similar but Cart is more capable (delivery groups, buyer identity, attributes). Hydrogen 2024+ ships with Cart helpers that abstract this.

What is the rate limit on Storefront API?

60 requests/sec/IP for anonymous, 200 req/sec for an authenticated public token. Plus shops can request a higher limit. Cost is request-based, not query-cost-based — but very deeply nested queries still time out at the resolver level after ~10 seconds.

Can I use the Storefront API with theme code (Liquid)?

Yes — use ?{shop}.myshopify.com/api/2026-01/graphql.json from a <script> tag with the public token. But you usually don't need to: Liquid renders products natively. Storefront API in themes is mostly for complex interactive widgets like personalized recommendations.


ECOSIRE has built 40+ headless Shopify storefronts on Hydrogen and Next.js, including custom Cart, Customer Account, and B2B portals. Our Shopify headless team handles auth, performance budgets, and Markets integration end-to-end.

E

Written by

ECOSIRE Team

Technical Writing

The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.

ECOSIRE

Scale Your Shopify Store

Custom development, optimization, and migration services for high-growth eCommerce.