shopify-api-patterns
SKILL.md
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
1. 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
}
}
}
2. Metafield Query Pattern
query getProductMetafields($productId: ID!) {
product(id: $productId) {
id
title
metafields(first: 20, namespace: "custom") {
edges {
node {
id
namespace
key
value
type
}
}
}
}
}
3. 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",
},
],
},
});
4. 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",
},
},
});
5. 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_CREATEPRODUCTS_UPDATEPRODUCTS_DELETEORDERS_CREATECUSTOMERS_CREATE
6. 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;
}
7. 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;
}
8. 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
errorsanduserErrorsin 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 textmulti_line_text_field- Long textnumber_integer- Whole numbersnumber_decimal- Decimal numbersjson- Structured datacolor- Color valuesurl- URLsboolean- True/falsedate- Date valueslist.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.
Weekly Installs
5
Repository
sarojpunde/shop…-pluginsGitHub Stars
1
First Seen
Feb 3, 2026
Security Audits
Installed on
opencode5
codex5
gemini-cli4
claude-code4
github-copilot4
kimi-cli4