The Storefront API Client provides a lightweight interface for building custom storefronts. It supports GraphQL queries with streaming responses, making it ideal for customer-facing applications.
Installation
npm install @shopify/storefront-api-client
CDN Usage
You can also use the UMD build via CDN:
< script src = "https://unpkg.com/@shopify/[email protected] /dist/umd/storefront-api-client.min.js" ></ script >
< script >
const client = ShopifyStorefrontAPIClient . createStorefrontApiClient ({
storeDomain: 'your-shop.myshopify.com' ,
apiVersion: '2025-01' ,
publicAccessToken: 'your-public-token' ,
});
</ script >
Basic Setup
Public Access Token (Browser)
For client-side applications, use a public access token:
import { createStorefrontApiClient } from '@shopify/storefront-api-client' ;
const client = createStorefrontApiClient ({
storeDomain: 'your-shop-name.myshopify.com' ,
apiVersion: '2025-01' ,
publicAccessToken: 'your-storefront-public-access-token' ,
});
Private Access Token (Server)
Private access tokens should only be used in server-to-server implementations. Never expose them in browser code.
For server-side applications:
import { createStorefrontApiClient } from '@shopify/storefront-api-client' ;
import { fetch as nodeFetch } from 'node-fetch' ;
const client = createStorefrontApiClient ({
storeDomain: 'your-shop-name.myshopify.com' ,
apiVersion: '2025-01' ,
privateAccessToken: 'your-storefront-private-access-token' ,
customFetchApi: nodeFetch ,
});
Configuration Options
Your shop’s domain (e.g., shop.myshopify.com or custom domain)
The Storefront API version (e.g., 2025-01)
Public access token for browser-based apps (required if privateAccessToken not provided)
Private access token for server-side apps (required if publicAccessToken not provided)
Client name for tracking (sent in Shopify-Storefront-SDK-Variant-Source header)
Number of retry attempts for failed requests (max: 3)
Custom fetch implementation for server environments
Logger function for debugging and monitoring
Making Requests
Basic Query
const productQuery = `
query ProductQuery($handle: String!) {
product(handle: $handle) {
id
title
handle
description
priceRange {
minVariantPrice {
amount
currencyCode
}
}
}
}
` ;
const { data , errors , extensions } = await client . request ( productQuery , {
variables: {
handle: 'sample-product' ,
},
});
if ( errors ) {
console . error ( 'Error:' , errors . message );
} else {
console . log ( 'Product:' , data . product . title );
}
Localized Queries
Use the @inContext directive for localized content:
const cartCreateMutation = `
mutation CreateCart($input: CartInput!, $country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
cartCreate(input: $input) {
cart {
id
checkoutUrl
cost {
totalAmount {
amount
currencyCode
}
}
}
userErrors {
field
message
}
}
}
` ;
const { data } = await client . request ( cartCreateMutation , {
variables: {
input: {},
country: 'JP' ,
language: 'JA' ,
},
});
Streaming Responses
Use the @defer directive for progressive data loading:
const productQuery = `
query ProductWithDefer($handle: String!) {
product(handle: $handle) {
id
handle
title
... @defer(label: "details") {
description
images(first: 10) {
edges {
node {
url
altText
}
}
}
}
}
}
` ;
const responseStream = await client . requestStream ( productQuery , {
variables: { handle: 'sample-product' },
});
// Process data as it arrives
for await ( const response of responseStream ) {
const { data , errors , extensions , hasNext } = response ;
if ( data ) {
console . log ( 'Partial data received:' , data );
}
if ( ! hasNext ) {
console . log ( 'Stream complete' );
break ;
}
}
Stream Response Type
Currently available data (may be partial)
Whether more data is coming in the stream
Client Methods
config
StorefrontApiClientConfig
Client configuration including store domain, API version, and headers
Returns merged default and custom headers
Returns the API URL for the specified version
fetch
(operation, options?) => Promise<Response>
Makes a raw fetch request and returns the Response
request
<TData>(operation, options?) => Promise<ClientResponse<TData>>
Makes a request and returns a normalized response
requestStream
<TData>(operation, options?) => Promise<AsyncIterator<ClientStreamResponse<TData>>>
Makes a streaming request for operations with @defer
Advanced Usage
Dynamic API Version
Override the API version per request:
const { data } = await client . request ( productQuery , {
variables: { handle: 'product' },
apiVersion: '2024-10' ,
});
Add custom headers for tracking or feature flags:
const { data } = await client . request ( productQuery , {
variables: { handle: 'product' },
headers: {
'Shopify-Storefront-Id' : 'my-storefront-id' ,
},
});
Request-Level Retries
const { data } = await client . request ( productQuery , {
variables: { handle: 'product' },
retries: 2 ,
});
Using Raw Fetch
Get the raw Response object:
const response = await client . fetch ( shopQuery );
if ( response . ok ) {
const { data , errors , extensions } = await response . json ();
}
Type Safety
Generate TypeScript types for your queries:
Install codegen
npm install --save-dev @shopify/api-codegen-preset
Configure codegen
// .graphqlrc.ts
import { ApiType , shopifyApiProject } from '@shopify/api-codegen-preset' ;
export default {
schema: 'https://shopify.dev/storefront-graphql-direct-proxy' ,
documents: [ '*.ts' , '!node_modules' ] ,
projects: {
default: shopifyApiProject ({
apiType: ApiType . Storefront ,
apiVersion: '2025-01' ,
outputDir: './types' ,
}),
} ,
} ;
Use typed queries
const { data } = await client . request < ProductQuery >(
`#graphql
query Product($handle: String!) {
product(handle: $handle) {
id
title
}
}` ,
{ variables: { handle: 'shoe' }}
);
// data.product is fully typed
console . log ( data ?. product . title );
Common Use Cases
Fetch Products
const productsQuery = `
query Products($first: Int!) {
products(first: $first) {
edges {
node {
id
title
handle
priceRange {
minVariantPrice {
amount
currencyCode
}
}
images(first: 1) {
edges {
node {
url
altText
}
}
}
}
}
}
}
` ;
const { data } = await client . request ( productsQuery , {
variables: { first: 20 },
});
Create Cart
const cartCreateMutation = `
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
}
}
}
}
}
}
}
userErrors {
field
message
}
}
}
` ;
const { data } = await client . request ( cartCreateMutation , {
variables: {
input: {
lines: [
{
merchandiseId: 'gid://shopify/ProductVariant/123' ,
quantity: 1 ,
},
],
},
},
});
Search Products
const searchQuery = `
query Search($query: String!) {
search(query: $query, first: 10, types: PRODUCT) {
edges {
node {
... on Product {
id
title
handle
}
}
}
}
}
` ;
const { data } = await client . request ( searchQuery , {
variables: { query: 'shoes' },
});
Error Handling
const { data , errors } = await client . request ( query , { variables });
if ( errors ) {
// Network or API errors
console . error ( 'Status:' , errors . networkStatusCode );
console . error ( 'Message:' , errors . message );
if ( errors . graphQLErrors ) {
// GraphQL-specific errors
errors . graphQLErrors . forEach (( error ) => {
console . error ( `GraphQL Error: ${ error . message } ` );
});
}
} else {
// Check for user errors in mutations
if ( data . cartCreate ?. userErrors ?. length > 0 ) {
data . cartCreate . userErrors . forEach (( error ) => {
console . error ( `User Error: ${ error . message } ` );
});
}
}
Logging
Monitor API requests with a custom logger:
const client = createStorefrontApiClient ({
storeDomain: 'shop.myshopify.com' ,
apiVersion: '2025-01' ,
publicAccessToken: 'token' ,
logger : ( log ) => {
switch ( log . type ) {
case 'HTTP-Response' :
console . log ( 'Response:' , log . content . response . status );
break ;
case 'HTTP-Retry' :
console . log ( `Retry ${ log . content . retryAttempt } ` );
break ;
case 'Unsupported_Api_Version' :
console . warn ( 'Unsupported version:' , log . content . apiVersion );
break ;
}
},
});
React Native
When using this package in React Native, you may need to polyfill the URL API:
Install polyfill
npm install react-native-url-polyfill
Import in entry file
// index.js or App.tsx
import 'react-native-url-polyfill/auto' ;
Best Practices
Use Public Tokens in Browsers : Always use public access tokens for client-side code. Private tokens should only be used on servers.
Leverage @defer : Use the @defer directive to load critical data first and defer heavy content, improving perceived performance.
Rate Limits : The Storefront API has rate limits based on your plan. Monitor the extensions.cost field and implement appropriate retry logic.
Next Steps
API Codegen Generate TypeScript types for your queries
Storefront API Docs Explore the full Storefront API schema