Introduction
Modern e-commerce experiences demand fast, flexible, and dynamic data fetching—and Shopify’s Storefront GraphQL API delivers just that. Unlike REST endpoints, GraphQL lets you request exactly the fields you need, reducing over-fetching and improving performance. In this post, we’ll explore how to authenticate with the Storefront API, craft essential product and collection queries, handle pagination, and integrate results into a typical JavaScript or React frontend. Whether you’re building a custom storefront, a headless site with Next.js, or enhancing theme sections, mastering the Storefront GraphQL API will unlock richer shopping experiences and tighter performance tuning.

Why Use the Storefront GraphQL API?
- Precise Data Fetching
Request only the fields you need—no more, no less. - Single Endpoint
Fetch products, collections, carts, and customer data through one unified endpoint. - Performance Gains
Smaller payloads and batched queries reduce latency and bandwidth. - Headless-Friendly
Ideal for JAMstack, React, Vue, or any frontend framework.
Authentication & Setup
Creating a Storefront Access Token
- In Shopify Admin, navigate to Apps → Develop apps.
- Create a new custom app and enable Storefront API access.
- Under Storefront API scopes, select permissions like
read_products
andread_collection_listings
. - Save and copy the Storefront access token.
Configuring Your HTTP Client
Here’s a simple fetch
wrapper in JavaScript:
jsCopyEditconst SHOP_DOMAIN = process.env.SHOPIFY_STORE_DOMAIN;
const STOREFRONT_TOKEN = process.env.SHOPIFY_STOREFRONT_TOKEN;
async function shopifyFetch(query, variables = {}) {
const response = await fetch(`https://${SHOP_DOMAIN}/api/2025-07/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(JSON.stringify(errors));
return data;
}
Querying Products
Basic Product Details
Fetch product titles, handles, and featured images:
graphqlCopyEditquery ProductList($first: Int!) {
products(first: $first) {
edges {
node {
id
title
handle
featuredImage {
url
altText
}
}
}
}
}
Invoke with:
jsCopyEditconst data = await shopifyFetch(`
query ($first: Int!) {
products(first: $first) {
edges { node { id title handle } }
}
}
`, { first: 10 });
console.log(data.products.edges);
Fetching Variants & Prices
Include variants, pricing, and inventory status:
graphqlCopyEditquery ProductWithVariants($handle: String!) {
productByHandle(handle: $handle) {
id
title
variants(first: 5) {
edges {
node {
id
title
priceV2 { amount currencyCode }
availableForSale
}
}
}
}
}
Working with Collections

Retrieving Collections and Their Products
Get collection title and up to 8 products per collection:
graphqlCopyEditquery CollectionList($first: Int!) {
collections(first: $first) {
edges {
node {
id
title
products(first: 8) {
edges { node { id title handle } }
}
}
}
}
}
Filtering Collections by Handle
Fetch a single collection by its handle:
graphqlCopyEditquery CollectionByHandle($handle: String!) {
collectionByHandle(handle: $handle) {
id
title
description
products(first: 12) {
edges { node { id title handle } }
}
}
}
Handling Pagination
GraphQL uses cursor-based pagination with pageInfo
and endCursor
:
graphqlCopyEditquery PaginatedProducts($first: Int!, $after: String) {
products(first: $first, after: $after) {
pageInfo { hasNextPage endCursor }
edges {
cursor
node { id title handle }
}
}
}
Client-side logic:
jsCopyEditlet cursor = null;
async function fetchAllProducts() {
const all = [];
do {
const { products } = await shopifyFetch(
`query ($first: Int!, $after: String) {
products(first: $first, after: $after) {
pageInfo { hasNextPage endCursor }
edges { node { id title } }
}
}`,
{ first: 50, after: cursor }
);
products.edges.forEach(e => all.push(e.node));
cursor = products.pageInfo.hasNextPage ? products.pageInfo.endCursor : null;
} while (cursor);
return all;
}
Mutations: Creating a Cart & Checkout
Create a New Cart
graphqlCopyEditmutation {
cartCreate {
cart { id }
userErrors { field message }
}
}
jsCopyEditconst { cartCreate } = await shopifyFetch(`
mutation { cartCreate { cart { id } } }
`);
const cartId = cartCreate.cart.id;
Add Lines to Cart
graphqlCopyEditmutation AddToCart($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart { id lines { id merchandise { ... on ProductVariant { id title } } } }
userErrors { field message }
}
}
jsCopyEditawait shopifyFetch(`
mutation ($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart { id lines { merchandise { ... on ProductVariant { title } } } }
}
}`, {
cartId,
lines: [{ merchandiseId: variantId, quantity: 2 }]
}
);
Integrating with React (or Next.js)

Using Apollo Client
bashCopyEditnpm install @apollo/client graphql
jsCopyEdit// lib/apollo.js
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
export const client = new ApolloClient({
link: new HttpLink({
uri: `https://${SHOP_DOMAIN}/api/2025-07/graphql.json`,
headers: { 'X-Shopify-Storefront-Access-Token': STOREFRONT_TOKEN }
}),
cache: new InMemoryCache()
});
jsxCopyEdit// pages/index.jsx
import { gql, useQuery } from '@apollo/client';
import { client } from '../lib/apollo';
const GET_PRODUCTS = gql`
query { products(first: 6) { edges { node { id title handle } } } }
`;
export default function Home() {
const { data, loading, error } = useQuery(GET_PRODUCTS, { client });
if (loading) return <p>Loading…</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
{data.products.edges.map(({ node }) => (
<a key={node.id} href={`/products/${node.handle}`}>
{node.title}
</a>
))}
</div>
);
}
Best Practices & Tips
- Batch Requests: Combine multiple queries into one request to reduce round trips.
- Error Handling: Always check
userErrors
in mutations and catch GraphQL errors. - Caching Strategy: Use
cache-first
for static content,network-only
for dynamic data like carts. - Version Awareness: Update your API version regularly to access new fields and avoid deprecation.
- Security: Never expose your access token client-side; proxy requests through a secure server when possible.

Conclusion
The Shopify Storefront GraphQL API empowers developers to build lightning-fast, query-optimized storefronts that deliver precisely the data customers need. By mastering authentication, crafting tailored queries, handling pagination, and integrating with frameworks like React or Next.js, you’ll create rich, high-performance shopping experiences. Start experimenting with product and collection queries, then layer in mutations for carts and checkouts—soon you’ll be harnessing the full power of Shopify’s headless commerce capabilities.