The Shopify Storefront API is the gateway to building custom commerce experiences on top of Shopify’s backend. Whether you are building a fully headless storefront with Hydrogen, a mobile application, a custom product configurator, or simply enhancing your Liquid theme with dynamic data fetching, the Storefront API is how you access Shopify’s product catalogue, manage carts, and handle customer authentication programmatically.

Unlike the Admin API (which provides authenticated, full access to all store data), the Storefront API is designed for customer-facing applications. It uses public access tokens, returns only publicly available data, and is optimised for high-throughput storefront traffic. Understanding its capabilities, limitations, and query patterns is essential for any developer working with Shopify beyond basic Liquid themes.

This guide covers the practical implementation patterns we use in our Shopify development work — from basic product queries to complex cart management, pagination, caching, and rate limit management. It complements our GraphQL API guide and the non-developer API overview.

What the Storefront API is

The Storefront API is a GraphQL-only API. There is no REST endpoint. Every interaction — reading products, managing carts, authenticating customers — uses GraphQL queries and mutations sent to a single endpoint:

POST https://{store-name}.myshopify.com/api/2026-01/graphql.json

The API provides access to:

  • Products and variants — titles, descriptions, prices, images, metafields, availability, options.
  • Collections — manual and automated collections with product filtering and sorting.
  • Cart — create carts, add/remove/update line items, apply discount codes, retrieve checkout URLs.
  • Customer accounts — login, registration, password reset, address management, order history.
  • Pages and blog posts — content pages and blog articles with metafields.
  • Shop information — store name, currency, payment settings, shipping countries.
  • Localisation — multi-currency pricing, translated content, country-specific availability.

The Storefront API is read-heavy by design. You can read all public catalogue data but can only write to carts and customer accounts. Order creation, product management, and inventory updates require the Admin API.

Diagram showing Storefront API capabilities: products, collections, cart, customer, pages
The Storefront API provides read access to all public catalogue data and write access for cart operations and customer authentication.

Authentication and access tokens

The Storefront API uses two types of access tokens:

Public access tokens (Storefront API tokens)

Created in the Shopify admin under Apps → Develop apps. These tokens are safe to expose in client-side JavaScript because they only provide access to public data. They are the standard choice for headless storefronts and JavaScript-enhanced themes.

// Basic fetch with Storefront API token
const response = await fetch(
  'https://your-store.myshopify.com/api/2026-01/graphql.json',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Shopify-Storefront-Access-Token': 'your-public-token-here',
    },
    body: JSON.stringify({
      query: `{
        shop {
          name
          primaryDomain { url }
          paymentSettings { currencyCode }
        }
      }`
    }),
  }
);
const { data } = await response.json();

Private access tokens (delegate access tokens)

Available on Shopify Plus, these provide server-side-only access with additional capabilities including buyer-specific pricing and content. Never expose these in client-side code.

Product data queries

Product queries are the foundation of any Storefront API integration. The key principle is to request only the fields you need — over-fetching wastes both query cost budget and network bandwidth.

// Fetch a single product by handle
const PRODUCT_QUERY = `
  query ProductByHandle($handle: String!) {
    product(handle: $handle) {
      id
      title
      handle
      descriptionHtml
      productType
      vendor
      tags
      availableForSale
      priceRange {
        minVariantPrice { amount currencyCode }
        maxVariantPrice { amount currencyCode }
      }
      compareAtPriceRange {
        minVariantPrice { amount currencyCode }
      }
      images(first: 10) {
        edges {
          node {
            id url altText width height
          }
        }
      }
      variants(first: 100) {
        edges {
          node {
            id title availableForSale quantityAvailable
            price { amount currencyCode }
            compareAtPrice { amount currencyCode }
            selectedOptions { name value }
            image { url altText }
          }
        }
      }
      seo { title description }
      metafield(namespace: "custom", key: "care_instructions") {
        value type
      }
    }
  }
`;

Collection product listing

