Skip to main content

Sync product data

Use the GraphQL Admin API productSet mutation to synchronize the complete state of a product with data from an external source in a single operation. This mutation is specifically designed for scenarios where Shopify acts as a downstream system that mirrors product data from an authoritative external database.

Unlike incremental mutations like productCreate or productVariantsBulkUpdate, productSet treats each operation as a complete product state declaration. The mutation creates, updates, or removes options and variants as needed to make Shopify's product data match exactly what you provide in the input.


  • Your app can make authenticated requests to the latest version of the GraphQL Admin API or higher.
  • Your app has the write_products access scope. Learn how to configure your access scopes using Shopify CLI.
  • You're familiar with Shopify's product model, where products contain options (like color and size) and variants represent specific purchasable combinations.
  • Your app manages product data in an external system (ERP, PIM, spreadsheet, or custom database) and needs to sync that data to Shopify.

The productSet mutation updates only the fields you explicitly include in the input. For options and variants, the mutation replaces the complete state—any options or variants not included in your input are removed. For other product fields (like description or tags), omitted fields remain unchanged while included fields are updated to match your input, even if you provide an empty value.

For example, if an existing product has a description and tags, and you call productSet with only title, productOptions, and variants, the description and tags remain unchanged. However, if you include tags: [] in your input, the tags are cleared.

The mutation supports both synchronous mode (returns results immediately) and asynchronous mode (processes in the background). Choose the mode that best fits your needs.


Anchor to Step 1: Create or update a productStep 1: Create or update a product

Use productSet to sync product data from an external system. The mutation supports two modes:

Anchor to Option A: Use synchronous modeOption A: Use synchronous mode

