shopify-storefront-api

Build a headless Shopify frontend using the GraphQL Storefront API for product queries, cart management, and checkout with the Buy SDK

11 stars

Best use case

shopify-storefront-api is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Build a headless Shopify frontend using the GraphQL Storefront API for product queries, cart management, and checkout with the Buy SDK

Teams using shopify-storefront-api should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/shopify-storefront-api/SKILL.md --create-dirs "https://raw.githubusercontent.com/finsilabs/awesome-ecommerce-skills/main/skills/platform-shopify/shopify-storefront-api/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/shopify-storefront-api/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How shopify-storefront-api Compares

Feature / Agentshopify-storefront-apiStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Build a headless Shopify frontend using the GraphQL Storefront API for product queries, cart management, and checkout with the Buy SDK

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Shopify Storefront API

## Overview

The Shopify Storefront API is a public-facing GraphQL API that provides read and write access to a store's products, collections, cart, and checkout from any frontend. It uses a Storefront Access Token (distinct from Admin API tokens) and is safe to expose in client-side JavaScript. Use it to build headless storefronts with Next.js, Remix/Hydrogen, or any JS framework.

## When to Use This Skill

- When building a headless Shopify storefront with a custom frontend framework
- When creating a React Native or Flutter mobile app that needs product and cart data
- When embedding a Shopify buy button or product widget in a non-Shopify site
- When using Shopify Hydrogen (Remix-based) for a fully custom storefront experience
- When needing real-time product availability or pricing without the Admin API overhead
- When implementing cart persistence across sessions with Shopify's hosted cart

## Core Instructions

1. **Create a Storefront Access Token**

   In Shopify Admin → Apps → Develop apps → Your App → API credentials → Storefront API access token. Or via the Admin API:

   ```javascript
   // Via Admin API (one-time setup)
   const token = await admin.graphql(`
     mutation {
       storefrontAccessTokenCreate(input: { title: "Headless Frontend" }) {
         storefrontAccessToken {
           accessToken
           title
         }
         userErrors { field message }
       }
     }
   `);
   ```

   Storefront Access Tokens do not use the `shpat_` prefix (that prefix is for Admin API tokens). Storefront tokens are opaque strings safe to use in browser code — they only allow storefront-scoped operations.

2. **Set up the Storefront API client**

   Using the official `@shopify/storefront-api-client`:

   ```bash
   npm install @shopify/storefront-api-client
   ```

   ```typescript
   // lib/shopify.ts
   import { createStorefrontApiClient } from "@shopify/storefront-api-client";

   export const storefront = createStorefrontApiClient({
     storeDomain: process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN!, // e.g. "mystore.myshopify.com"
     apiVersion: "2025-01",
     publicAccessToken: process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_TOKEN!,
   });
   ```

   For server-side calls with a private access token (higher rate limits):

   ```typescript
   export const storefrontServer = createStorefrontApiClient({
     storeDomain: process.env.SHOPIFY_STORE_DOMAIN!,
     apiVersion: "2025-01",
     privateAccessToken: process.env.SHOPIFY_STOREFRONT_PRIVATE_TOKEN!,
   });
   ```

3. **Query products and collections**

   ```typescript
   // lib/products.ts
   export async function getProducts(first = 20, after?: string) {
     const { data, errors } = await storefront.request(`
       query GetProducts($first: Int!, $after: String) {
         products(first: $first, after: $after, sortKey: BEST_SELLING) {
           pageInfo {
             hasNextPage
             endCursor
           }
           edges {
             node {
               id
               title
               handle
               availableForSale
               priceRange {
                 minVariantPrice { amount currencyCode }
                 maxVariantPrice { amount currencyCode }
               }
               images(first: 1) {
                 edges {
                   node { url altText width height }
                 }
               }
               variants(first: 10) {
                 edges {
                   node {
                     id
                     title
                     availableForSale
                     selectedOptions { name value }
                     price { amount currencyCode }
                   }
                 }
               }
             }
           }
         }
       }
     `, { variables: { first, after } });

     if (errors) throw new Error(errors.message);
     return data.products;
   }
   ```

