Google Sheets to Shopify, products added without duplicates
Your “simple” product upload turns into a mess fast. One wrong CSV, one duplicate handle, one missed inventory setting, and suddenly you’re fixing the store instead of building it.
This Sheets Shopify import problem hits ecommerce managers hardest, honestly. But agency teams setting up client staging stores and founders testing new themes feel it too. You need products created cleanly, with inventory ready to sell, without babysitting every row.
This n8n workflow pulls products from Google Sheets, checks Shopify for duplicates by slug (handle), and only creates what’s new. You’ll see how it works, what you need, and what results to expect.
How This Automation Works
The full n8n workflow, from trigger to final output:
n8n Workflow Template: Google Sheets to Shopify, products added without duplicates
flowchart LR
subgraph sg0["Start Workflow Flow"]
direction LR
n0@{ icon: "mdi:play-circle", form: "rounded", label: "Start Workflow", pos: "b", h: 48 }
n1@{ icon: "mdi:database", form: "rounded", label: "Google Sheet, Fetch Products", pos: "b", h: 48 }
n2["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/graphql.png' width='40' height='40' /></div><br/>Shopify, ProductQuery"]
n3@{ icon: "mdi:swap-horizontal", form: "rounded", label: "If product exists", pos: "b", h: 48 }
n4["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/graphql.png' width='40' height='40' /></div><br/>Shopify, CreateProduct"]
n5@{ icon: "mdi:swap-vertical", form: "rounded", label: "Loop Over Items", pos: "b", h: 48 }
n6["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/graphql.png' width='40' height='40' /></div><br/>Shopify, GetLocations"]
n7["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/graphql.png' width='40' height='40' /></div><br/>Shopify, Enable InventoryTra.."]
n8["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/graphql.png' width='40' height='40' /></div><br/>Shopify, Set InventoryLevel"]
n9@{ icon: "mdi:cog", form: "rounded", label: "Finished", pos: "b", h: 48 }
n0 --> n6
n5 --> n9
n5 --> n2
n3 --> n5
n3 --> n4
n6 --> n1
n2 --> n3
n4 --> n7
n8 --> n5
n1 --> n5
n7 --> n8
end
%% Styling
classDef trigger fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
classDef ai fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef aiModel fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px
classDef decision fill:#fff8e1,stroke:#f9a825,stroke-width:2px
classDef database fill:#fce4ec,stroke:#c2185b,stroke-width:2px
classDef api fill:#fff3e0,stroke:#e65100,stroke-width:2px
classDef code fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef disabled stroke-dasharray: 5 5,opacity: 0.5
class n0 trigger
class n3 decision
class n1 database
classDef customIcon fill:none,stroke:none
class n2,n4,n6,n7,n8 customIcon
The Problem: Adding Products Without Creating a Duplicate Mess
Importing products sounds like a one-time chore, until you do it more than once. You tweak a title, re-upload a sheet, and Shopify ends up with near-identical products that differ only by a handle or SKU. Then inventory gets weird. Some items track stock, others don’t, and the “available” quantity is wrong at the default location. Even in a staging store, that chaos wastes time because every fix is manual, slow, and easy to mess up again.
The friction compounds. Here’s where it breaks down.
- You re-import the same rows and create duplicates because there’s no reliable “already exists” check tied to Shopify handles.
- Inventory tracking is often forgotten during imports, which means stock numbers don’t actually control purchasing.
- Setting starting stock at the right location becomes a second workflow in your head, not in your systems.
- One typo in a slug or SKU triggers hours of cleanup across product pages, collections, and reports.
The Solution: Create Shopify Products from Google Sheets (No Duplicates)
This workflow takes a Google Sheet that already matches how your team thinks (one row per product) and turns it into a controlled Shopify import. You start it manually in n8n, and it immediately fetches your store’s locations so stock updates go to the right place. Then it reads your sheet rows in batches, checks Shopify to see if a product with that slug (handle) already exists, and makes a decision. If Shopify already has it, the workflow skips it and moves on. If it’s new, the workflow creates the product via Shopify’s GraphQL Admin API, enables inventory tracking, and sets the starting quantity at your default location.
It begins when you run the workflow and it pulls your latest sheet data. From there, it loops through each row, queries Shopify by handle, and only creates products that don’t exist yet. Finally, it flips inventory tracking on and adjusts stock so the product is actually sellable without extra clicks.
What You Get: Automation vs. Results
| What This Workflow Automates | Results You’ll Get |
|---|---|
|
|
Example: What This Looks Like
Say you have 120 test products in a Google Sheet and you refresh a staging store once a week. Manually, you might spend about 2 minutes per product to check for duplicates, create it, then find inventory settings and set stock, which is roughly 4 hours of repetitive clicking. With this workflow, you update the sheet, run the manual trigger, and let it process the rows. In practice, that’s closer to 10 minutes of setup time plus some waiting while Shopify creates products and updates inventory.
What You’ll Need
- n8n instance (try n8n Cloud free)
- Self-hosting option if you prefer (Hostinger works well)
- Google Sheets to store product rows and columns.
- Shopify (Admin access) to create products and edit inventory.
- Shopify Admin API access token (create it in Shopify Admin under Apps, then “Develop apps”).
Skill level: Intermediate. You’ll connect accounts, paste API credentials, and update the Shopify store URL inside the GraphQL nodes.
Don’t want to set this up yourself? Talk to an automation expert (free 15-minute consultation).
How It Works
You trigger the run manually. That’s intentional for imports, especially in staging, because you can update the sheet first and hit “go” when you’re ready.
The workflow finds your store’s location. Shopify inventory is location-based, so the automation fetches locations up front and uses the default one for stock updates.
Each sheet row is checked against Shopify before creation. n8n reads the Google Sheet and processes products in batches, querying Shopify’s GraphQL API to see if the slug already exists as a product handle. If it exists, the workflow does nothing and moves on.
New products are created and made “inventory real.” When a handle doesn’t exist, it creates the product, turns on inventory tracking for the inventory item, and sets stock_on_hand at the default location so you can actually test add-to-cart flows.
You can easily modify the sheet columns to match your catalog conventions based on your needs. See the full implementation guide below for customization options.
Step-by-Step Implementation Guide
Step 1: Configure the Manual Trigger
This workflow starts manually so you can run imports on demand.
- Add the Manual Start Trigger node as the workflow entry point.
- Connect Manual Start Trigger to Fetch Store Locations to begin the Shopify data lookups.
Step 2: Connect Google Sheets
Pull the product data from your spreadsheet before iterating through each row.
- Add Retrieve Sheet Products and set Document to the spreadsheet named
Shopify Test Store Data. - Set Sheet to
Products. - Credential Required: Connect your googleSheetsOAuth2Api credentials in Retrieve Sheet Products.
- Connect Fetch Store Locations → Retrieve Sheet Products → Iterate Sheet Rows to match the execution flow.
Step 3: Set Up Processing and Existence Checks
Loop through each row and check if the product already exists in Shopify by handle.
- In Iterate Sheet Rows, keep default batch settings to process each row from Retrieve Sheet Products.
- Configure Query Store Products with Endpoint
https://[YOUR_ID].myshopify.com/admin/api/2025-04/graphql.jsonand Variables to={ "handle" : "{{ $json.slug }}" }. - Credential Required: Connect your httpHeaderAuth credentials in Query Store Products.
- Set Product Exists Check condition Left Value to
{{ $json.data.productByHandle }}with the Exists operator. - Wire the logic so Query Store Products → Product Exists Check, then the “true” output loops back to Iterate Sheet Rows.
Tip: Ensure your Google Sheet includes the slug field—this is required for the handle lookup in Query Store Products.
Step 4: Configure Product Creation and Inventory Actions
If the product does not exist, the workflow creates it and updates inventory tracking and quantities.
- In Generate Product Entry, set Endpoint to
https://[YOUR_ID].myshopify.com/admin/api/2025-04/graphql.jsonand Variables to the provided JSON expression, including{{ $('Iterate Sheet Rows').item.json.title }},{{ $('Iterate Sheet Rows').item.json.description }},{{ $('Iterate Sheet Rows').item.json.company }},{{ $('Iterate Sheet Rows').item.json.category }},{{ $('Iterate Sheet Rows').item.json.status }}, and{{ $('Iterate Sheet Rows').item.json.slug }}. - Credential Required: Connect your httpHeaderAuth credentials in Generate Product Entry.
- Configure Activate Inventory Tracking with Variables set to
={ "id": "{{ $json.data.productCreate.product.variants.edges[0].node.inventoryItem.id }}", "input": { "tracked": true, "requiresShipping": true } }. - Configure Adjust Inventory Quantity with Endpoint
=https://[YOUR_ID].myshopify.com/admin/api/2025-04/graphql.jsonand Variables to={ "input" : { "reason": "correction", "setQuantities" :[{ "inventoryItemId":"{{ $json.data.inventoryItemUpdate.inventoryItem.id }}", "locationId": "{{ $('Fetch Store Locations').item.json.data.locations.edges[0].node.id }}", "quantity" : {{ $('Iterate Sheet Rows').item.json.stock_on_hand }} }] } }. - Credential Required: Connect your httpHeaderAuth credentials in Fetch Store Locations, Activate Inventory Tracking, and Adjust Inventory Quantity.
- Ensure the execution path follows Generate Product Entry → Activate Inventory Tracking → Adjust Inventory Quantity → Iterate Sheet Rows.
⚠️ Common Pitfall: The Adjust Inventory Quantity endpoint is an expression starting with =https://.... Keep the leading = to preserve the expression format.
Step 5: Test and Activate Your Workflow
Run a manual test to confirm products are created only when missing and inventory is updated correctly.
- Click Execute Workflow on Manual Start Trigger to run a test import.
- Verify that Query Store Products returns existing products and routes them back to Iterate Sheet Rows.
- Confirm new items pass through Generate Product Entry, then update inventory via Activate Inventory Tracking and Adjust Inventory Quantity.
- Check that the final batch completes at Completion Marker without errors.
- Once verified, toggle the workflow to Active to use it in production.
Common Gotchas
- Shopify GraphQL credentials can expire or lack the right scopes. If things break, check your custom app access scopes in Shopify Admin first (Products and Inventory are the usual suspects).
- If you’re using Wait nodes or external rendering, processing times vary. Bump up the wait duration if downstream nodes fail on empty responses.
- Google Sheets column headers must match what the workflow expects (like slug, sku, and stock_on_hand). A small header change can make fields come through blank, which leads to “created, but wrong” products.
Frequently Asked Questions
About 30 minutes if your sheet and Shopify access are ready.
No. You’ll connect Google Sheets and Shopify, then paste credentials into n8n.
Yes. n8n has a free self-hosted option and a free trial on n8n Cloud. Cloud plans start at $20/month for higher volume. You’ll also need to factor in Shopify Admin API access (included with Shopify), since this workflow uses GraphQL calls.
Two options: n8n Cloud (managed, easiest setup) or self-hosting on a VPS. For self-hosting, Hostinger VPS is affordable and handles n8n well. Self-hosting gives you unlimited executions but requires basic server management.
Yes, but keep it disciplined. The easiest approach is to add columns to your Google Sheet and map them inside the “Generate Product Entry” GraphQL node so Shopify receives the extra fields. Common customizations include setting vendor/company from your company column, mapping product type from category, and defaulting status to DRAFT for safer staging imports.
Usually it’s an API token issue or missing scopes on the Shopify custom app. Regenerate the Admin API access token, then update it anywhere the GraphQL nodes authenticate. Also verify the store URL inside each GraphQL request (people forget to replace the placeholder), and watch for rate limiting if you run huge imports back-to-back.
Hundreds in a single run is normal, and more is possible if you let it take its time.
For this use case, n8n is usually the better fit because Shopify GraphQL calls, branching (exists vs. create), and batch processing are first-class patterns in n8n. Zapier can do it, but you often end up paying for high task volume and working around logic limits. Make is flexible, though complex Shopify inventory steps can still get fiddly. If you’re importing once a month, that may not matter. If you’re rebuilding staging stores often, it does. Talk to an automation expert if you’re not sure which fits.
Once this is in place, product imports stop being risky. You get a repeatable path from sheet to Shopify, with duplicates blocked and inventory handled automatically.
Need Help Setting This Up?
Our automation experts can build and customize this workflow for your specific needs. Free 15-minute consultation—no commitment required.