--- title: Sync product data description: >- Learn how to sync product data from an external source using the GraphQL Admin API. source_url: html: >- https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data md: >- https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md --- ExpandOn this page * [Requirements](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#requirements) * [How it works](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#how-it-works) * [Step 1: Create or update a product](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#step-1-create-or-update-a-product) * [Step 2: Poll for completion (asynchronous mode only)](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#step-2-poll-for-completion-asynchronous-mode-only) * [Step 3 (Optional): Update existing products](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#step-3-optional-update-existing-products) * [Next steps](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#next-steps) # Sync product data Use the GraphQL Admin API [`productSet`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productCreate) or [`productVariantsBulkUpdate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productVariantsBulkUpdate), [`productSet`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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. *** ## Requirements * Your app can make [authenticated requests](https://shopify.dev/docs/api/admin-graphql#authentication) to the latest version of the GraphQL Admin API or higher. * Your app has the `write_products` [access scope](https://shopify.dev/docs/api/usage/access-scopes). Learn how to [configure your access scopes using Shopify CLI](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration). * You're familiar with Shopify's [product model](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections#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. *** ## How it works The [`productSet`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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. *** ## Step 1: Create or update a product Use [`productSet`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productSet) to sync product data from an external system. The mutation supports two modes: * [**Synchronous mode**](#option-a-use-synchronous-mode) (default): Returns product data immediately. * [**Asynchronous mode**](#option-b-use-asynchronous-mode) (`synchronous: false`): Processes in the background. You'll query for completion separately in [Step 2](#step-2-poll-for-completion-asynchronous-mode-only). ### Option 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 ```graphql 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 ```json { "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": [] } } } ``` ### Option 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`](https://shopify.dev/docs/api/admin-graphql/latest/objects/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](#step-2-poll-for-completion-asynchronous-mode-only). ## POST https://{shop}.myshopify.com/api/{api\_version}/graphql.json ## GraphQL mutation ```graphql 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 ```json { "data": { "productSet": { "productSetOperation": { "id": "gid://shopify/ProductSetOperation/1", "status": "CREATED", "userErrors": [] }, "userErrors": [] } } } ``` *** ## Step 2: Poll for completion (asynchronous mode only) Poll the [`productOperation`](https://shopify.dev/docs/api/admin-graphql/latest/queries/productOperation) query with the operation ID from [Step 1](#step-1-create-or-update-a-product) 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 ```graphql 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) ```json { "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": [] } } } ``` *** ## Step 3 (Optional): Update existing products To update an existing product's complete state, use [`productSet`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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 ```graphql 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 ```json { "data": { "productSet": { "productSetOperation": { "id": "gid://shopify/ProductSetOperation/2", "status": "CREATED" }, "userErrors": [] } } } ``` *** ## Next steps * Learn how to [add product data](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/add-data) using incremental mutations. * Learn how to [update product data](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/update-data) with targeted mutations. * Explore the [`productSet`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productSet) mutation and [`productOperation`](https://shopify.dev/docs/api/admin-graphql/latest/queries/productOperation) query in the GraphQL Admin API reference. *** * [Requirements](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#requirements) * [How it works](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#how-it-works) * [Step 1: Create or update a product](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#step-1-create-or-update-a-product) * [Step 2: Poll for completion (asynchronous mode only)](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#step-2-poll-for-completion-asynchronous-mode-only) * [Step 3 (Optional): Update existing products](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#step-3-optional-update-existing-products) * [Next steps](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data.md#next-steps)