Querying Your Storefront with the Shopify Storefront GraphQL API

Table of Contents
Big thanks to our contributors those make our blogs possible.

Our growing community of contributors bring their unique insights from around the world to power our blog. 

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

  1. In Shopify Admin, navigate to AppsDevelop apps.
  2. Create a new custom app and enable Storefront API access.
  3. Under Storefront API scopes, select permissions like read_products and read_collection_listings.
  4. 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.

Let's connect on TikTok

Join our newsletter to stay updated

Sydney Based Software Solutions Professional who is crafting exceptional systems and applications to solve a diverse range of problems for the past 10 years.

Share the Post

Related Posts