This guide covers making GraphQL queries and mutations using the Shopify Admin API client.
Creating a GraphQL Client
Create a GraphQL client using a session:
const client = new shopify.clients.Graphql({session});
You can optionally override the API version:
const client = new shopify.clients.Graphql({
session,
apiVersion: '2024-01',
});
Source: lib/clients/admin/graphql/client.ts:38-67
Making Queries
Use the request() method to execute GraphQL operations:
const response = await client.request(
`query getProduct($id: ID!) {
product(id: $id) {
id
title
description
variants(first: 10) {
edges {
node {
id
price
sku
}
}
}
}
}`,
{
variables: {
id: 'gid://shopify/Product/1234567890',
},
}
);
console.log(response.data.product);
Source: lib/clients/admin/graphql/client.ts:110-144
Response Structure
interface GraphQLClientResponse<T> {
data: T; // Query response data
headers: Headers; // Response headers
errors?: {
response: Response; // Fetch Response object
graphQLErrors?: any[]; // GraphQL errors
networkStatusCode?: number;
};
}
Mutations
Mutations work the same way as queries:
const response = await client.request(
`mutation updateProduct($input: ProductInput!) {
productUpdate(input: $input) {
product {
id
title
}
userErrors {
field
message
}
}
}`,
{
variables: {
input: {
id: 'gid://shopify/Product/1234567890',
title: 'Updated Product Title',
},
},
}
);
if (response.data.productUpdate.userErrors.length > 0) {
console.error('Errors:', response.data.productUpdate.userErrors);
} else {
console.log('Product updated:', response.data.productUpdate.product);
}
Request Options
The request() method accepts additional options:
const response = await client.request(
operation,
{
variables: {...},
headers: {
'X-Custom-Header': 'value',
},
retries: 2,
}
);
Options:
variables - GraphQL variables object
headers - Additional headers to send
retries - Number of retry attempts on failure
apiVersion - Override the API version for this request
Source: lib/clients/admin/graphql/client.ts:110-128
Error Handling
GraphQL requests can fail in multiple ways:
import {GraphqlQueryError} from '@shopify/shopify-api';
try {
const response = await client.request(operation);
// Check for GraphQL errors in response
if (response.errors) {
console.error('GraphQL Errors:', response.errors.graphQLErrors);
}
} catch (error) {
if (error instanceof GraphqlQueryError) {
console.error('Query failed:', error.message);
console.error('Response:', error.response);
console.error('Body:', error.body);
}
}
Source: lib/error.ts:74-92
Error Types
The client throws errors when:
- Network request fails
- Response is not OK (status >= 400)
- GraphQL returns errors
Source: lib/clients/admin/graphql/client.ts:130-134
Use cursor-based pagination for lists:
let hasNextPage = true;
let cursor = null;
const allProducts = [];
while (hasNextPage) {
const response = await client.request(
`query getProducts($cursor: String) {
products(first: 50, after: $cursor) {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
}
}
}`,
{
variables: {cursor},
}
);
const {edges, pageInfo} = response.data.products;
allProducts.push(...edges.map(edge => edge.node));
hasNextPage = pageInfo.hasNextPage;
cursor = edges[edges.length - 1]?.cursor;
}
console.log(`Fetched ${allProducts.length} products`);
Fragments
Use GraphQL fragments to reuse field selections:
const PRODUCT_FRAGMENT = `
fragment ProductFields on Product {
id
title
description
status
variants(first: 10) {
edges {
node {
id
price
sku
}
}
}
}
`;
const response = await client.request(
`${PRODUCT_FRAGMENT}
query getProduct($id: ID!) {
product(id: $id) {
...ProductFields
}
}`,
{
variables: {id: 'gid://shopify/Product/123'},
}
);
Bulk Operations
For large datasets, use bulk operations:
// Start bulk operation
const startResponse = await client.request(
`mutation {
bulkOperationRunQuery(
query: """
{
products {
edges {
node {
id
title
}
}
}
}
"""
) {
bulkOperation {
id
status
}
userErrors {
field
message
}
}
}`
);
const operationId = startResponse.data.bulkOperationRunQuery.bulkOperation.id;
// Poll for completion
let status = 'RUNNING';
while (status === 'RUNNING') {
await new Promise(resolve => setTimeout(resolve, 1000));
const statusResponse = await client.request(
`query getBulkOperation($id: ID!) {
node(id: $id) {
... on BulkOperation {
id
status
objectCount
url
}
}
}`,
{variables: {id: operationId}}
);
status = statusResponse.data.node.status;
}
// Download results from url
const resultsUrl = statusResponse.data.node.url;
Rate Limiting
The client automatically handles rate limits with retries:
const response = await client.request(
operation,
{
retries: 3, // Retry up to 3 times on throttling
}
);
Check rate limit status in headers:
const response = await client.request(operation);
console.log('Cost:', response.headers['x-graphql-cost-include-fields']);
console.log('Throttle:', response.headers['x-shopify-api-request-failure-reauthorize']);
TypeScript Support
Use type parameters for typed responses:
interface ProductData {
product: {
id: string;
title: string;
variants: {
edges: Array<{
node: {
id: string;
price: string;
};
}>;
};
};
}
const response = await client.request<ProductData>(
`query getProduct($id: ID!) {
product(id: $id) {
id
title
variants(first: 10) {
edges {
node {
id
price
}
}
}
}
}`,
{variables: {id: 'gid://shopify/Product/123'}}
);
// response.data is typed as ProductData
console.log(response.data.product.title);
Complete Example
import {shopifyApi} from '@shopify/shopify-api';
const shopify = shopifyApi({...});
app.get('/api/products/:id', async (req, res) => {
const session = await loadSession(req);
const client = new shopify.clients.Graphql({session});
try {
const response = await client.request(
`query getProduct($id: ID!) {
product(id: $id) {
id
title
description
variants(first: 10) {
edges {
node {
id
title
price
}
}
}
}
}`,
{
variables: {
id: `gid://shopify/Product/${req.params.id}`,
},
}
);
res.json(response.data.product);
} catch (error) {
console.error('Query failed:', error);
res.status(500).json({error: 'Failed to fetch product'});
}
});
Best Practices
- Use variables instead of string interpolation
- Implement pagination for large result sets
- Handle both GraphQL and network errors
- Check
userErrors in mutation responses
- Use fragments to avoid repeating field selections
- Set appropriate retry counts for production
- Monitor GraphQL cost in response headers
The GraphQL client requires a valid access token. Missing access tokens will throw a MissingRequiredArgument error.Source: lib/clients/admin/graphql/client.ts:41-45