The following example shows how to create a product synchronously. The mutation waits until processing completes, then returns the complete product data in a single response. No polling is required.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation setProductSync {
productSet(
# Set to true (or omit) to run synchronously.
# The mutation waits for completion and returns product data immediately.
synchronous: true,
input: {
title: "My Cool Product",
# Define the complete product state.
productOptions: [
{
name: "Color",
values: [
{ name: "Red" },
{ name: "Green" },
{ name: "Blue" }
]
}
],
# Define all variants that should exist.
variants: [
{ optionValues: [{ optionName: "Color", name: "Red" }] },
{ optionValues: [{ optionName: "Color", name: "Green" }] },
{ optionValues: [{ optionName: "Color", name: "Blue" }] }
]
}
) {
product {
id
title
options {
id
name
optionValues {
id
name
hasVariants
}
}
variants(first: 100) {
edges {
node {
id
selectedOptions {
name
value
}
}
}
}
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"productSet": {
"product": {
"id": "gid://shopify/Product/1",
"title": "My Cool Product",
"options": [
{
"id": "gid://shopify/ProductOption/1",
"name": "Color",
"optionValues": [
{
"id": "gid://shopify/ProductOptionValue/1",
"name": "Red",
"hasVariants": true
},
{
"id": "gid://shopify/ProductOptionValue/2",
"name": "Green",
"hasVariants": true
},
{
"id": "gid://shopify/ProductOptionValue/3",
"name": "Blue",
"hasVariants": true
}
]
}
],
"variants": {
"edges": [
{
"node": {
"id": "gid://shopify/ProductVariant/1",
"selectedOptions": [
{
"name": "Color",
"value": "Red"
}
]
}
},
{
"node": {
"id": "gid://shopify/ProductVariant/2",
"selectedOptions": [
{
"name": "Color",
"value": "Green"
}
]
}
},
{
"node": {
"id": "gid://shopify/ProductVariant/3",
"selectedOptions": [
{
"name": "Color",
"value": "Blue"
}
]
}
}
]
}
},
"userErrors": []
}
}
}

Anchor to Option B: Use asynchronous modeOption B: Use asynchronous mode

The following example shows how to create a product with three color variants in asynchronous mode. The mutation returns immediately with a ProductSetOperation object (status "CREATED") while Shopify processes the product in the background. You'll use the operation ID to poll for completion in Step 2.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation setProduct {
productSet(
# Set to false to run asynchronously.
# The mutation returns immediately with an operation ID.
synchronous: false,
input: {
# Include the product ID to update an existing product.
# Omit the ID to create a new product.
id: "gid://shopify/Product/1",
title: "My Cool Product",
# Define the complete product state.
# All options and variants must be specified.
productOptions: [
{
name: "Color",
values: [
{ name: "Red" },
{ name: "Green" },
{ name: "Blue" }
]
}
],
# Define all variants that should exist.
# Any existing variants not included here will be deleted.
variants: [
{ optionValues: [{ optionName: "Color", name: "Red" }] },
{ optionValues: [{ optionName: "Color", name: "Green" }] },
{ optionValues: [{ optionName: "Color", name: "Blue" }] }
]
}
) {
productSetOperation {
id
status
userErrors {
code
field
message
}
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"productSet": {
"productSetOperation": {
"id": "gid://shopify/ProductSetOperation/1",
"status": "CREATED",
"userErrors": []
},
"userErrors": []
}
}
}

Anchor to Step 2: Poll for completion (asynchronous mode only)Step 2: Poll for completion (asynchronous mode only)

Poll the productOperation query with the operation ID from Step 1 until the status changes to COMPLETE or FAILED.

The following example queries the operation status. When COMPLETE, the response includes the full product data. If FAILED, check userErrors for details.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL query

query productSetOperation {
# Use the operation ID returned from the productSet mutation.
productOperation(id: "gid://shopify/ProductSetOperation/1") {
... on ProductSetOperation {
id
# Status indicates the current state: CREATED, COMPLETE, or FAILED.
# Continue polling while status is CREATED.
status
# Product data is only available when status is COMPLETE.
# This field is null while the operation is processing.
product {
id
title
options {
id
name
optionValues {
id
name
}
}
variants(first: 100) {
edges {
node {
id
selectedOptions {
name
value
}
}
}
}
}
# Check userErrors if status is FAILED to understand what went wrong.
userErrors {
code
field
message
}
}
}
}

JSON response (when COMPLETE)

{
"data": {
"productOperation": {
"id": "gid://shopify/ProductSetOperation/1",
"status": "COMPLETE",
"product": {
"id": "gid://shopify/Product/1",
"title": "My Cool Product",
"options": [
{
"id": "gid://shopify/ProductOption/1",
"name": "Color",
"optionValues": [
{
"id": "gid://shopify/ProductOptionValue/1",
"name": "Red"
},
{
"id": "gid://shopify/ProductOptionValue/2",
"name": "Green"
},
{
"id": "gid://shopify/ProductOptionValue/3",
"name": "Blue"
}
]
}
],
"variants": {
"edges": [
{
"node": {
"id": "gid://shopify/ProductVariant/1",
"selectedOptions": [
{
"name": "Color",
"value": "Red"
}
]
}
},
{
"node": {
"id": "gid://shopify/ProductVariant/2",
"selectedOptions": [
{
"name": "Color",
"value": "Green"
}
]
}
},
{
"node": {
"id": "gid://shopify/ProductVariant/3",
"selectedOptions": [
{
"name": "Color",
"value": "Blue"
}
]
}
}
]
}
},
"userErrors": []
}
}
}

Anchor to Step 3 (Optional): Update existing productsStep 3 (Optional): Update existing products

To update an existing product's complete state, use productSet with the product ID in the input. The mutation replaces all product data with what you provide.

The following example shows how to update a product's title and replace its color options and variants. If the product previously had 5 variants and you provide 3, it will have exactly 3 variants (2 deleted). Any options or variants not included in the input are removed.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation updateProduct {
productSet(
input: {
# Including the product ID tells the API to update this existing product.
# The complete product state will be replaced with what you provide.
id: "gid://shopify/Product/1",
title: "My Extra Cool Product",
# Replace all existing options and values with these.
# Any options not included here will be deleted.
productOptions: [
{
name: "Color",
values: [
{ name: "Maroon" },
{ name: "Forest Green" },
{ name: "Deep Sea Blue" }
]
}
],
# Replace all existing variants with exactly these three.
# If the product had 5 variants before, it will have 3 after (2 deleted).
variants: [
{ optionValues: [{ optionName: "Color", name: "Deep Sea Blue" }] },
{ optionValues: [{ optionName: "Color", name: "Forest Green" }] },
{ optionValues: [{ optionName: "Color", name: "Maroon" }] }
]
}
) {
productSetOperation {
id
status
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"productSet": {
"productSetOperation": {
"id": "gid://shopify/ProductSetOperation/2",
"status": "CREATED"
},
"userErrors": []
}
}
}


Was this page helpful?