What the Storefront API Unlocks for Developers
The Shopify Storefront API is the GraphQL interface that powers every headless Shopify storefront. Whether you are building with Hydrogen, Next.js, a mobile app, or even an in-store kiosk, this API is how your frontend reads product data, manages carts, and initiates checkout.
Unlike Shopify's Admin API (which manages the backend — products, orders, fulfillment), the Storefront API is designed for customer-facing experiences. It is optimized for speed, has no rate limits on read requests, and supports both public and private access patterns. Every custom storefront built on Shopify in 2026 runs on this API.
This Shopify Storefront API getting started guide walks you from zero to a working integration. By the end, you will be able to query products, build a cart, and send customers to checkout — the three fundamental operations that make a headless storefront functional. If you are exploring the broader headless and Hydrogen ecosystem, this API is the foundation everything else builds on.
Setting Up API Access
Before making your first query, you need access tokens. Shopify provides two access paths, each designed for different use cases.
Install the Headless Sales Channel
- Log in to your Shopify admin
- Navigate to Settings > Apps and sales channels > Shopify App Store
- Search for and install the Headless sales channel
- Click Create storefront to generate your access tokens
This gives you two tokens:
- Public access token — safe to embed in browser-side JavaScript. Limited to read operations and cart mutations.
- Private access token — for server-side use only. Provides access to all Storefront API features including customer operations.
Authentication Headers
For public (client-side) requests:
X-Shopify-Storefront-Access-Token: your-public-token
Content-Type: application/jsonFor private (server-side) requests:
Shopify-Storefront-Private-Token: your-private-token
Content-Type: application/jsonCritical: When making server-side requests, always include the Shopify-Storefront-Buyer-IP header with the customer's real IP address. This allows Shopify to enforce bot protection accurately and prevents your server from being flagged as malicious traffic. The Shopify API authentication documentation covers all header requirements.
API Endpoint and Versioning
The Storefront API endpoint follows this pattern:
https://{your-store}.myshopify.com/api/{version}/graphql.jsonShopify releases new API versions quarterly. As of March 2026, the current stable version is 2026-01. Always pin your integration to a specific version:
https://your-store.myshopify.com/api/2026-01/graphql.json| Version | Release Date | Supported Until |
|---|---|---|
| 2026-01 | January 2026 | January 2027 |
| 2025-10 | October 2025 | October 2026 |
| 2025-07 | July 2025 | July 2026 |
Your First Query: Fetching Products

