
Shopify Storefront API GraphQL: The Complete Guide
The Shopify Storefront API is a public GraphQL API that lets developers build custom shopping experiences outside of the classic Liquid theme. It exposes core objects such as Product, Collection, Cart, and Customer for reading, along with mutations for cart management and customer identity handling.
Unlike the Admin API, which is reserved for back-office operations, the Storefront API is designed to power user-facing interfaces directly:
- Headless storefronts built with Next.js or Hydrogen
- Native mobile applications (iOS, Android)
- Progressive Web Apps (PWA)
- In-store kiosks and interactive displays
- Voice commerce and IoT interfaces
In 2026, the Storefront API has established itself as the standard for e-commerce brands that demand performance and flexibility beyond what monolithic architectures can deliver. Within a composable commerce ecosystem, it serves as the technical foundation for headless commerce on Shopify, fully decoupling the front-end from the transactional back-end within a modern Jamstack architecture. This approach is particularly prevalent among Shopify Plus merchants managing high-traffic volumes and internationalized catalogs.
For a broader overview of headless commerce on Shopify, see our complete Shopify headless guide.
Why Shopify chose GraphQL for its Storefront API
The limitations of REST for headless commerce
Traditional REST APIs impose fixed endpoints that return predefined data structures. To display a complete product page, a REST client must chain multiple sequential calls:
/products/{id}for basic information (title, description, status)/products/{id}/variantsfor variants and their prices/products/{id}/imagesfor visuals/products/{id}/metafieldsfor custom data
Each additional request adds latency and degrades Core Web Vitals. The request waterfall negatively impacts Largest Contentful Paint (LCP) by delaying product image loading, and Interaction to Next Paint (INP) when data blocks interactivity. In a headless context where every millisecond counts for conversion rates, this approach becomes a bottleneck.
GraphQL: one request, one precise response
GraphQL solves this fundamental problem. Thanks to the strongly typed schema of the Storefront API -- versioned quarterly by Shopify (e.g., 2026-01) -- a single request is enough to retrieve exactly the data needed. The key advantages:
- Elimination of over-fetching: only requested fields are returned, no unnecessary data is transferred
- Elimination of under-fetching: all required data arrives in a single request, with no additional calls needed
- Self-documenting schema: the GraphQL schema serves as an explorable API contract via tools like GraphiQL
- Strong typing: structural errors are caught before execution, on both client and server side
In practice, a single GraphQL query can simultaneously return:
- Product details (title, description, handle)
- Its product variants with localized prices via international pricing
- Optimized images with dimensions
- Custom metafields
- Stock availability by location
Here is a typical request for a product page with internationalized pricing:
query ProductPage($handle: String!, $country: CountryCode!)
@inContext(country: $country) {
productByHandle(handle: $handle) {
title
descriptionHtml
variants(first: 10) {
edges {
node {
priceV2 { amount currencyCode }
availableForSale
}
}
}
metafields(identifiers: [{ namespace: "custom", key: "specs" }]) {
value
type
}
}
}The @inContext directive contextualizes prices based on the buyer's country, a native Storefront API feature for international commerce.
Queries: for reading data. Queries retrieve information without modifying the store's state. They are used to display catalogs, product pages, and collections.
Mutations: for writing data. Mutations modify the storefront's state. The most common Storefront API mutations are cartCreate, cartLinesAdd, cartLinesUpdate, and customerAccessTokenCreate.
Important nuance: this efficiency comes with a trade-off. Unlike REST where native HTTP caching (ETags, Cache-Control) works per endpoint, caching GraphQL responses requires a dedicated strategy. On the client side, libraries like Apollo Client or urql normalize the cache. On the server side, persisted queries combined with a CDN (Cloudflare, Fastly) enable edge-level response caching.
Storefront API vs Admin API: technical comparison
The choice between the Storefront API and the Admin API depends on the use case. Here are the fundamental differences:
Use cases and permissions
| Criteria | Storefront API | Admin API |
|---|---|---|
| Audience | Client-side applications, public storefronts | Back-office applications, server automations |
| Access | Read products/collections + write cart/customer | Full read/write access to all resources |
| Token | Public access token (safe to expose client-side) | Private access token (secret, server only) |
| Rate limits | Per buyer IP (scalable) | Per store (point bucket) |
| IDs | Base64 encoded (Z2lkOi8v...) | Global IDs (gid://shopify/Product/123) |
When to use each
Use the Storefront API for any buyer-facing interface: catalog display, product pages, cart management, search and filtering, customer authentication.
Use the Admin API for back-office operations: product creation, order management, inventory synchronization, webhooks, and automations.
In a typical headless architecture, both APIs coexist. The front-end consumes the Storefront API for the shopping experience, while back-end services use the Admin API for operational management. Learn how to structure this architecture through our Shopify headless development service.
Quick start: your first request in 5 minutes
Generate your access credentials
To access the Storefront API, install the Headless sales channel from the Shopify App Store:
- In the Shopify admin, install the Headless channel
- Click Create storefront to generate access tokens
- Note the private access token (for server-side requests) and the public access token (for client-side requests)
Each store can have up to 100 active storefronts, each with its own tokens and permissions.
Authentication: public vs delegated access token
The Storefront API offers two authentication modes:
Public access token: can be used directly in the browser or a mobile app. This token grants access to the store's public data (products, collections, prices) and cart operations. It is passed via the X-Shopify-Storefront-Access-Token header.
const headers = {
"Content-Type": "application/json",
"X-Shopify-Storefront-Access-Token": "your-public-token"
};Delegated access token: generated server-side for an authenticated customer. It grants access to personal data (order history, addresses) in addition to public data. This token is obtained via the customerAccessTokenCreate mutation after customer authentication.
The security difference is fundamental: the public access token can be safely exposed in front-end code, while the delegated access token must remain server-side to protect personal customer data.
Configure a GraphQL client
You can query the Storefront API with any HTTP client. Here is an example using a simple fetch in JavaScript:
const SHOPIFY_STORE = "your-store.myshopify.com";
const API_VERSION = "2026-01";
const STOREFRONT_TOKEN = "your-public-token";
async function storefrontQuery(query, variables = {}) {
const response = await fetch(
`https://${SHOPIFY_STORE}/api/${API_VERSION}/graphql.json`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Storefront-Access-Token": STOREFRONT_TOKEN,
},
body: JSON.stringify({ query, variables }),
}
);
const { data, errors } = await response.json();
if (errors) throw new Error(errors.map((e) => e.message).join(", "));
return data;
}For production projects, prefer a dedicated GraphQL client like Apollo Client (normalized cache, advanced error handling) or urql (lightweight, extensible via exchanges).
Execute your first query
Test your setup by retrieving the store information:
query ShopInfo {
shop {
name
primaryDomain {
url
}
paymentSettings {
currencyCode
}
}
}If the response contains your store's name, your configuration is operational.
Mastering Storefront API GraphQL operations
Queries: retrieving store data
List products with variants, prices, and images
The following query retrieves the first 12 products with essential information for a catalog page:
query ProductList($country: CountryCode!) @inContext(country: $country) {
products(first: 12) {
edges {
node {
id
title
handle
featuredImage {
url
altText
width
height
}
priceRange {
minVariantPrice { amount currencyCode }
maxVariantPrice { amount currencyCode }
}
variants(first: 3) {
edges {
node {
id
title
priceV2 { amount currencyCode }
availableForSale
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}The pageInfo field is essential for cursor-based pagination. Use endCursor as the starting point of the next request with the after argument to traverse the entire catalog.
Query a collection and its products
query CollectionProducts($handle: String!, $first: Int!) {
collectionByHandle(handle: $handle) {
title
description
products(first: $first, sortKey: BEST_SELLING) {
edges {
node {
id
title
handle
priceRange {
minVariantPrice { amount currencyCode }
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}The sortKey parameter accepts multiple values: BEST_SELLING, CREATED_AT, PRICE, TITLE, RELEVANCE. This flexibility lets you adapt sorting based on the browsing context.
Build a detailed product page
For a complete product page, combine product data, variants, images, metafields, and recommendations:
query ProductDetail($handle: String!, $country: CountryCode!)
@inContext(country: $country) {
productByHandle(handle: $handle) {
id
title
descriptionHtml
vendor
tags
seo {
title
description
}
images(first: 10) {
edges {
node {
url
altText
width
height
}
}
}
variants(first: 50) {
edges {
node {
id
title
priceV2 { amount currencyCode }
compareAtPriceV2 { amount currencyCode }
availableForSale
selectedOptions {
name
value
}
}
}
}
metafields(identifiers: [
{ namespace: "custom", key: "specifications" },
{ namespace: "custom", key: "care_instructions" }
]) {
key
value
type
}
}
productRecommendations(productId: $productId) {
id
title
handle
priceRange {
minVariantPrice { amount currencyCode }
}
featuredImage {
url
altText
}
}
}This single query replaces what would require 5 to 7 distinct REST calls.
Mutations: modifying storefront state
Create a cart
The cartCreate mutation initializes a new cart:
mutation CartCreate($input: CartInput!) {
cartCreate(input: $input) {
cart {
id
checkoutUrl
lines(first: 10) {
edges {
node {
id
quantity
merchandise {
... on ProductVariant {
id
title
priceV2 { amount currencyCode }
}
}
}
}
}
cost {
totalAmount { amount currencyCode }
subtotalAmount { amount currencyCode }
}
}
userErrors {
field
message
}
}
}Input variables:
{
"input": {
"lines": [
{
"merchandiseId": "gid://shopify/ProductVariant/12345",
"quantity": 1
}
]
}
}Add and update cart items
To add lines to an existing cart, use cartLinesAdd:
mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart {
id
totalQuantity
cost {
totalAmount { amount currencyCode }
}
}
userErrors {
field
message
}
}
}To update quantity, use cartLinesUpdate. To remove a line, use cartLinesRemove with the lineId obtained during creation.
Associate a customer with the cart
After customer authentication, associate their identity with the cart via cartBuyerIdentityUpdate:
mutation CartBuyerIdentityUpdate(
$cartId: ID!
$buyerIdentity: CartBuyerIdentityInput!
) {
cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: $buyerIdentity) {
cart {
id
buyerIdentity {
email
countryCode
}
checkoutUrl
}
userErrors {
field
message
}
}
}This association enables personalized discounts, pre-fills the checkout, and contextualizes prices based on the customer's location.
Advanced patterns for a rich headless experience
Leveraging metafields via GraphQL
Metafields extend Shopify's standard data model. Through the Storefront API, you can access public metafields defined on products, variants, collections, and customers.
For a metafield to be accessible via the Storefront API, it must be explicitly enabled in the "Storefronts" settings of the metafield definition in the Shopify admin.
Common metafield use cases in a headless storefront:
- Technical specs: detailed specifications, materials, dimensions
- Care instructions: washing guides, precautions
- Rich content: videos, usage guides, certifications
- SEO data: custom meta tags, additional structured data
Implementing international pricing
The Storefront API natively supports international pricing via the @inContext directive. This directive accepts country and language to contextualize the entire response:
query InternationalProduct($handle: String!)
@inContext(country: FR, language: FR) {
productByHandle(handle: $handle) {
title
variants(first: 5) {
edges {
node {
priceV2 { amount currencyCode }
compareAtPriceV2 { amount currencyCode }
}
}
}
}
}Returned prices are automatically converted to the currency associated with the specified country, using the pricing rules defined in Shopify Markets. This feature is available on stores that have configured Shopify Markets in their admin.
Customer account management
The Storefront API provides a set of mutations for managing the complete customer lifecycle:
customerCreate: register a new customercustomerAccessTokenCreate: authenticate and generate the delegated access tokencustomerUpdate: modify profile (name, email, phone)customerAddressCreate/customerAddressUpdate: manage the address bookcustomerquery: access order history, addresses, and customer metafields
The delegated access token obtained via customerAccessTokenCreate has a limited validity period. Implement a refresh mechanism via customerAccessTokenRenew to maintain active sessions without re-requesting credentials.
Advanced search and filtering
The Storefront API exposes predictive search functionality via the search query and a faceted filtering system on collections:
query SearchProducts($query: String!, $first: Int!) {
search(query: $query, first: $first, types: [PRODUCT]) {
edges {
node {
... on Product {
id
title
handle
priceRange {
minVariantPrice { amount currencyCode }
}
}
}
}
totalCount
}
}For faceted filtering (size, color, price), use the filters argument on collection queries. Available filters are determined by the metafields and variant options configured in your store.
Integration with a modern framework
Architecture with Next.js App Router
Next.js remains the most versatile framework for building a headless storefront on Shopify. With the App Router and React Server Components, GraphQL queries to the Storefront API execute directly server-side without exposing tokens in the client bundle:
// app/products/[handle]/page.tsx
async function getProduct(handle: string) {
const data = await storefrontQuery(PRODUCT_QUERY, {
handle,
country: "FR",
});
return data.productByHandle;
}
export default async function ProductPage({
params,
}: {
params: { handle: string };
}) {
const product = await getProduct(params.handle);
return <ProductTemplate product={product} />;
}Server Components eliminate unnecessary client-side JavaScript, reduce bundle size, and improve Core Web Vitals. Combine this approach with SSR, SSG (via generateStaticParams), and ISR (via revalidate) rendering strategies to balance data freshness and performance.
For a detailed comparison between Next.js and Hydrogen, see our Shopify Hydrogen vs Next.js analysis.
Using Shopify Hydrogen
Hydrogen is Shopify's official React framework, built on Remix. It provides pre-built components and hooks optimized for the Storefront API:
<Money>: automatic price formatting based on locale<Image>: optimized Shopify image loading with lazy loading<CartProvider>: global cart state managementuseShopQuery: hook for Storefront API queries with built-in caching
Hydrogen excels in projects that are 100% Shopify where development speed is the priority. It natively handles Server-Side Rendering (SSR) via Remix and deploys on Oxygen, Shopify's edge infrastructure.
The choice between Next.js and Hydrogen depends on your ecosystem: Next.js offers more flexibility for integrating multiple data sources (CMS, PIM, search), while Hydrogen provides a turnkey experience optimized for Shopify.
For a broader view of headless e-commerce frameworks, see our Next.js e-commerce guide.
Rendering strategies: SSR, SSG, and ISR
The rendering strategy choice directly impacts performance and data freshness:
- SSR (Server-Side Rendering): the page is generated on each request. Ideal for cart pages, dynamic pricing, and personalized content
- SSG (Static Site Generation): the page is generated at build time. Maximum performance for stable product pages and institutional pages
- ISR (Incremental Static Regeneration): the page is regenerated periodically in the background. Optimal trade-off between performance and freshness for medium-sized catalogs
For catalogs with over 10,000 products, prefer ISR with a revalidate of 60 to 300 seconds. This avoids excessive build times while ensuring that price or stock changes are reflected within minutes.
Performance and caching: strategies for a fast storefront
Caching GraphQL responses
Unlike REST APIs where each endpoint has a unique URL (making HTTP caching straightforward), GraphQL requests all go through a single POST endpoint. This requires specific caching strategies:
Server-side (CDN / Edge):
- Use persisted queries: transform GraphQL queries into GET requests with a unique hash, making CDN caching possible
- Configure edge caching (Cloudflare Workers, Vercel Edge) to cache catalog query responses with a 60 to 300-second TTL
- Selectively invalidate the cache via Shopify webhooks when products are updated
Client-side:
- Apollo Client: normalized cache that automatically deduplicates objects by
id - urql: configurable cache exchanges, from simple document cache to normalized cache via Graphcache
stale-while-revalidate: serve cached data immediately while refreshing in the background
Avoiding cascade queries (N+1)
The N+1 problem occurs when an initial query triggers N additional queries to resolve relationships. With the Storefront API, this risk primarily appears in Server Components:
// Avoid: N+1
const products = await getProducts();
const details = await Promise.all(
products.map((p) => getProductDetails(p.handle))
);
// Preferred: a single query with the required fields
const products = await getProductsWithDetails();Consolidate your data needs into a minimum number of GraphQL queries. The Storefront API exposes nested fields (variants, images, metafields) precisely to avoid additional round trips.
Best practices for performant queries
- Limit requested fields: only request fields that are actually displayed in the interface
- Paginate wisely: use
first: 12orfirst: 24based on your grid size, notfirst: 250 - Use GraphQL fragments: factorize common fields (ProductCard, PriceDisplay) to reduce duplication and simplify maintenance
- Monitor query cost: each request has a cost calculated by Shopify. Complex queries with many nested connections consume more points
Managing Storefront API rate limits
The Storefront API uses a rate limit system per buyer IP, fundamentally different from the Admin API which limits per store. This design allows handling significant traffic spikes without one buyer's requests impacting others.
The cost calculation works as follows:
- Each request has a requested query cost (estimated cost) and an actual query cost (real cost after execution)
- The point bucket gradually recharges (restore rate)
- Responses include an
extensions.costfield that details the cost and remaining balance
{
"extensions": {
"cost": {
"requestedQueryCost": 32,
"actualQueryCost": 24,
"throttleStatus": {
"maximumAvailable": 1000,
"currentlyAvailable": 976,
"restoreRate": 50
}
}
}
}Strategies to avoid throttling:
- Monitor the
currentlyAvailablefield and implement an exponential backoff mechanism - Break down expensive queries into lighter requests spread over time
- Prefer cursor-based pagination with reasonable batch sizes (20-50 items)
Common errors and debugging
Handling GraphQL errors
Unlike REST APIs that use HTTP status codes (404, 500), GraphQL always returns an HTTP 200 status, even on application errors. Errors are encapsulated in the response body:
{
"data": null,
"errors": [
{
"message": "Field 'nonExistentField' doesn't exist on type 'Product'",
"locations": [{ "line": 4, "column": 5 }],
"path": ["productByHandle"]
}
]
}For mutations, systematically check the userErrors field in addition to the global errors field. userErrors contain business validation errors (invalid quantity, sold-out variant).
Frequent issues and solutions
"Field doesn't exist on type": this error occurs when you request a field that does not exist in the API version being used. Check the version in your URL (/api/2026-01/graphql.json) and consult the schema via GraphiQL.
"Access denied": verify that Storefront API permissions are correctly configured in the Headless channel. Each data type (products, collections, customers) requires a specific permission.
Empty metafields: metafields must be explicitly enabled for Storefront access in the metafield definition. A metafield that exists in the Admin API may not be visible through the Storefront API.
Incomplete pagination: always check pageInfo.hasNextPage and use endCursor to traverse all results. Without pagination, the Storefront API returns a maximum of 250 items per connection.
Frequently asked questions
How to manage cart persistence between sessions?
The Storefront API returns a unique cartId when creating a cart via cartCreate. To maintain the cart between sessions, store this cartId in an HTTP-only cookie or in the browser's localStorage. On each visit, query the existing cart via cart(id: $cartId) to restore its contents.
For authenticated customers, associate the cart with the customer identity via cartBuyerIdentityUpdate. The cart then persists server-side and automatically synchronizes across devices.
What is the security difference between public and delegated access tokens?
The public access token is designed to be exposed in front-end code. It only grants access to the store's public data (products, collections, prices) and cart operations. It does not allow access to personal customer data.
The delegated access token is generated after customer authentication via customerAccessTokenCreate. It grants access to personal data (addresses, order history) in addition to public data. This token must be managed exclusively server-side to protect personal information.
Is the Storefront API faster than a classic Liquid theme?
The Storefront API is not inherently "faster" than Liquid. Liquid is rendered server-side by Shopify's infrastructure, with internal cache optimizations. The difference lies in control: with the Storefront API, you fully control the rendering stack, caching, and optimization.
A well-optimized headless storefront (SSR + CDN + GraphQL caching) generally outperforms a Liquid theme on Core Web Vitals, particularly LCP and INP. However, a poorly configured storefront can be significantly slower than a standard Liquid theme.
When should you avoid using the Storefront API?
The Storefront API is not suitable in the following situations:
- Simple stores without specific needs: a standard Liquid theme covers 80% of requirements at a much lower development cost
- Teams without front-end expertise: headless requires proficiency in React, state management, and application deployment
- Limited budget: development, hosting, and maintenance costs for a headless storefront exceed those of a classic theme
- Need for full data access: the Storefront API is limited in write operations. For back-office operations, the Admin API remains essential
Next steps for your headless project
The Shopify Storefront API combined with GraphQL provides a powerful technical foundation for building custom e-commerce experiences. The move to headless commerce is not purely a technical decision: it involves the organization, budget, and team capabilities over the long term.
To move forward concretely:
- Prototype a product page with the Storefront API and your framework of choice (Next.js or Hydrogen)
- Evaluate performance compared to your current theme on Core Web Vitals
- Map the required data (products, metafields, customers) to size your GraphQL architecture
- Plan the caching and deployment strategy before going to production
For guidance on setting up your headless Shopify architecture, explore our Shopify headless development service.