← Shopify App BuilderCONTENT HISTORY

Update to Shopify App Builder

Snapshot Sep 30, 2026 · 23:13 UTC · version 1.4.1

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full 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