The most common Storefront API operation is reading product data. Here is a complete working example.
Basic Product Query
query GetProducts {
products(first: 12) {
edges {
node {
id
title
handle
description
priceRange {
minVariantPrice {
amount
currencyCode
}
maxVariantPrice {
amount
currencyCode
}
}
images(first: 1) {
edges {
node {
url
altText
width
height
}
}
}
variants(first: 10) {
edges {
node {
id
title
availableForSale
price {
amount
currencyCode
}
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Making the Request (JavaScript)
const STOREFRONT_API_URL = 'https://your-store.myshopify.com/api/2026-01/graphql.json';
const STOREFRONT_ACCESS_TOKEN = 'your-public-token';
async function fetchProducts() {
const response = await fetch(STOREFRONT_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Storefront-Access-Token': STOREFRONT_ACCESS_TOKEN,
},
body: JSON.stringify({
query: `
query GetProducts {
products(first: 12) {
edges {
node {
id
title
handle
priceRange {
minVariantPrice {
amount
currencyCode
}
}
}
}
}
}
`,
}),
});
const { data } = await response.json();
return data.products.edges.map(edge => edge.node);
}Understanding Connections and Edges
The Storefront API uses Relay-style pagination. Every list query returns a connection with edges and pageInfo:
- `edges` — array of results, each containing a
node(the actual data) and acursor(pagination position) - `pageInfo` — contains
hasNextPage,hasPreviousPage,startCursor, andendCursor
This pattern is consistent across products, collections, variants, images, and every other list type in the API. The Shopify Storefront API reference documents every available connection.
Querying Collections and Navigation

Collections are how Shopify organizes products. Your headless storefront needs to fetch collection data for category pages and navigation menus.
Fetch a Single Collection with Products
query GetCollection($handle: String!) {
collection(handle: $handle) {
id
title
description
image {
url
altText
}
products(first: 24, sortKey: BEST_SELLING) {
edges {
node {
id
title
handle
priceRange {
minVariantPrice {
amount
currencyCode
}
}
images(first: 1) {
edges {
node {
url
altText
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}Product Sorting Options
The Storefront API supports these sort keys for products within collections:
| Sort Key | Description |
|---|---|
| BEST_SELLING | Highest sales volume first |
| CREATED_AT | Newest first |
| PRICE | Lowest price first (use reverse: true for highest) |
| TITLE | Alphabetical |
| COLLECTION_DEFAULT | The order set in Shopify admin |
| RELEVANCE | Search relevance (only for search queries) |
Product Filtering
The Storefront API supports server-side product filtering within collections:
query FilteredCollection($handle: String!, $filters: [ProductFilter!]) {
collection(handle: $handle) {
products(first: 24, filters: $filters) {
filters {
id
label
type
values {
id
label
count
}
}
edges {
node {
id
title
handle
}
}
}
}
}Available filter types include price range, availability, product type, vendor, variant options (size, color), and tag. Understanding how to build filtered collection pages is essential for any Shopify theme or storefront development.
Building the Cart with Mutations
The Cart API is the write side of the Storefront API. Every add-to-cart click, quantity change, and discount code application is a cart mutation.
Create a Cart
mutation CreateCart($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
}
}
}
userErrors {
field
message
}
}
}Variables:
{
"input": {
"lines": [
{
"merchandiseId": "gid://shopify/ProductVariant/12345678",
"quantity": 1
}
]
}
}Add Items to an Existing Cart
mutation AddToCart($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart {
id
totalQuantity
lines(first: 50) {
edges {
node {
id
quantity
merchandise {
... on ProductVariant {
id
title
}
}
}
}
}
cost {
totalAmount {
amount
currencyCode
}
}
}
userErrors {
field
message
}
}
}Update and Remove Items
# Update quantity
mutation UpdateCartLine($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
cartLinesUpdate(cartId: $cartId, lines: $lines) {
cart {
id
totalQuantity
}
userErrors {
field
message
}
}
}
# Remove items
mutation RemoveCartLine($cartId: ID!, $lineIds: [ID!]!) {
cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
cart {
id
totalQuantity
}
userErrors {
field
message
}
}
}Apply Discount Codes
mutation ApplyDiscount($cartId: ID!, $discountCodes: [String!]!) {
cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) {
cart {
id
discountCodes {
code
applicable
}
cost {
totalAmount {
amount
currencyCode
}
}
}
userErrors {
field
message
}
}
}Important: The Checkout API was deprecated in April 2025. All new headless builds must use the Cart API for cart management and checkout URL generation. The DigitalSuits migration guide covers the transition from the old Checkout API to the Cart API.
Handling Checkout and Payments

The checkout flow in a headless Shopify storefront is straightforward because Shopify handles it entirely.
The Checkout Redirect Pattern
Every cart object includes a checkoutUrl field. When the customer clicks "Checkout," you redirect them to this URL:
async function redirectToCheckout(cartId) {
const cart = await fetchCart(cartId);
window.location.href = cart.checkoutUrl;
}The customer completes checkout on Shopify's hosted checkout page (checkout.shopify.com or your custom domain on Shopify Plus). After payment, they return to your storefront via the thank-you page URL configured in Shopify settings.
Buyer Identity
For returning customers, attach their identity to the cart for personalized pricing and saved addresses:
mutation UpdateBuyerIdentity($cartId: ID!, $buyerIdentity: CartBuyerIdentityInput!) {
cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: $buyerIdentity) {
cart {
id
buyerIdentity {
email
countryCode
}
}
userErrors {
field
message
}
}
}Checkout Customization Options
| Plan | Customization Level |
|---|---|
| Basic / Shopify / Advanced | Shopify standard checkout (branding only) |
| Shopify Plus | Checkout extensions (custom UI, logic, and payment methods) |
For most merchants, Shopify's standard checkout converts well and requires no customization. If you are on Shopify Plus and need custom checkout flows, checkout extensions use a separate API surface. Our guide to Shopify headless commerce covers checkout architecture in greater depth.
Customer Account API Integration
If your storefront supports customer accounts, the Storefront API provides endpoints for authentication and account management.
Customer Access Tokens
Authenticate customers and create access tokens:
mutation CustomerAccessTokenCreate($input: CustomerAccessTokenCreateInput!) {
customerAccessTokenCreate(input: $input) {
customerAccessToken {
accessToken
expiresAt
}
customerUserErrors {
code
field
message
}
}
}Variables:
{
"input": {
"email": "customer@example.com",
"password": "customer-password"
}
}Fetch Customer Data
With an access token, query the customer's profile, order history, and addresses:
query GetCustomer($customerAccessToken: String!) {
customer(customerAccessToken: $customerAccessToken) {
id
firstName
lastName
email
orders(first: 10) {
edges {
node {
id
orderNumber
totalPrice {
amount
currencyCode
}
processedAt
fulfillmentStatus
}
}
}
addresses(first: 5) {
edges {
node {
id
address1
city
province
country
zip
}
}
}
}
}Account Management Mutations
| Operation | Mutation | Purpose |
|---|---|---|
| Register | customerCreate | Create new customer account |
| Login | customerAccessTokenCreate | Generate session token |
| Password reset | customerRecover | Send reset email |
| Update profile | customerUpdate | Change name, email, password |
| Add address | customerAddressCreate | Add shipping address |
| Update address | customerAddressUpdate | Modify existing address |
The Shopify Storefront API Learning Kit on GitHub provides complete working examples for customer account flows.
Rate Limits and Performance Best Practices

One of the Storefront API's biggest advantages is its generous rate limiting — or lack thereof for most operations.
Rate Limit Structure
- Read queries (products, collections, pages) — no rate limit. You can query as frequently as needed.
- Mutations (cart operations, customer actions) — throttled per shop, with additional per-IP throttling for authenticated requests.
- Checkout creation — limited per minute to prevent abuse.
- Bot protection — if Shopify's systems detect malicious traffic patterns, requests receive a
430 Security Rejectionresponse.
Query Cost and Complexity
While there are no request-count rate limits on reads, the Storefront API does enforce query complexity limits. Every field in your query has a cost, and the total cost per query is capped.
Best practices to stay within complexity limits:
- Request only the fields you actually use
- Limit
firstandlastarguments to the minimum necessary - Avoid deeply nested connections (3+ levels deep)
- Use separate queries for different page sections rather than one massive query
Caching Strategy
| Data Type | Cache Duration | Rationale |
|---|---|---|
| Product catalog | 5-15 minutes | Changes infrequently |
| Collection listings | 5-15 minutes | Changes infrequently |
| Inventory/availability | 30-60 seconds | Changes with purchases |
| Cart data | Never cache | Always personalized |
| Customer data | Never cache | Always personalized |
| Navigation/menus | 1 hour | Rarely changes |
Optimizing Query Performance
According to Shopify's API rate limits documentation, the key to fast Storefront API queries is minimizing the data you request:
- Use fragments — define reusable field sets for products, variants, and images
- Implement pagination — never request
first: 250when you only display 12 items - Prefetch on hover — load the next page's data when a user hovers over a link
- Batch queries — combine related queries into a single request where possible
Search and Predictive Search
The Storefront API includes built-in search functionality that powers product discovery on headless storefronts.
Full Search Query
query Search($query: String!) {
search(query: $query, first: 20, types: [PRODUCT, ARTICLE, PAGE]) {
edges {
node {
... on Product {
id
title
handle
priceRange {
minVariantPrice {
amount
currencyCode
}
}
}
... on Article {
id
title
handle
}
... on Page {
id
title
handle
}
}
}
totalCount
}
}Predictive Search
For instant search-as-you-type experiences:
query PredictiveSearch($query: String!) {
predictiveSearch(query: $query, limit: 5, types: [PRODUCT, COLLECTION, QUERY]) {
products {
id
title
handle
variants(first: 1) {
edges {
node {
price {
amount
currencyCode
}
}
}
}
}
collections {
id
title
handle
}
queries {
text
styledText
}
}
}Predictive search results are lightweight and fast — designed for the sub-200ms response times that search-as-you-type demands. If you are building search into an existing Shopify app, the Storefront API search provides a solid foundation.
Internationalization and Multi-Currency
The Storefront API supports international commerce through context — localization parameters that affect pricing, availability, and language.
Setting the Buyer's Context
Pass a @inContext directive to get localized data:
query GetLocalizedProducts @inContext(country: CA, language: FR) {
products(first: 10) {
edges {
node {
title
priceRange {
minVariantPrice {
amount
currencyCode
}
}
}
}
}
}Supported Context Parameters
| Parameter | Values | Effect |
|---|---|---|
| country | ISO 3166-1 alpha-2 codes (US, CA, GB, etc.) | Localizes pricing and availability |
| language | ISO 639-1 codes (EN, FR, ES, etc.) | Translates content |
Multi-Currency Pricing
When you set the country context, product prices automatically return in the local currency configured in your Shopify Markets settings. This means:
- A product priced at $50 USD returns as ~$68 CAD when queried with
country: CA - Currency conversion uses the rates configured in your Shopify admin
- Fixed local pricing (if configured) overrides automatic conversion
The HelloBizMia Shopify API guide provides additional context on how the internationalization features interact with Shopify Markets configuration.
Common Mistakes and How to Avoid Them
These are the errors that trip up most developers when first working with the Shopify Storefront API.
Exposing the Private Token Client-Side
The private access token grants elevated access to the API. If embedded in client-side JavaScript, anyone can extract it from your page source.
Fix: Use the public token for all browser-side requests. Reserve the private token for server-side code only. Never include it in your frontend bundle.
Not Handling userErrors
Every mutation returns a userErrors array. Ignoring it means silent failures — items not added to cart, discounts not applied, customers not created.
Fix: Always check userErrors after every mutation:
const { data } = await cartLinesAdd(cartId, lines);
if (data.cartLinesAdd.userErrors.length > 0) {
// Handle the error — show message to user
console.error(data.cartLinesAdd.userErrors);
return;
}
// Proceed with success flowOver-Fetching Data
Requesting every field on every product when you only need titles and prices wastes bandwidth and increases query cost.
Fix: Request only the fields you display. Use GraphQL fragments to standardize your field selections across components.
Ignoring API Versioning
Queries that work on 2025-07 might break on 2026-01 due to field deprecations or breaking changes.
Fix: Pin your integration to a specific API version. Subscribe to Shopify's developer changelog. Test against new versions before upgrading.
Not Forwarding Buyer IP Addresses
Server-side requests without the Shopify-Storefront-Buyer-IP header can trigger Shopify's bot protection, causing legitimate customer requests to be blocked.
Fix: Always extract the customer's IP from the incoming request and forward it in your API calls.
| Mistake | Risk Level | Fix |
|---|---|---|
| Private token in client code | Critical | Use public token for browser requests |
| Ignoring userErrors | High | Check after every mutation |
| Over-fetching data | Medium | Request only needed fields |
| No API version pinning | Medium | Pin to a specific version |
| Missing buyer IP header | High | Forward IP on all server requests |
Building a Complete Storefront: Putting It All Together

Here is the query architecture for a complete headless storefront, showing which API operations power each page.
Page-to-Query Map
| Page | Primary Query | Mutations |
|---|---|---|
| Homepage | products (featured), collections (featured) | None |
| Collection page | collection with products (filtered, sorted, paginated) | None |
| Product page | product by handle (with variants, images, metafields) | cartCreate, cartLinesAdd |
| Cart page | cart by ID | cartLinesUpdate, cartLinesRemove, cartDiscountCodesUpdate |
| Search results | search or predictiveSearch | None |
| Customer login | None | customerAccessTokenCreate |
| Customer account | customer (orders, addresses) | customerUpdate, customerAddressCreate |
Architecture Pattern
Browser (React/Next.js/Hydrogen)
→ Server-side loader (fetch data with private token)
→ Storefront API (GraphQL)
← Render HTML with data
→ Client-side mutations (cart actions with public token)
→ Storefront API (GraphQL)
← Update UI optimisticallyThis pattern keeps sensitive operations on the server while allowing fast client-side interactions for cart management. The headless Shopify with Webflow guide shows how this same API powers no-code headless builds.
The Shopify Storefront API is the foundation of every headless Shopify experience. Master the product queries, cart mutations, and checkout redirect pattern, and you can build any custom commerce experience on any platform. Start with the basics covered in this guide, test in Shopify's GraphiQL explorer, and expand from there.
For more developer resources and community support, visit the Talk Shop blog or join the Shopify developer community to connect with other builders.
What are you building with the Storefront API? Share your project in the community.

About Talk Shop
The Talk Shop team — insights from our community of Shopify developers, merchants, and experts.
Related Insights
The ecommerce newsletter that's actually useful.
Daily trends, teardowns, and tactics from the top 1% of ecommerce brands. Delivered every morning.
