Developer reference

Shopify Admin API for Product Options

Programmatically managing personalization options — creating option definitions, updating variants, setting metafields, bulk operations at scale — all happens through the Admin API. This is the complete 2026 GraphQL reference for personalization-app developers and Shopify store engineers who need to automate option management. REST is deprecated for new apps; everything below uses GraphQL 2025-07+.

Last updated: August 23, 2026~12 min readBy the Print It My Way team

REST is deprecated — use GraphQL

Shopify announced REST Product endpoints deprecated for new apps on February 1, 2025. All new option-management integrations must use the Admin GraphQL API. Existing REST integrations continue to work but won't receive new features (including the 2,048-variant cap, which requires GraphQL to fully utilize). If you're building or maintaining a personalization app in 2026, use GraphQL exclusively.

Authentication + endpoint

Endpoint: https://{shop}.myshopify.com/admin/api/2025-07/graphql.json
Headers:
  X-Shopify-Access-Token: {your app's access token}
  Content-Type: application/json

Custom apps: generate the token in the admin (Settings → Apps → Develop apps → API credentials). Public apps: tokens come from the OAuth flow. Required scopes for personalization: read_products, write_products, read_orders, write_orders, read_inventory, write_inventory, read_metaobjects, write_metaobjects.

Query product options + variants

query ProductOptions($id: ID!) {
  product(id: $id) {
    id
    title
    options {
      id
      name
      values
      position
    }
    variants(first: 100) {
      nodes {
        id
        title
        sku
        price
        selectedOptions { name value }
        metafields(first: 20, namespace: "custom") {
          nodes { key value type }
        }
      }
      pageInfo { hasNextPage endCursor }
    }
  }
}

productSet — the modern all-in-one mutation

Since 2025-01 API, productSet is the recommended atomic mutation for creating or fully replacing a product's options + variants + media in one operation:

mutation ProductSet($input: ProductSetInput!) {
  productSet(input: $input) {
    product {
      id
      title
      variants(first: 250) {
        nodes { id sku selectedOptions { name value } }
      }
    }
    userErrors { field message }
  }
}

Variables:
{
  "input": {
    "title": "Personalized Mug",
    "productOptions": [
      { "name": "Color", "values": [{"name": "White"}, {"name": "Black"}] },
      { "name": "Size", "values": [{"name": "11oz"}, {"name": "15oz"}] }
    ],
    "variants": [
      {"optionValues": [{"optionName":"Color","name":"White"},{"optionName":"Size","name":"11oz"}], "price": "14.99", "sku": "MUG-W-11"},
      {"optionValues": [{"optionName":"Color","name":"White"},{"optionName":"Size","name":"15oz"}], "price": "17.99", "sku": "MUG-W-15"},
      {"optionValues": [{"optionName":"Color","name":"Black"},{"optionName":"Size","name":"11oz"}], "price": "14.99", "sku": "MUG-B-11"},
      {"optionValues": [{"optionName":"Color","name":"Black"},{"optionName":"Size","name":"15oz"}], "price": "17.99", "sku": "MUG-B-15"}
    ]
  }
}

The 2,048 variant cap

The variant cap raised from 100 to 2,048 on October 15, 2025. If you're operating on products with 500+ variants, you MUST paginate — the variants(first: N) connection now requires first: 250 maximum per page and you iterate via pageInfo.endCursor.

Migration cost: old code that assumed variants(first: 250) returned all variants must now handle pagination. Every read of a 500+ variant product breaks silently — the code gets the first 250, thinks it has all, and misses the rest.

Metafields for personalization data

Custom personalization data (per-variant DPI requirements, POD partner routing, brand-approved logos) lives in metafields. Use metafield definitions to enforce schema:

mutation CreateMetafieldDefinition {
  metafieldDefinitionCreate(definition: {
    namespace: "personalization"
    key: "engraving_max_chars"
    name: "Engraving max chars"
    type: "number_integer"
    ownerType: PRODUCTVARIANT
    validations: [{ name: "min", value: "1" }, { name: "max", value: "50" }]
  }) {
    createdDefinition { id }
    userErrors { field message }
  }
}

mutation SetVariantMetafield {
  metafieldsSet(metafields: [{
    ownerId: "gid://shopify/ProductVariant/40123",
    namespace: "personalization",
    key: "engraving_max_chars",
    value: "20",
    type: "number_integer"
  }]) {
    metafields { id key value }
    userErrors { field message }
  }
}

Bulk operations for large catalogs

Updating 5,000 variants one at a time hits rate limits fast. Use bulkOperationRunMutation for CSV-scale operations:

mutation StartBulkImport {
  bulkOperationRunMutation(
    mutation: "mutation ($input: ProductVariantsBulkInput!) { productVariantsBulkUpdate(...) { ... } }",
    stagedUploadPath: "..."
  ) { bulkOperation { id status } }
}

query CheckBulkStatus {
  currentBulkOperation { id status errorCode createdAt completedAt url }
}

Rate limits

APIRate limitNotes
Admin GraphQL1,000-2,000 cost points/min (bucket)Complex queries cost more points; use @include to skip fields you don't need
Admin REST40 requests/sec (leaky bucket)Deprecated for new products/variants
Bulk Operations1 concurrent bulk operation per app per shopLong-running, ideal for 1000+ item mutations

Webhooks for option changes

Subscribe to products/update and product_variants/update webhooks to react to merchant option changes in real time — regenerating printful mockups, updating fulfillment metafields, or syncing to external ERP. See webhook variant changes for the full pattern.

Common errors

Frequently asked questions

Does Shopify Admin API support product option management?

Yes — via GraphQL. Use productSet to atomically create/update options + variants, metafieldsSet for per-variant personalization metadata, and bulkOperationRunMutation for large-scale updates.

Is REST or GraphQL better for options in 2026?

GraphQL — REST product endpoints were deprecated for new apps on February 1, 2025. All new option-management code should use GraphQL exclusively.

What's the Shopify variant limit and how does it affect API code?

2,048 variants per product as of October 15, 2025 (up from 100). This means your API code MUST paginate variant queries — an unpaginated query on a 500+ variant product silently returns only 250.

How do I store custom personalization data on variants?

Use metafield definitions. Create a definition for each personalization data point (engraving_max_chars, dpi_requirement, pod_partner) with typed validation, then set values per variant via metafieldsSet.

What API scopes does a personalization app need?

read_products, write_products, read_orders, write_orders, read_inventory, write_inventory, read_metaobjects, write_metaobjects. Add write_cart_transforms for Cart Transform functions and write_files for photo-upload personalization.

How do I handle Admin API rate limits at scale?

GraphQL uses a cost-based bucket (1,000-2,000 cost points/min). Complex queries cost more. Use bulk operations for 1000+ item mutations. Add exponential backoff on 429 responses.

What's the difference between productSet and productUpdate?

productSet is atomic and full-state — you pass the entire product spec (options + variants + media) and Shopify replaces it. productUpdate is partial. For personalization apps managing options + variants together, productSet is safer.

How do I test Admin API changes without breaking a live store?

Use a Shopify development store (free via Partners dashboard). Test all mutations against it, then promote to production. Never test destructive mutations (productDelete, productVariantsBulkDelete) on live data.

Related reading

Try it free on Shopify

Print It My Way's permanent Free plan uses Admin GraphQL API + metafields + Cart Transform — the modern option-management stack, no code required.

Install Print It My Way