Claude-code-plugins shopify-sdk-patterns
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/shopify-pack/skills/shopify-sdk-patterns" ~/.claude/skills/jeremylongshore-claude-code-plugins-shopify-sdk-patterns && rm -rf "$T"
plugins/saas-packs/shopify-pack/skills/shopify-sdk-patterns/SKILL.mdShopify 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
v9+ installed@shopify/shopify-api- 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 LATEST_API_VERSION, cache sessions per shop, and expose a typed shopifyQuery<T>() 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<T>() 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 |
|---|---|---|
| All API calls | Returns instead of throwing |
| 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<T>() 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.