Shopify API Patterns Skill
Purpose
Provides reusable patterns for common Shopify Admin GraphQL API operations including product queries, metafield management, webhook handling, and bulk operations.
When This Skill Activates
-
Working with Shopify Admin GraphQL API
-
Querying products, variants, customers, or orders
-
Managing metafields
-
Implementing webhooks
-
Handling bulk operations
-
Implementing rate limiting
Core Patterns
- Product Query with Pagination
query getProducts($first: Int!, $after: String) { products(first: $first, after: $after) { edges { node { id title vendor handle productType tags variants(first: 10) { edges { node { id title price sku } } } } cursor } pageInfo { hasNextPage endCursor } } }
- Metafield Query Pattern
query getProductMetafields($productId: ID!) { product(id: $productId) { id title metafields(first: 20, namespace: "custom") { edges { node { id namespace key value type } } } } }
- Metafield Update Mutation
mutation updateMetafields($metafields: [MetafieldsSetInput!]!) { metafieldsSet(metafields: $metafields) { metafields { id namespace key value type } userErrors { field message } } }
Usage Example:
const response = await admin.graphql(UPDATE_METAFIELDS, { variables: { metafields: [ { ownerId: "gid://shopify/Product/123", namespace: "custom", key: "color", value: "Red", type: "single_line_text_field", }, ], }, });
- Metafield Definition Creation
mutation createMetafieldDefinition($definition: MetafieldDefinitionInput!) { metafieldDefinitionCreate(definition: $definition) { createdDefinition { id name namespace key type ownerType } userErrors { field message } } }
Usage:
await admin.graphql(CREATE_METAFIELD_DEFINITION, { variables: { definition: { name: "Product Color", namespace: "custom", key: "color", type: "single_line_text_field", ownerType: "PRODUCT", }, }, });
- Webhook Registration
mutation registerWebhook($topic: WebhookSubscriptionTopic!, $webhookSubscription: WebhookSubscriptionInput!) { webhookSubscriptionCreate(topic: $topic, webhookSubscription: $webhookSubscription) { webhookSubscription { id topic endpoint { __typename ... on WebhookHttpEndpoint { callbackUrl } } } userErrors { field message } } }
Common Topics:
-
PRODUCTS_CREATE
-
PRODUCTS_UPDATE
-
PRODUCTS_DELETE
-
ORDERS_CREATE
-
CUSTOMERS_CREATE
- Pagination Helper
async function fetchAllProducts(admin) { let hasNextPage = true; let cursor = null; const allProducts = [];
while (hasNextPage) { const response = await admin.graphql(GET_PRODUCTS, { variables: { first: 250, after: cursor }, });
const data = await response.json();
if (data.errors) {
throw new Error(`GraphQL error: ${data.errors[0].message}`);
}
const products = data.data.products.edges.map(edge => edge.node);
allProducts.push(...products);
hasNextPage = data.data.products.pageInfo.hasNextPage;
cursor = data.data.products.pageInfo.endCursor;
// Rate limiting check
const rateLimitCost = response.headers.get("X-Shopify-Shop-Api-Call-Limit");
if (rateLimitCost) {
const [used, total] = rateLimitCost.split("/").map(Number);
if (used > total * 0.8) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
return allProducts; }
- Bulk Operation Pattern
mutation bulkOperationRunQuery { bulkOperationRunQuery( query: """ { products { edges { node { id title metafields { edges { node { namespace key value } } } } } } } """ ) { bulkOperation { id status } userErrors { field message } } }
Check Status:
query { currentBulkOperation { id status errorCode createdAt completedAt objectCount fileSize url } }
Download and Process Results:
async function processBulkOperationResults(url: string) { const response = await fetch(url); const jsonl = await response.text();
const lines = jsonl.trim().split("\n"); const results = lines.map(line => JSON.parse(line));
return results; }
- Rate Limiting Handler
async function graphqlWithRetry(admin, query, variables, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await admin.graphql(query, { variables });
// Check rate limit
const rateLimitCost = response.headers.get("X-Shopify-Shop-Api-Call-Limit");
if (rateLimitCost) {
const [used, total] = rateLimitCost.split("/").map(Number);
console.log(`API calls: ${used}/${total}`);
if (used > total * 0.9) {
console.warn("Approaching rate limit, slowing down...");
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
const data = await response.json();
if (data.errors) {
throw new Error(`GraphQL error: ${data.errors[0].message}`);
}
return data;
} catch (error) {
if (error.message.includes("Throttled") && i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000; // Exponential backoff
console.log(`Rate limited, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
} }
Best Practices
-
Pagination - Always use cursor-based pagination for large result sets
-
Field Selection - Only request fields you need to reduce response size
-
Rate Limiting - Monitor API call limits and implement backoff
-
Error Handling - Check both errors and userErrors in responses
-
Bulk Operations - Use for processing 1000+ products
-
Metafield Types - Use appropriate types (single_line_text_field, number_integer, json, etc.)
-
Webhook Verification - Always verify HMAC signatures
-
Caching - Cache frequently accessed data like metafield definitions
-
Retry Logic - Implement exponential backoff for transient failures
-
Logging - Log API calls and errors for debugging
Common Metafield Types
-
single_line_text_field
-
Short text
-
multi_line_text_field
-
Long text
-
number_integer
-
Whole numbers
-
number_decimal
-
Decimal numbers
-
json
-
Structured data
-
color
-
Color values
-
url
-
URLs
-
boolean
-
True/false
-
date
-
Date values
-
list.single_line_text_field
-
Array of strings
Quick Reference
Get Product by Handle
query getProductByHandle($handle: String!) { productByHandle(handle: $handle) { id title vendor } }
Get Product Variants
query getProductVariants($productId: ID!) { product(id: $productId) { variants(first: 100) { edges { node { id title price sku inventoryQuantity } } } } }
Update Product
mutation productUpdate($input: ProductInput!) { productUpdate(input: $input) { product { id title } userErrors { field message } } }
Remember: Always check the Shopify Admin API documentation for the latest schema and deprecations.