Skip to main content
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

storeDomain
string
required
Your shop’s domain (e.g., shop.myshopify.com or custom domain)
apiVersion
string
required
The Storefront API version (e.g., 2025-01)
publicAccessToken
string
Public access token for browser-based apps (required if privateAccessToken not provided)
privateAccessToken
string
Private access token for server-side apps (required if publicAccessToken not provided)
clientName
string
Client name for tracking (sent in Shopify-Storefront-SDK-Variant-Source header)
retries
number
default:"0"
Number of retry attempts for failed requests (max: 3)
customFetchApi
function
Custom fetch implementation for server environments
logger
function
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

data
TData | any
Currently available data (may be partial)
errors
ResponseErrors
Any errors that occurred
extensions
Record<string, any>
Response metadata
hasNext
boolean
Whether more data is coming in the stream

Client Methods

config
StorefrontApiClientConfig
Client configuration including store domain, API version, and headers
getHeaders
(customHeaders?) => Record<string, string>
Returns merged default and custom headers
getApiUrl
(apiVersion?) => string
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',
});

Custom Headers

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:
1

Install codegen

npm install --save-dev @shopify/api-codegen-preset
2

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',
    }),
  },
};
3

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:
1

Install polyfill

npm install react-native-url-polyfill
2

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

Build docs developers (and LLMs) love