Skip to main content
The GraphQL Client is a lightweight, generic client for interacting with any of Shopify’s GraphQL APIs. It provides core GraphQL functionality including request handling, streaming responses, and automatic retries.
For most use cases, use the Admin API Client or Storefront API Client instead. Use this client when you need direct control over the API URL and headers, or when working with custom GraphQL endpoints.

Installation

npm install @shopify/graphql-client

CDN Usage

You can use the UMD build via CDN:
<script src="https://unpkg.com/@shopify/[email protected]/dist/umd/graphql-client.min.js"></script>

<script>
  const client = ShopifyGraphQLClient.createGraphQLClient({
    url: 'https://shop.myshopify.com/api/2025-01/graphql.json',
    headers: {
      'Content-Type': 'application/json',
      'X-Shopify-Storefront-Access-Token': 'token',
    },
  });
</script>

Basic Setup

import {createGraphQLClient} from '@shopify/graphql-client';

const client = createGraphQLClient({
  url: 'https://your-shop.myshopify.com/api/2025-01/graphql.json',
  headers: {
    'Content-Type': 'application/json',
    'X-Shopify-Storefront-Access-Token': 'your-token',
  },
  retries: 1,
});

Configuration Options

url
string
required
The complete GraphQL API endpoint URL
headers
Record<string, string | string[]>
required
Headers to include in all requests (e.g., authentication, content-type)
retries
number
default:"0"
Number of retry attempts for failed requests (max: 3)
customFetchApi
function
Custom fetch implementation for Node.js or other environments
logger
function
Logger function for debugging and monitoring

Making Requests

Using request()

The request() method returns a normalized response:
const productQuery = `
  query ProductQuery($handle: String!) {
    product(handle: $handle) {
      id
      title
      handle
    }
  }
`;

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);
}

Using fetch()

The fetch() method returns the raw Response object:
const shopQuery = `
  query {
    shop {
      name
      email
    }
  }
`;

const response = await client.fetch(shopQuery);

if (response.ok) {
  const {data, errors, extensions} = await response.json();
  console.log('Shop:', data.shop.name);
}

Response Types

ClientResponse

data
TData | any
The GraphQL response data. Type is TData if provided, otherwise any
errors
ResponseErrors
API or network errors (excludes UserErrors)
extensions
Record<string, any>
Additional response metadata and context

ResponseErrors

networkStatusCode
number
HTTP response status code
message
string
Error message description
graphQLErrors
any[]
Array of GraphQL errors from the API
response
Response
Raw Response object from fetch

Streaming Responses

Use requestStream() for operations with the @defer directive:
const productQuery = `
  query ProductWithDefer($handle: String!) {
    product(handle: $handle) {
      id
      handle
      ... @defer(label: "details") {
        title
        description
      }
    }
  }
`;

const responseStream = await client.requestStream(productQuery, {
  variables: {handle: 'sample-product'},
});

for await (const response of responseStream) {
  const {data, errors, extensions, hasNext} = response;
  
  console.log('Received data:', data);
  
  if (!hasNext) {
    console.log('Stream complete');
    break;
  }
}
@defer Required: The requestStream() method only works with operations containing the @defer directive. For regular operations, use request() instead.

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

Request Options

All request methods accept an options object:
variables
Record<string, any>
GraphQL operation variables
url
string
Override the default API URL for this request
headers
Record<string, string | string[]>
Additional or override headers for this request
retries
number
Override retry count for this request (0-3)
keepalive
boolean
default:"false"
Keep connection alive when page unloads before request completes
signal
AbortSignal
AbortSignal for canceling the request

Advanced Usage

Custom Headers Per Request

const {data} = await client.request(query, {
  variables: {handle: 'product'},
  headers: {
    'X-Custom-Header': 'value',
  },
});

Override URL Per Request

const {data} = await client.request(query, {
  variables: {handle: 'product'},
  url: 'https://shop.myshopify.com/api/unstable/graphql.json',
});

Request Retries

const {data} = await client.request(query, {
  retries: 2, // Retry up to 2 times on 429/503 errors
});

Keepalive Requests

Prevent request cancellation when page unloads:
const {data} = await client.request(analyticsQuery, {
  keepalive: true,
});

Request Cancellation

const controller = new AbortController();

const promise = client.request(query, {
  signal: controller.signal,
});

// Cancel the request
controller.abort();

Server-Side Usage

For Node.js environments, provide a custom fetch implementation:
import {createGraphQLClient} from '@shopify/graphql-client';
import {fetch as nodeFetch} from 'node-fetch';

const client = createGraphQLClient({
  url: 'https://shop.myshopify.com/api/2025-01/graphql.json',
  headers: {
    'Content-Type': 'application/json',
    'X-Shopify-Storefront-Access-Token': 'token',
  },
  customFetchApi: nodeFetch,
});