// Fetch products in a collection with filtering
const COLLECTION_QUERY = `
  query CollectionProducts($handle: String!, $first: Int!, $after: String, $filters: [ProductFilter!]) {
    collection(handle: $handle) {
      id title description
      products(first: $first, after: $after, filters: $filters, sortKey: BEST_SELLING) {
        pageInfo { hasNextPage endCursor }
        edges {
          node {
            id title handle availableForSale
            priceRange {
              minVariantPrice { amount currencyCode }
            }
            featuredImage { url altText width height }
            variants(first: 1) {
              edges {
                node {
                  compareAtPrice { amount currencyCode }
                }
              }
            }
          }
        }
      }
    }
  }
`;
GraphQL query structure showing product data relationships
GraphQL lets you request exactly the product fields you need, reducing response size and query cost compared to REST endpoints that return fixed payloads.

Collection filtering and sorting

The Storefront API supports filtering products within collections by multiple attributes. Filters are combined with AND logic — all conditions must be true.

// Available filter types
const filters = [
  // Price range
  { price: { min: 20, max: 100 } },
  // Availability
  { available: true },
  // Product type
  { productType: "T-Shirt" },
  // Vendor
  { productVendor: "Brand Name" },
  // Variant option (e.g., size or colour)
  { variantOption: { name: "Size", value: "Large" } },
  // Metafield value
  { productMetafield: { namespace: "custom", key: "material", value: "Cotton" } },
  // Tag
  { tag: "sale" },
];

Cart management

The Cart API is the Storefront API’s write interface. It replaces the deprecated Checkout API and provides a complete cart management workflow.

// Create a new cart
const CREATE_CART = `
  mutation CartCreate($input: CartInput!) {
    cartCreate(input: $input) {
      cart {
        id
        checkoutUrl
        lines(first: 10) {
          edges {
            node {
              id quantity
              merchandise { ... on ProductVariant { id title price { amount currencyCode } } }
            }
          }
        }
        cost {
          totalAmount { amount currencyCode }
          subtotalAmount { amount currencyCode }
          totalTaxAmount { amount currencyCode }
        }
      }
      userErrors { field message }
    }
  }
`;

// Add items to cart
const ADD_TO_CART = `
  mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
    cartLinesAdd(cartId: $cartId, lines: $lines) {
      cart {
        id
        lines(first: 50) {
          edges { node { id quantity merchandise { ... on ProductVariant { id title } } } }
        }
        cost { totalAmount { amount currencyCode } }
      }
      userErrors { field message }
    }
  }
`;

// Apply discount code
const APPLY_DISCOUNT = `
  mutation CartDiscountCodesUpdate($cartId: ID!, $discountCodes: [String!]) {
    cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) {
      cart {
        discountCodes { applicable code }
        cost { totalAmount { amount currencyCode } }
      }
      userErrors { field message }
    }
  }
`;

Customer account operations

The Storefront API provides customer authentication, registration, and account management. Customer access tokens are returned on login and used for subsequent authenticated requests.

// Customer login
const CUSTOMER_LOGIN = `
  mutation CustomerAccessTokenCreate($input: CustomerAccessTokenCreateInput!) {
    customerAccessTokenCreate(input: $input) {
      customerAccessToken { accessToken expiresAt }
      customerUserErrors { code field message }
    }
  }
`;

// Fetch customer data with access token
const CUSTOMER_QUERY = `
  query Customer($customerAccessToken: String!) {
    customer(customerAccessToken: $customerAccessToken) {
      id firstName lastName email phone
      defaultAddress { address1 city province country zip }
      orders(first: 10) {
        edges {
          node {
            id orderNumber processedAt financialStatus fulfillmentStatus
            totalPrice { amount currencyCode }
          }
        }
      }
    }
  }
`;
Customer authentication flow using Storefront API access tokens
Customer authentication uses access tokens returned on login. The token is included in subsequent requests to access customer-specific data like order history and addresses.

Cursor-based pagination

The Storefront API uses cursor-based pagination (not page numbers). Each connection returns pageInfo with hasNextPage and endCursor. Pass the cursor as the after argument to fetch the next page.

