← Shopify App BuilderCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Shopify App Builder
Snapshot Sep 30, 2026 · 23:13 UTC · version 1.4.1
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Build Shopify Admin GraphQL queries and mutations for products, orders, customers, inventory, and more. Covers cost-aware rate limiting, cursor pagination, bulk operations, global resource identifiers, and version-safe API usage.",
"included_files": [],
"name": "admin-graphql",
"skill_md_contents": "---\nname: admin-graphql\ndescription: \"Build Shopify Admin GraphQL queries and mutations for products, orders, customers, inventory, and more. Covers cost-aware rate limiting, cursor pagination, bulk operations, global resource identifiers, and version-safe API usage.\"\n---\n\n## When to Use This Skill\n\nUse **Admin GraphQL API** when you need to:\n- Manage products, variants, collections, and pricing (preferred over REST for any business logic)\n- Query or modify orders, fulfillments, and refunds\n- Create or update customers and customer accounts\n- Adjust inventory across multiple locations\n- Create metafields and metaobjects for custom data\n- Set up webhooks for event subscriptions\n- Perform bulk operations on large datasets (1000+ records)\n- Access the latest Shopify features (GraphQL-only endpoints)\n\n**GraphQL vs REST comparison:**\n| Feature | GraphQL Admin | REST (Deprecated) |\n|---------|--------------|-------------------|\n| Rate Limiting | Cost-based; standard restore rate is 100 points/sec | Request bucket; standard restore rate is 2 requests/sec |\n| Pagination | Cursor-based (Relay pattern) | Offset/limit (deprecated) |\n| Field Selection | Precise (fetch only needed fields) | Fixed response shape (wasteful) |\n| Batch Operations | Native bulk operations (JSONL) | Multiple sequential requests |\n| Latest Features | Current platform features | Some newer features are GraphQL-only |\n| Status | Required for new public apps | Legacy since October 1, 2024 |\n\n---\n\n## API Endpoint & Authentication\n\n**Endpoint:** `https://{shop}.myshopify.com/admin/api/2026-07/graphql.json`\n\n**Authentication:**\n```javascript\nconst headers = {\n 'Content-Type': 'application/json',\n 'X-Shopify-Access-Token': process.env.SHOPIFY_ACCESS_TOKEN,\n};\n```\n\n---\n\n## Cost-Based Rate Limiting\n\nShopify uses calculated query costs. The standard GraphQL Admin API restore rate is 100 points per second; Advanced, Plus, and enterprise plans have higher rates. A single query can't exceed 1,000 requested points. Read `extensions.cost.throttleStatus` instead of hard-coding one bucket size.\n\n**Handling throttling:**\n```javascript\nasync function executeWithRetry(query, variables, maxRetries = 3) {\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers,\n body: JSON.stringify({ query, variables }),\n });\n\n const data = await response.json();\n\n if (data.errors?.some(e => e.extensions?.code === 'THROTTLED')) {\n const waitTime = Math.pow(2, attempt - 1) * 1000;\n console.log(`Throttled. Waiting ${waitTime}ms...`);\n await new Promise(resolve => setTimeout(resolve, waitTime));\n continue;\n }\n\n return data;\n }\n throw new Error('Max retries exceeded');\n}\n```\n\n---\n\n## Cursor-Based Pagination\n\n```graphql\nquery GetProducts($first: Int, $after: String) {\n products(first: $first, after: $after) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n id\n title\n }\n }\n }\n}\n```\n\n**JavaScript iteration:**\n```javascript\nconst allProducts = [];\nlet hasNextPage = true;\nlet endCursor = null;\n\nwhile (hasNextPage) {\n const data = await executeQuery(GET_PRODUCTS, {\n first: 50,\n after: endCursor,\n });\n\n const { edges, pageInfo } = data.products;\n allProducts.push(...edges.map(e => e.node));\n\n hasNextPage = pageInfo.hasNextPage;\n endCursor = pageInfo.endCursor;\n}\n```\n\n---\n\n## 30 Essential Operations\n\n### Product Management\n\n**1. Create Product**\n```graphql\nmutation CreateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n handle\n title\n status\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n**2. Update Product**\n```graphql\nmutation UpdateProduct($input: ProductInput!) {\n productUpdate(input: $input) {\n product {\n id\n title\n updatedAt\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n**3. Create Variants (Bulk)**\n```graphql\nmutation BulkCreateVariants($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {\n productVariantsBulkCreate(productId: $productId, variants: $variants) {\n productVariants {\n id\n title\n sku\n price\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n**4. Update Variants (Bulk)**\n```graphql\nmutation BulkUpdateVariants($variants: [ProductVariantsBulkInput!]!) {\n productVariantsBulkUpdate(variants: $variants) {\n productVariants {\n id\n sku\n price\n }\n userErrors {\n message\n }\n }\n}\n```\n\n### Order Management\n\n**5. Get Orders (Paginated)**\n```graphql\nquery GetOrders($first: Int!, $after: String) {\n orders(first: $first, after: $after) {\n edges {\n node {\n id\n name\n createdAt\n customer {\n id\n email\n }\n lineItems(first: 10) {\n edges {\n node {\n id\n title\n quantity\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n}\n```\n\n**6. Update Order (Add Tags)**\n```graphql\nmutation UpdateOrderTags($input: OrderInput!) {\n orderUpdate(input: $input) {\n order {\n id\n tags\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n**7. Begin Order Edit**\n```graphql\nmutation BeginOrderEdit($orderId: ID!) {\n orderEditBegin(orderId: $orderId) {\n calculatedOrder {\n id\n lineItems(first: 10) {\n edges {\n node {\n id\n title\n quantity\n }\n }\n }\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n**8. Commit Order Edit**\n```graphql\nmutation CommitOrderEdit($id: ID!) {\n orderEditCommit(id: $id) {\n order {\n id\n name\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n### Fulfillment\n\n**9. Create Fulfillment**\n```graphql\nmutation CreateFulfillment($input: FulfillmentInput!) {\n fulfillmentCreate(input: $input) {\n fulfillment {\n id\n status\n trackingInfo {\n number\n company\n url\n }\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n### Inventory\n\n**10. Adjust Inventory (Multiple Locations)**\n```graphql\nmutation AdjustInventory($input: InventoryAdjustQuantitiesInput!) {\n inventoryAdjustQuantities(input: $input) {\n inventoryLevels {\n id\n quantity\n location {\n id\n name\n }\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n### Customers\n\n**11. Create Customer**\n```graphql\nmutation CreateCustomer($input: CustomerInput!) {\n customerCreate(input: $input) {\n customer {\n id\n email\n firstName\n lastName\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n**12. Update Customer**\n```graphql\nmutation UpdateCustomer($input: CustomerInput!) {\n customerUpdate(input: $input) {\n customer {\n id\n email\n updatedAt\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n**13. Get Customer with Orders**\n```graphql\nquery GetCustomerOrders($customerId: ID!, $first: Int!) {\n customer(id: $customerId) {\n id\n email\n firstName\n orders(first: $first) {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n}\n```\n\n### Metafields\n\n**14. Set Metafields (Product)**\n```graphql\nmutation SetMetafields($input: MetafieldsSetInput!) {\n metafieldsSet(input: $input) {\n metafields {\n id\n namespace\n key\n value\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n**15. Create Metaobject**\n```graphql\nmutation CreateMetaobject($input: MetaobjectInput!) {\n metaobjectCreate(input: $input) {\n metaobject {\n id\n type\n fields {\n key\n value\n }\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n### Discounts\n\n**16. Create Discount Code**\n```graphql\nmutation CreateDiscount($input: DiscountCodeBasicInput!) {\n discountCodeBasicCreate(input: $input) {\n discountCodeBasic {\n id\n codes(first: 1) {\n edges {\n node {\n code\n }\n }\n }\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n### Webhooks\n\n**17. Create Webhook Subscription**\n```graphql\nmutation CreateWebhook($input: WebhookSubscriptionInput!) {\n webhookSubscriptionCreate(input: $input) {\n webhookSubscription {\n id\n topic\n endpoint {\n __typename\n ... on WebhookHttpEndpoint {\n callbackUrl\n }\n }\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n### Draft Orders\n\n**18. Create Draft Order**\n```graphql\nmutation CreateDraftOrder($input: DraftOrderInput!) {\n draftOrderCreate(input: $input) {\n draftOrder {\n id\n invoiceUrl\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n**19. Complete Draft Order**\n```graphql\nmutation CompleteDraftOrder($id: ID!) {\n draftOrderComplete(id: $id) {\n draftOrder {\n id\n order {\n id\n name\n }\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n### Shop\n\n**20. Get Shop Details**\n```graphql\nquery GetShopDetails {\n shop {\n id\n name\n email\n myshopifyDomain\n currency\n }\n}\n```\n\n### Collections\n\n**21. Create Collection**\n```graphql\nmutation CreateCollection($input: CollectionInput!) {\n collectionCreate(input: $input) {\n collection {\n id\n handle\n title\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n**22. Add Products to Collection**\n```graphql\nmutation AddProductsToCollection($id: ID!, $productIds: [ID!]!) {\n collectionAddProducts(id: $id, productIds: $productIds) {\n collection {\n id\n products(first: 10) {\n totalCount\n }\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n### Bulk Operations\n\n**23. Run Bulk Query**\n```graphql\nmutation BulkQueryRun($query: String!) {\n bulkOperationRunQuery(query: $query) {\n bulkOperation {\n id\n status\n createdAt\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n**24. Get Bulk Operation Status**\n```graphql\nquery GetBulkOperation($id: ID!) {\n node(id: $id) {\n ... on BulkOperation {\n id\n status\n createdAt\n completedAt\n objectCount\n fileSize\n url\n }\n }\n}\n```\n\n**25. Run Bulk Mutation**\n```graphql\nmutation BulkMutationRun($input: String!) {\n bulkOperationRunMutation(input: $input) {\n bulkOperation {\n id\n status\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n### Search\n\n**26. Search Products**\n```graphql\nquery SearchProducts($query: String!, $first: Int) {\n products(first: $first, query: $query) {\n edges {\n node {\n id\n title\n handle\n }\n }\n }\n}\n```\n\n### Locations\n\n**27. Get All Locations**\n```graphql\nquery GetLocations {\n locations(first: 250) {\n edges {\n node {\n id\n name\n isActive\n }\n }\n }\n}\n```\n\n### Refunds\n\n**28. Create Refund**\n```graphql\nmutation CreateRefund($input: RefundInput!) {\n refundCreate(input: $input) {\n refund {\n id\n status\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n### Returns\n\n**29. Create Return**\n```graphql\nmutation CreateReturn($input: ReturnInput!) {\n returnCreate(input: $input) {\n return {\n id\n status\n requestedAt\n }\n userErrors {\n field\n message\n }\n }\n}\n```\n\n### App Info\n\n**30. Get App Installation Data**\n```graphql\nquery GetAppInstallation {\n appInstallation {\n launchUrl\n accessScopes {\n handle\n }\n }\n}\n```\n\n---\n\n## Bulk Operations Workflow\n\nFor 100+ record operations, use bulk mutations to avoid rate limit delays:\n\n```javascript\nasync function processBulkResults(fileUrl) {\n const response = await fetch(fileUrl);\n const text = await response.text();\n const lines = text.trim().split('\\n');\n\n const results = lines.map(line => JSON.parse(line));\n const errors = results.filter(r => r.__typename === 'Error');\n\n if (errors.length > 0) {\n console.error('Bulk operation errors:', errors);\n }\n\n return results.filter(r => r.__typename !== 'Error');\n}\n```\n\n---\n\n## User Errors Handling\n\n```javascript\nfunction handleMutationResponse(response) {\n if (response.errors) {\n throw new Error(`GraphQL Error: ${response.errors[0].message}`);\n }\n\n const result = response.data?.productCreate;\n\n if (result.userErrors.length > 0) {\n const fieldErrors = result.userErrors.map(err =>\n `${err.field.join('.')}: ${err.message}`\n ).join('; ');\n throw new Error(`Validation failed: ${fieldErrors}`);\n }\n\n return result.product;\n}\n```\n\n---\n\n## Global Resource Identifiers (GIDs)\n\nAll Shopify resources use format: `gid://shopify/{ResourceType}/{NumericID}`\n\n**Common types:**\n- `gid://shopify/Product/123456`\n- `gid://shopify/Order/345678`\n- `gid://shopify/Customer/901234`\n- `gid://shopify/Location/567890`\n\n**Parsing GIDs:**\n```javascript\nfunction parseGid(gid) {\n const match = gid.match(/gid:\\/\\/shopify\\/(\\w+)\\/(.+)/);\n return {\n type: match[1],\n id: match[2],\n };\n}\n```\n\n---\n\n## Scope-to-Operation Mapping\n\n| Scope | Operations |\n|-------|-----------|\n| `write_products` | Create, update products; manage variants |\n| `read_products` | Query products, variants, collections |\n| `write_orders` | Update orders, create fulfillments |\n| `read_orders` | Query orders, line items |\n| `write_customers` | Create, update customers |\n| `read_customers` | Query customer data |\n| `write_inventory` | Adjust inventory quantities |\n| `read_inventory` | Query inventory levels |\n| `write_webhooks` | Create webhook subscriptions |\n\n---\n\n## MoneyV2 Fields Best Practice\n\nAlways request money values with currency:\n\n```graphql\nquery {\n products(first: 1) {\n edges {\n node {\n priceRange {\n minVariantPrice {\n amount\n currencyCode\n }\n }\n variants(first: 1) {\n edges {\n node {\n price\n compareAtPrice\n }\n }\n }\n }\n }\n }\n}\n```\n\n---\n\n## Common Gotchas (15 Critical Issues)\n\n| Gotcha | Solution |\n|--------|----------|\n| **userErrors but no message** | Check field array—sometimes indicates the issue |\n| **Product not appearing in storefront** | Ensure `status: ACTIVE` is set |\n| **Metafield value undefined** | Metafields must match namespace/key exactly |\n| **Pagination returns empty with hasNextPage: true** | Use `after: endCursor`, not offset |\n| **GID format errors** | Always use full format: `gid://shopify/Type/ID` |\n| **Rate limited immediately** | Check query cost; use `first: 50` initially |\n| **Variant options not syncing** | Product options must be created before variants |\n| **Webhook never delivers** | Verify endpoint returns 200-299 status |\n| **Customer metafields not visible** | Check `visible_to_storefront` flag |\n| **Bulk operation returns partial results** | JSONL requires proper line breaks |\n| **Order edit fails silently** | Must run `orderEditBegin` first |\n| **Price not updating** | Use `variants` input as collection |\n| **Inventory shows negative** | Shopify allows negatives; check location config |\n| **Collection products order wrong** | Use `collectionReorderProducts` if order matters |\n| **Webhook signature mismatch** | Use raw request body bytes for HMAC, not JSON |\n\n---\n\n## Decision Tree: Choosing the Right Operation\n\n**Product Management:** Single product? Use create/update. Many variants? Use bulk variants.\n**Order Processing:** Modify after creation? Use orderEditBegin/Commit. Add tags? Use orderUpdate.\n**Inventory:** Single location? Use inventoryAdjustQuantities. Many locations? Use bulk mutation.\n**Customers:** New customer? Use customerCreate. Attach data? Use metafieldsSet.\n**Custom Data:** Attach to existing resource? Use metafieldsSet. Create new structure? Use metaobjectCreate.\n\n---\n\n## API Version & Support Lifecycle\n\n- **Current when this release was audited:** 2026-07\n- **Rule:** Confirm the latest stable version and its support dates before deployment\n- **Release:** Quarterly (Jan, Apr, Jul, Oct)\n- **Support:** 12 months per version\n\n---\n\n## Reference URLs\n\n- [Shopify Admin GraphQL API Docs](https://shopify.dev/docs/api/admin-graphql/latest)\n- [GraphQL Mutations Reference](https://shopify.dev/docs/api/admin-graphql/latest/mutations)\n- [Rate Limiting Guide](https://shopify.dev/docs/api/usage/limits)\n- [Global IDs Explained](https://shopify.dev/docs/api/usage/gids)\n- [OAuth Scopes Reference](https://shopify.dev/docs/api/usage/access-scopes)\n- [API Version Timeline](https://shopify.dev/api/admin-graphql#api-versions)\n- [Bulk Operations Guide](https://shopify.dev/docs/api/usage/bulk-operations/queries)\n"
}SHA-256 of public snapshot: dfd1b06dad27a378e5fa336fa0758110b8e8cacf4bfb9db16602720012a83863