Client Properties

config
ClientConfig
Client configuration object
{
  url: string,
  headers: Record<string, string | string[]>,
  retries: number
}
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

Error Handling

Network Errors

const {data, errors} = await client.request(query);

if (errors) {
  if (errors.networkStatusCode === 401) {
    console.error('Unauthorized');
  } else if (errors.networkStatusCode === 429) {
    console.error('Rate limited');
  } else if (errors.networkStatusCode === 503) {
    console.error('Service unavailable');
  }
}

GraphQL Errors

const {data, errors} = await client.request(query);

if (errors?.graphQLErrors) {
  errors.graphQLErrors.forEach((error) => {
    console.error('GraphQL Error:', error.message);
    console.error('Path:', error.path);
    console.error('Extensions:', error.extensions);
  });
}

Stream Errors

const stream = await client.requestStream(query);

for await (const response of stream) {
  if (response.errors) {
    console.error('Stream error:', response.errors.message);
    break;
  }
  
  console.log('Data:', response.data);
}

Logging

Monitor requests and retries:
const client = createGraphQLClient({
  url: 'https://api.example.com/graphql',
  headers: {'Content-Type': 'application/json'},
  logger: (log) => {
    if (log.type === 'HTTP-Response') {
      console.log('Response:', log.content.response.status);
    } else if (log.type === 'HTTP-Retry') {
      const {retryAttempt, maxRetries} = log.content;
      console.log(`Retry ${retryAttempt}/${maxRetries}`);
    }
  },
});

Log Types

HTTP-Response
HTTPResponseLog
Sent when an HTTP response is received
{
  type: 'HTTP-Response',
  content: {
    requestParams: [url, init],
    response: Response
  }
}
HTTP-Retry
HTTPRetryLog
Sent when the client retries a request
{
  type: 'HTTP-Retry',
  content: {
    requestParams: [url, init],
    lastResponse: Response,
    retryAttempt: number,
    maxRetries: number
  }
}
HTTP-Response-GraphQL-Deprecation-Notice
HTTPResponseGraphQLDeprecationNotice
Sent when deprecated fields are used
{
  type: 'HTTP-Response-GraphQL-Deprecation-Notice',
  content: {
    requestParams: [url, init],
    deprecationNotice: string
  }
}

Utility Functions

The GraphQL Client also exports utility functions:

API Version Utilities

import {
  getCurrentSupportedApiVersions,
  validateApiVersion,
} from '@shopify/graphql-client';

// Get currently supported API versions
const versions = getCurrentSupportedApiVersions();
console.log(versions); // ['2024-10', '2025-01', '2025-04', 'unstable']

// Validate an API version
validateApiVersion({
  client: 'My Client',
  currentSupportedApiVersions: versions,
  apiVersion: '2025-01',
  logger: (log) => console.warn(log),
});

Domain Validation

import {validateDomainAndGetStoreUrl} from '@shopify/graphql-client';

const url = validateDomainAndGetStoreUrl({
  client: 'My Client',
  storeDomain: 'shop.myshopify.com',
});

console.log(url); // 'https://shop.myshopify.com'

Type Safety

Use TypeScript generics for type-safe responses:
interface Product {
  id: string;
  title: string;
  handle: string;
}

interface ProductQueryResponse {
  product: Product;
}

const {data} = await client.request<ProductQueryResponse>(
  productQuery,
  {variables: {handle: 'shoe'}}
);

// data.product is typed as Product
console.log(data?.product.title);

Best Practices

Use Specific Clients: For Admin or Storefront APIs, prefer the dedicated clients as they handle authentication and URL formatting automatically.
Retry Strategy: Retries only occur for abandoned requests or 429/503 status codes. Configure appropriate retry counts based on your use case.
Browser vs Server: When using in Node.js, always provide a custom fetch API like node-fetch.

Examples

Admin API

const client = createGraphQLClient({
  url: 'https://shop.myshopify.com/admin/api/2025-01/graphql.json',
  headers: {
    'Content-Type': 'application/json',
    'X-Shopify-Access-Token': 'admin-token',
  },
});

Storefront API

const client = createGraphQLClient({
  url: 'https://shop.myshopify.com/api/2025-01/graphql.json',
  headers: {
    'Content-Type': 'application/json',
    'X-Shopify-Storefront-Access-Token': 'storefront-token',
  },
});

Custom GraphQL Endpoint

const client = createGraphQLClient({
  url: 'https://my-custom-api.com/graphql',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer custom-token',
  },
});

Next Steps

Admin API Client

Use the Admin API-specific client

Storefront API Client

Use the Storefront API-specific client

Build docs developers (and LLMs) love