// Paginate through all products
async function fetchAllProducts(storefront) {
  let allProducts = [];
  let hasNextPage = true;
  let cursor = null;

  while (hasNextPage) {
    const { data } = await storefront.query(PRODUCTS_QUERY, {
      variables: { first: 50, after: cursor }
    });

    const products = data.products.edges.map(e => e.node);
    allProducts = [...allProducts, ...products];
    hasNextPage = data.products.pageInfo.hasNextPage;
    cursor = data.products.pageInfo.endCursor;
  }

  return allProducts;
}

Caching strategies

Effective caching is critical for Storefront API performance. Without caching, every page load makes multiple API calls, each adding latency.

  • HTTP cache headers — Shopify returns Cache-Control headers on Storefront API responses. Respect them in your caching layer.
  • CDN edge caching — cache API responses at the edge for static or semi-static data (product catalogues change infrequently compared to cart data).
  • In-memory caching — for server-side applications, cache frequently accessed data (shop info, navigation, featured collections) in memory with appropriate TTLs.
  • Stale-while-revalidate — serve cached data immediately while fetching fresh data in the background. This provides instant responses without serving severely stale data.
// Simple in-memory cache with TTL
const cache = new Map();

async function cachedQuery(key, queryFn, ttlMs = 60000) {
  const cached = cache.get(key);
  if (cached && Date.now() - cached.timestamp < ttlMs) {
    return cached.data;
  }

  const data = await queryFn();
  cache.set(key, { data, timestamp: Date.now() });
  return data;
}

// Usage: cache product data for 60 seconds
const product = await cachedQuery(
  `product:${handle}`,
  () => storefront.query(PRODUCT_QUERY, { variables: { handle } }),
  60000
);

Rate limits and cost management

The Storefront API uses a calculated cost model. Each query has a cost based on its complexity — the number of objects requested, the depth of connections, and the first/last argument values.

// Response includes cost information in extensions
{
  "extensions": {
    "cost": {
      "requestedQueryCost": 42,
      "actualQueryCost": 38,
      "throttleStatus": {
        "maximumAvailable": 1000,
        "currentlyAvailable": 962,
        "restoreRate": 50
      }
    }
  }
}

Strategies for managing query costs:

  • Request only the fields you need — remove unused fields from queries.
  • Use appropriate first/last values — do not request 250 products when you only display 12.
  • Batch related data into single queries — one query requesting product + recommendations is cheaper than two separate queries.
  • Cache aggressively — cached responses cost zero API calls.
Storefront API rate limit bucket diagram showing cost, capacity, and restore rate
The Storefront API uses a leaky bucket model: queries consume cost points from a 1,000-point bucket that refills at 50 points per second.

Practical integration patterns

Based on our Shopify development experience, here are the most common integration patterns:

Pattern 1: Ajax cart in Liquid themes

Use the Storefront API’s Cart mutations from client-side JavaScript to build a dynamic cart drawer without full page reloads. This is the most common non-headless use of the API.

Pattern 2: Custom product search

Build a custom search experience using the Storefront API’s predictive search or product filtering capabilities, providing faster and more relevant results than Shopify’s default search.

Pattern 3: Headless product pages

Build specific high-value pages (product configurators, lookbooks, editorial content) as standalone applications consuming the Storefront API, while keeping the rest of the store on standard Liquid. This is the hybrid approach we often recommend.

Pattern 4: Mobile application

Build native iOS/Android applications using the Storefront API for all commerce functionality — browsing, cart, checkout, and customer accounts.


The Storefront API is the most important API in the Shopify ecosystem for frontend developers. Whether you are building a fully headless storefront or enhancing a Liquid theme with dynamic functionality, understanding its query patterns, caching strategies, and rate limits is essential for building performant, reliable commerce experiences.

If you need help integrating the Storefront API into your Shopify store or building a headless commerce solution, get in touch. API integration is a core part of our Shopify development services.