Create or update a whole product with productSet

Sync a product from an external system in one call: productSet creates or updates the product, its options and variants, matched by handle.

API
Admin GraphQL API
Operation
productSet
Access scope
write_products
Replaces (REST)
POST /admin/api/products.json and PUT /admin/api/products/{id}.json
API version
2026-10
Last checked

In short

To sync a product from an ERP or PIM, call productSet with the full product: title, status, productOptions and variants. Pass identifier with a handle or ID to update an existing product, or leave it out to create one. List fields are replaced, so variants you leave out are deleted. Use synchronous: true for small products and the asynchronous mode for large ones.

How to use it

  1. Map your source record to ProductSetInput: every option with all its values, then one variant per combination with optionValues, sku and price.
  2. Pass identifier with the handle (or the ID, or a unique metafield as customId) so a second run updates the product instead of creating a copy.
  3. Send SyncProduct. With synchronous: true the product comes back in the response.
  4. For large products, send synchronous: false and poll the productOperation query with the returned productSetOperation ID.

Code

product-set.graphql
mutation SyncProduct($identifier: ProductSetIdentifiers, $input: ProductSetInput!) {
  productSet(identifier: $identifier, input: $input, synchronous: true) {
    product {
      id
      handle
      title
      status
      variants(first: 10) {
        nodes {
          id
          sku
          price
          selectedOptions {
            name
            value
          }
        }
      }
    }
    userErrors {
      code
      field
      message
    }
  }
}
product-set.variables.json
{
  "identifier": {
    "handle": "linen-shirt"
  },
  "input": {
    "title": "Linen shirt",
    "handle": "linen-shirt",
    "status": "ACTIVE",
    "vendor": "Your Brand",
    "productType": "Shirts",
    "tags": ["summer", "linen"],
    "productOptions": [
      {
        "name": "Size",
        "values": [{"name": "S"}, {"name": "M"}, {"name": "L"}]
      }
    ],
    "variants": [
      {"optionValues": [{"optionName": "Size", "name": "S"}], "sku": "LS-S", "price": "49.00"},
      {"optionValues": [{"optionName": "Size", "name": "M"}], "sku": "LS-M", "price": "49.00"},
      {"optionValues": [{"optionName": "Size", "name": "L"}], "sku": "LS-L", "price": "52.00"}
    ]
  }
}
product-set.response.json
{
  "data": {
    "productSet": {
      "product": {
        "id": "gid://shopify/Product/1234567890",
        "handle": "linen-shirt",
        "title": "Linen shirt",
        "status": "ACTIVE",
        "variants": {
          "nodes": [
            {"id": "gid://shopify/ProductVariant/1111111111", "sku": "LS-S", "price": "49.00", "selectedOptions": [{"name": "Size", "value": "S"}]},
            {"id": "gid://shopify/ProductVariant/1111111112", "sku": "LS-M", "price": "49.00", "selectedOptions": [{"name": "Size", "value": "M"}]},
            {"id": "gid://shopify/ProductVariant/1111111113", "sku": "LS-L", "price": "52.00", "selectedOptions": [{"name": "Size", "value": "L"}]}
          ]
        }
      },
      "userErrors": []
    }
  }
}

Written by Lintel. MIT license: free to use in your own store and in client work.

Limits and edge cases

  • List fields (variants, productOptions, metafields, collections, files) are replaced: entries you leave out are deleted. Other fields only change when you send them.
  • Stores can have up to 2048 variants per product by default.
  • Inside a bulk operation, productSet always runs synchronously.
  • To change only variants or only options, use productVariantsBulkCreate, productVariantsBulkUpdate or the productOption mutations instead.

Questions

What is the difference between productSet and productCreate?
productCreate creates the product shell and options; variants follow in other calls. productSet takes the whole product with variants and either creates or updates it, which suits syncing from an external source.
Will productSet delete variants?
Yes, if you leave them out. variants is a list field, so the result matches exactly the list you send.
How do I upsert a product by handle?
Pass identifier: { handle: "your-handle" }. If no product has that handle, productSet creates one.
  • Validated against the Admin or Storefront API schema for version 2026-10 on the date shown. Check the version in your request URL.
  • IDs and store addresses in the examples are placeholders. Replace them with your own.
  • Admin API calls need an access token from your app. Never put an Admin API token in a storefront or theme.
  • MIT license. Use the code in your own store or in client work.

Need this built, tested and maintained for your store? See API integrations.

Have something to build? Let’s talk it through.

Taking a limited number of projects at a time. Every brief is read by an engineer, not a salesperson.

Gencer KarakayaShopify Engineer

An engineer reads your brief, and we reply by email.