shopify-sdk-patterns
'Apply production-ready patterns for @shopify/shopify-api including typed
Allowed Tools
Provided by Plugin
shopify-pack
Claude Code skill pack for Shopify (30 skills)
Installation
This skill is included in the shopify-pack plugin:
/plugin install shopify-pack@claude-code-plugins-plus
Click to copy
Instructions
Shopify SDK Patterns
Overview
Production-ready patterns for the @shopify/shopify-api library: singleton clients, typed GraphQL operations, session management, cursor-based pagination, codegen-typed operations, bulk operations, and webhook registry patterns.
Prerequisites
@shopify/shopify-apiv9+ installed- Familiarity with Shopify's GraphQL Admin API
- Understanding of async/await and TypeScript generics
Instructions
Step 1: Typed GraphQL Client Wrapper
Initialize a singleton shopifyApi instance with LATESTAPIVERSION, cache sessions per shop, and expose a typed shopifyQuery helper that wraps client.request().
See Typed GraphQL Client for the complete implementation.
Step 2: Error Handling with Shopify Error Types
Custom ShopifyServiceError class that distinguishes retryable errors (429, 5xx) from permanent ones. Includes handleShopifyError() for error translation and safeShopifyCall() that returns {data, error} tuples instead of throwing.
See Error Handling for the complete implementation.
Step 3: Cursor-Based Pagination
Async generator paginateShopify for Relay-style cursor pagination. Yields batches of nodes, automatically following pageInfo.endCursor until hasNextPage is false. Memory-efficient for large datasets.
See Cursor Pagination for the complete implementation.
Step 4: Multi-Tenant Client Factory
ShopifyClientFactory class for apps installed on multiple stores. Creates isolated GraphqlClient instances per merchant with session caching. Includes removeClient() for eviction on app uninstall.
See Multi-Tenant Factory for the complete implementation.
Step 5: Codegen-Typed Operations
Use @shopify/api-codegen-preset to generate TypeScript types from your GraphQL operations. This eliminates manual type definitions and catches schema changes at build time.
// codegen.ts — project root config
import { shopifyApiProject, ApiType } from "@shopify/api-codegen-preset";
export default {
schema: "https://shopify.dev/admin-graphql-direct-proxy",
documents: ["src/**/*.{ts,tsx}"],
projects: {
default: shopifyApiProject({
apiType: ApiType.Admin,
apiVersion: "2025-04", // Update quarterly
outputDir: "./src/types",
}),
},
};
// src/operations/products.ts — typed query with codegen output
import type { ProductsQuery } from "../types/admin.generated";
const PRODUCTS_QUERY = `#graphql
query Products($first: Int!, $after: String) {
products(first: $first, after: $after) {
edges {
node { id title status totalInventory }
}
pageInfo { hasNextPage endCursor }
}
}
` as const;
// Return type is fully inferred from codegen
export async function getProducts(shop: string): Promise<ProductsQuery> {
return shopifyQuery<ProductsQuery>(shop, PRODUCTS_QUERY, { first: 50 });
}
Run npx graphql-codegen after changing any GraphQL operation or upgrading API versions.
Step 6: Bulk Operation Helpers
For datasets too large for pagination (100k+ records), use Shopify's Bulk Operations API. It runs a query server-side and produces a JSONL file you download when ready.
// src/shopify/bulk.ts
const BULK_QUERY = `
mutation bulkOperationRunQuery($query: String!) {
bulkOperationRunQuery(query: $query) {
bulkOperation { id status }
userErrors { field message }
}
}
`;
const POLL_QUERY = `{
currentBulkOperation {
id status errorCode objectCount url
}
}`;
export async function runBulkOperation(
shop: string,
query: string
): Promise<string> {
// Start the bulk operation
const { bulkOperationRunQuery } = await shopifyQuery(shop, BULK_QUERY, { query });
if (bulkOperationRunQuery.userErrors?.length) {
throw new Error(bulkOperationRunQuery.userErrors[0].message);
}
// Poll until complete (typically 1-10 minutes for large datasets)
let result;
do {
await new Promise((r) => setTimeout(r, 5000)); // 5s interval
result = (await shopifyQuery(shop, POLL_QUERY)).currentBulkOperation;
} while (result.status === "RUNNING" || result.status === "CREATED");
if (result.status !== "COMPLETED") {
throw new Error(`Bulk operation failed: ${result.errorCode}`);
}
return result.url; // JSONL download URL
}
// Usage: export all products
const url = await runBulkOperation(shop, `{
products { edges { node { id title status variants { edges { node { sku price } } } } } }
}`);
const response = await fetch(url);
const jsonl = await response.text();
const products = jsonl.trim().split("\n").map(JSON.parse);
Step 7: Webhook Registry Patterns
Programmatically register webhook subscriptions using webhookSubscriptionCreate with typed WebhookSubscriptionInput. Supports both HTTP and EventBridge/PubSub endpoints.
// src/shopify/webhooks.ts
const REGISTER_WEBHOOK = `
mutation webhookSubscriptionCreate(
$topic: WebhookSubscriptionTopic!,
$webhookSubscription: WebhookSubscriptionInput!
) {
webhookSubscriptionCreate(topic: $topic, webhookSubscription: $webhookSubscription) {
webhookSubscription { id topic }
userErrors { field message }
}
}
`;
interface WebhookConfig {
topic: string;
callbackUrl: string;
format?: "JSON" | "XML";
}
export async function registerWebhooks(
shop: string,
webhooks: WebhookConfig[]
): Promise<{ registered: string[]; errors: string[] }> {
const registered: string[] = [];
const errors: string[] = [];
for (const wh of webhooks) {
const result = await shopifyQuery(shop, REGISTER_WEBHOOK, {
topic: wh.topic,
webhookSubscription: {
callbackUrl: wh.callbackUrl,
format: wh.format ?? "JSON",
},
});
const userErrors = result.webhookSubscriptionCreate.userErrors;
if (userErrors?.length) {
errors.push(`${wh.topic}: ${userErrors[0].message}`);
} else {
registered.push(wh.topic);
}
}
return { registered, errors };
}
Output
- Type-safe GraphQL client with singleton session management
- Structured error handling that distinguishes retryable from permanent errors
- Cursor-based pagination generator for large datasets
- Multi-tenant client factory for apps serving multiple stores
- Codegen-typed operations eliminating manual type definitions
- Bulk operation helpers for large dataset exports
- Webhook registry patterns for programmatic subscription management
Error Handling
| Pattern | Use Case | Benefit |
|---|---|---|
safeShopifyCall |
All API calls | Returns {data, error} instead of throwing |
handleShopifyError |
Error translation | Maps HTTP/GraphQL errors to typed errors |
| Cursor pagination | Large datasets | Memory-efficient streaming with backpressure |
| Bulk operations | 100k+ records | Server-side execution, no client memory pressure |
| Client factory | Multi-tenant apps | Isolated sessions per merchant |
Examples
Setting Up a Type-Safe GraphQL Client
Initialize a singleton Shopify client with session caching and a typed shopifyQuery helper for all API calls.
See Typed GraphQL Client for the complete implementation.
Handling Retryable vs Permanent Errors
Distinguish 429/5xx retryable errors from permanent validation failures using a structured error class and safe call wrapper.
See Error Handling for the complete error handling implementation.
Building a Multi-Tenant App
Create isolated GraphQL clients per merchant with session caching and eviction on app uninstall using a client factory.
See Multi-Tenant Factory for the complete implementation.