4. **Create and manage a cart**

   ```typescript
   // lib/cart.ts

   // Create a new cart
   export async function cartCreate(lines: { merchandiseId: string; quantity: number }[]) {
     const { data } = await storefront.request(`
       mutation CartCreate($lines: [CartLineInput!]) {
         cartCreate(input: { lines: $lines }) {
           cart {
             id
             checkoutUrl
             lines(first: 50) {
               edges {
                 node {
                   id
                   quantity
                   merchandise {
                     ... on ProductVariant {
                       id
                       title
                       price { amount currencyCode }
                       product { title handle }
                     }
                   }
                 }
               }
             }
             cost {
               subtotalAmount { amount currencyCode }
               totalAmount { amount currencyCode }
             }
           }
           userErrors { field message }
         }
       }
     `, { variables: { lines } });
     return data.cartCreate;
   }

   // Add lines to existing cart
   export async function cartLinesAdd(cartId: string, lines: { merchandiseId: string; quantity: number }[]) {
     const { data } = await storefront.request(`
       mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
         cartLinesAdd(cartId: $cartId, lines: $lines) {
           cart { id checkoutUrl }
           userErrors { field message }
         }
       }
     `, { variables: { cartId, lines } });
     return data.cartLinesAdd;
   }
   ```

5. **Persist cart ID and redirect to checkout**

   ```typescript
   // hooks/useCart.ts
   import { useState, useEffect } from "react";
   import { cartCreate, cartLinesAdd } from "../lib/cart";

   const CART_ID_KEY = "shopify_cart_id";

   export function useCart() {
     const [cartId, setCartId] = useState<string | null>(null);
     const [checkoutUrl, setCheckoutUrl] = useState<string | null>(null);

     useEffect(() => {
       setCartId(localStorage.getItem(CART_ID_KEY));
     }, []);

     const addToCart = async (variantId: string, quantity = 1) => {
       const lines = [{ merchandiseId: variantId, quantity }];
       if (cartId) {
         const result = await cartLinesAdd(cartId, lines);
         setCheckoutUrl(result.cart.checkoutUrl);
       } else {
         const result = await cartCreate(lines);
         const newCartId = result.cart.id;
         localStorage.setItem(CART_ID_KEY, newCartId);
         setCartId(newCartId);
         setCheckoutUrl(result.cart.checkoutUrl);
       }
     };

     const goToCheckout = () => {
       if (checkoutUrl) window.location.href = checkoutUrl;
     };

     return { addToCart, goToCheckout, cartId };
   }
   ```

## Examples

### Product Detail Page with variant selection (Next.js)

```typescript
// app/products/[handle]/page.tsx
import { storefront } from "@/lib/shopify";

async function getProduct(handle: string) {
  const { data } = await storefront.request(`
    query GetProduct($handle: String!) {
      product(handle: $handle) {
        id
        title
        descriptionHtml
        seo { title description }
        images(first: 10) {
          edges { node { url altText } }
        }
        options {
          id name values
        }
        variants(first: 100) {
          edges {
            node {
              id
              availableForSale
              selectedOptions { name value }
              price { amount currencyCode }
              compareAtPrice { amount currencyCode }
            }
          }
        }
      }
    }
  `, { variables: { handle } });
  return data.product;
}

export default async function ProductPage({ params }: { params: { handle: string } }) {
  const product = await getProduct(params.handle);
  // Render product with client-side variant picker
  return <ProductDetail product={product} />;
}

// Generate static params for all products
export async function generateStaticParams() {
  const { data } = await storefront.request(`
    query { products(first: 200) { edges { node { handle } } } }
  `);
  return data.products.edges.map(({ node }: { node: { handle: string } }) => ({
    handle: node.handle,
  }));
}
```

### Predictive search

```typescript
export async function predictiveSearch(query: string) {
  const { data } = await storefront.request(`
    query PredictiveSearch($query: String!) {
      predictiveSearch(query: $query, limit: 5, types: [PRODUCT, COLLECTION, ARTICLE]) {
        products {
          id title handle
          featuredImage { url altText }
          priceRange { minVariantPrice { amount currencyCode } }
        }
        collections {
          id title handle
          image { url altText }
        }
      }
    }
  `, { variables: { query } });
  return data.predictiveSearch;
}
```

## Best Practices

- **Use private tokens server-side** — private Storefront Access Tokens have higher rate limits (1000 req/s vs 100 req/s) and should never be exposed to browsers
- **Fetch product data at build time** when possible (ISR or SSG) — the Storefront API rate limits apply per store, not per customer
- **Always check `availableForSale`** on both product and variant before showing Add-to-Cart — a product can be available while individual variants are sold out
- **Paginate with `after` cursors**, not offsets — the Storefront API uses cursor-based pagination; store `endCursor` for next-page queries
- **Cache collection and product queries** with Next.js `fetch` cache tags or React cache — product data rarely changes in real time
- **Use `@inContext` directive** for international pricing — `@inContext(country: CA, language: EN)` returns prices in the buyer's currency
- **Fragment reuse** — define GraphQL fragments (e.g., `ProductFragment`) to avoid duplicating field selections across queries
- **Handle `userErrors`** on all mutations — cart mutations return `userErrors` array; check it before updating local state

## Common Pitfalls

| Problem | Solution |
|---------|----------|
| Rate limit errors (429) | Use private access token server-side and implement request batching; avoid N+1 product queries |
| Cart ID lost after page reload | Persist `cartId` in `localStorage` or a cookie; create a new cart only if none exists |
| Product prices show in wrong currency | Add `@inContext(country: $country)` directive and pass buyer's country via geolocation |
| `product(handle:)` returns null | Handle slugified handles correctly — Shopify handles are lowercase with hyphens; check exact slug |
| Checkout redirect fails on mobile Safari | Use `window.location.href = checkoutUrl` inside a user gesture handler, not async callback |
| Variant not found when selecting options | Use client-side filtering of `variants.edges` by matching all `selectedOptions`, not just one |

## Related Skills

- @shopify-admin-api
- @shopify-app-development
- @shopify-checkout-extensions
- @headless-commerce-architecture
- @graphql-api-design

Related Skills

storefront-theming

11
from finsilabs/awesome-ecommerce-skills

Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode

responsive-storefront

11
from finsilabs/awesome-ecommerce-skills

Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion

shopify-webhooks

11
from finsilabs/awesome-ecommerce-skills

Register, verify, and reliably process Shopify webhook events for orders, inventory, and customers with HMAC validation and idempotency handling

shopify-theme-development

11
from finsilabs/awesome-ecommerce-skills

Build and customize Shopify themes using Liquid templating, JSON sections, dynamic blocks, and theme app extensions for added functionality

shopify-metafields

11
from finsilabs/awesome-ecommerce-skills

Store custom data on any Shopify resource — products, orders, customers — using typed metafield definitions accessible from Liquid and the Storefront API

shopify-checkout-extensions

11
from finsilabs/awesome-ecommerce-skills

Customize Shopify's checkout with UI extensions for upsells and custom fields, plus Shopify Functions for serverless discount and shipping logic

shopify-hydrogen

11
from finsilabs/awesome-ecommerce-skills

Build a custom Shopify storefront using the Hydrogen React framework with Remix routing and deploy it to Shopify's Oxygen edge hosting

pwa-storefront

11
from finsilabs/awesome-ecommerce-skills

Turn your store into an installable Progressive Web App with offline product browsing, push notifications, and home screen access for mobile shoppers

jamstack-storefront

11
from finsilabs/awesome-ecommerce-skills

Build a blazing-fast storefront with Next.js or Astro that pre-renders product pages as static HTML and fetches live data from commerce APIs

wishlist-save-for-later

11
from finsilabs/awesome-ecommerce-skills

Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price

search-autocomplete

11
from finsilabs/awesome-ecommerce-skills

Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch

recently-viewed-products

11
from finsilabs/awesome-ecommerce-skills

Show shoppers the products they recently browsed using browser storage so they can easily pick up where they left off on your store