Sync Shopify Customers to HubSpot with n8n (Free, Real-Time)

Sync Shopify customers to HubSpot workflow in n8n









This guide shows you how to sync Shopify customers to HubSpot with n8n in real time, so every new or updated shopper lands in your CRM within seconds. Two Shopify webhooks catch customer events, a Filter drops records with no email, an Edit Fields node maps the data, and the HubSpot node upserts a contact by email. No duplicates, no paid sync app, and no manual CSV exports ever again.

Prefer to skip the build? Grab the ready-made template at the bottom of this guide and be syncing in under ten minutes.

What it does

Your customer list lives in Shopify, but your sales follow-up, lists, and reporting live in HubSpot. Keeping the two in sync by hand means exporting a CSV every week, deduping it, and re-importing. Customers who bought yesterday sit invisible in your CRM until you get around to it.

This workflow closes that gap. The moment a shopper creates an account or updates their details in Shopify, n8n receives the event, cleans the data, and writes a HubSpot contact. The same run handles both first-time customers and edits to existing ones, so your CRM always mirrors the store.

Concretely, once it is live:

  1. A customer signs up or is created at checkout in Shopify.
  2. n8n receives the customers/create (or customers/update) webhook instantly.
  3. The record is checked for a valid email and mapped to HubSpot fields.
  4. HubSpot creates a new contact, or updates the existing one that shares that email.

You get a HubSpot contact database that stays current on its own, complete with each shopper’s order count and lifetime spend carried over as custom properties.

Why it beats the default

Shopify and HubSpot both sell you a way to bridge them, and both have real drawbacks.

Approach Cost The catch
Manual CSV export/import Free Stale within a day, dedupe headaches, easy to forget
Paid connector app $15 to $50+ per month Recurring fee, rigid field mapping, another vendor to trust
HubSpot native Shopify sync Paid tiers Locks the useful settings behind higher plans, limited field control
This n8n workflow Free (self-hosted) You own the mapping and the logic end to end

With n8n you decide exactly which Shopify fields become which HubSpot properties, you add or remove custom fields whenever you like, and you pay nothing beyond the server you already run. When Shopify changes a payload or you want to enrich the contact before it lands, you edit one node instead of filing a support ticket. This is the same building-block approach behind our wider library of n8n Shopify automation guides.

What you need

  • A running n8n instance (Cloud or self-hosted, version 1.0 or newer).
  • A Shopify store and a custom app created through the 2026 Dev Dashboard. If you have not connected Shopify to n8n yet, follow connect Shopify to n8n (2026 method) first; the trigger needs the read_customers scope.
  • A free HubSpot account with a Private App token (we cover this below).
  • About 30 minutes if you build from scratch, or under 10 with the template.

Node-by-node list

Five nodes, one straight line with two entry points:

  [Shopify Trigger: customers/create] --.
                                         >--[Filter: email exists]--[Edit Fields: map]--[HubSpot: create/update contact]
  [Shopify Trigger: customers/update] --'
# Node Type Job
1 New Customer n8n-nodes-base.shopifyTrigger Fires on customers/create
2 Updated Customer n8n-nodes-base.shopifyTrigger Fires on customers/update
3 Has Email? n8n-nodes-base.filter Continues only if email is set
4 Map to HubSpot n8n-nodes-base.set Shapes the payload into HubSpot properties
5 Upsert Contact n8n-nodes-base.hubspot Create or Update a contact by email

Step-by-step build

1 New Customer (Shopify Trigger)

Add a Shopify Trigger node. Select your Shopify credential (set up via the 2026 Dev Dashboard method), then set Topic to Customer Created. n8n registers the webhook with Shopify automatically the first time the workflow is active. A new sign-up now delivers a payload like this:

{
  "id": 8213004821,
  "email": "james.carter@gmail.com",
  "first_name": "James",
  "last_name": "Carter",
  "phone": "+1 555 234 7890",
  "orders_count": 0,
  "total_spent": "0.00",
  "tags": "",
  "default_address": {
    "city": "Austin",
    "province_code": "TX",
    "country": "United States"
  }
}
💡

Tip: Use n8n’s Listen for event button and create a test customer in Shopify to capture a real payload. Everything downstream can then be mapped by clicking fields instead of typing expressions.

2 Updated Customer (Shopify Trigger)

Add a second Shopify Trigger with the same credential but Topic set to Customer Updated. This catches address changes, new orders raising the order count, and marketing-consent edits. Both triggers are entry points and both connect forward to the same Filter node, so you maintain the mapping in one place.

📌

Do not chain the two triggers together. Each is an independent start of the flow; wire both of their outputs into the Filter node.

3 Has Email? (Filter)

Add a Filter node so only records with an email reach HubSpot. Set one condition:

  • Value 1: ={{ $json.email }}
  • Operation: is not empty

Shopify allows phone-only customers, and HubSpot keys contacts on email, so this guard stops the workflow from failing on records it cannot upsert.

4 Map to HubSpot (Edit Fields)

Add an Edit Fields node (also called Set). Keep only set fields listed below off, and add one assignment per property so the payload matches HubSpot’s field names:

Name Type Value
email String ={{ $json.email }}
firstname String ={{ $json.first_name }}
lastname String ={{ $json.last_name }}
phone String ={{ $json.phone }}
city String ={{ $json.default_address?.city }}
state String ={{ $json.default_address?.province_code }}
shopify_customer_id String ={{ $json.id }}
shopify_orders_count Number ={{ $json.orders_count }}
shopify_total_spent Number ={{ $json.total_spent }}

The first four map to HubSpot’s built-in contact properties. The last three are custom properties you create once inside HubSpot under Settings → Properties → Create property, on the Contact object, named exactly as above.

📌

Create shopify_customer_id, shopify_orders_count, and shopify_total_spent in HubSpot before your first run. Sending a value for a property that does not exist returns a 400 error.

5 Upsert Contact (HubSpot)

Add the HubSpot node and configure it:

  1. Authentication: App Token, using the Private App token from your HubSpot account.
  2. Resource: Contact.
  3. Operation: Create or Update.
  4. Email: ={{ $json.email }} — this is the match key.
  5. Under Additional Fields, add First Name, Last Name, Phone Number, City, State, and each custom property, pulling from the mapped fields (for example First Name = ={{ $json.firstname }}).

Because the operation is Create or Update, HubSpot matches on the email. A brand-new email becomes a fresh contact; a known email updates the record in place. That single choice is what keeps your CRM clean.

💡

Tip: Turn on Save Execution Data for this workflow so you can see exactly what was sent to HubSpot if a contact ever looks wrong.

Save the workflow, toggle it Active, and you are live. Create a test customer in Shopify and watch the contact appear in HubSpot seconds later.

Common mistakes

  • Skipping the custom properties. Sending shopify_total_spent before it exists in HubSpot throws a 400. Create all three custom properties first.
  • Chaining the two triggers. Both Shopify Triggers are separate entry points. Wire each one into the Filter; never connect one trigger’s output into the other.
  • Wrong Shopify scope. Without read_customers the webhooks never register. Set the scope when you create the app.
  • No email guard. Remove the Filter and phone-only customers will fail the HubSpot step and clutter your execution log with errors.
  • Expecting deduplication elsewhere. The safety against duplicates comes entirely from the Create or Update operation matching on email. Do not switch it to plain Create.

Cost at realistic volume

Take a store adding 500 new or updated customers a month.

Component Usage Cost
Shopify webhooks 500 events Free (built in)
n8n (self-hosted) 500 executions Free
HubSpot free CRM 500 contact upserts Free (up to 1M contacts)
Total $0 / month

Even at 5,000 customer events a month you stay comfortably inside HubSpot’s free API allowance (100 requests per 10 seconds) because Shopify delivers webhooks in a steady trickle, not a burst. The only real cost is the server you already run n8n on.

Get the Shopify to HubSpot Sync template

The guide above is free to follow. If you would rather skip the build, download the ready-to-import workflow, then just add your credentials. Prefer it installed and tuned for you? See our done-for-you services.

Download the template ($12) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

Does this create duplicate contacts in HubSpot?

No. The HubSpot node uses the Create or Update operation, which matches on the email address. If a contact with that email already exists, HubSpot updates it instead of adding a new record, so re-syncing the same customer is always safe.

What if a Shopify customer has no email address?

The Filter node drops those records before they reach HubSpot. HubSpot identifies contacts by email, so a phone-only Shopify customer cannot be upserted. Filtering them out keeps the workflow from erroring on records it cannot process.

Can I sync historical Shopify customers, not just new ones?

Yes. Add a one-time branch with a Schedule Trigger plus the Shopify Get All Customers operation, loop the results through the same Filter, Edit Fields, and HubSpot nodes, then disable that branch once the backfill finishes.

Do I need a paid HubSpot plan for this?

No. The free HubSpot CRM stores up to one million contacts and its API supports contact create and update. You only need a paid tier if you later add marketing email or workflow automations inside HubSpot itself, which this n8n workflow does not require.

Which Shopify API scope does the trigger need?

The connection needs read_customers so the customers/create and customers/update webhooks can fire and return the full customer payload. Set this scope when you create the app in the Shopify Dev Dashboard, following the 2026 connection guide linked above.

Related guides

n8n
Shopify
HubSpot
CRM
automation

Auto-Hide Out-of-Stock Products on Shopify With n8n









This guide builds a Shopify auto hide out of stock products n8n workflow that scans your catalog on a schedule, sets any product with zero total inventory to draft so it drops off your storefront, then republishes it the moment stock comes back. It marks every product it touches with an “auto-hidden” tag so it never republishes something you drafted on purpose. Free to run, no monthly app fee, and you own the logic.

What it does

An empty product page is a small leak that runs all day. A shopper clicks an ad or a search result, lands on a hoodie that says “Sold out”, and leaves. Google keeps crawling the dead page, and your collection grids stay cluttered with items nobody can buy. The usual fix is a paid app that hides sold-out products for you, billed every month whether or not your stock moves.

This workflow does the same job inside n8n, on infrastructure you already control. On a fixed interval it pulls every product from the Shopify Admin API, adds up the inventory across all variants, and decides one of three things per product:

  • Total inventory is zero and the product is currently active, so set it to draft and tag it auto-hidden.
  • Total inventory is back above zero, the product is a draft, and it carries the auto-hidden tag, so set it back to active and remove the tag.
  • Anything else, leave it alone.

A drafted product disappears from the online store, collections, and search, but keeps its URL, reviews, and history intact. When you restock, the exact same page comes back live with its ranking signals preserved.

Why it beats the default

Shopify’s built-in behaviour is to show sold-out products with a disabled “Sold out” button. That is fine for a hype drop where scarcity sells, but for most catalogs it wastes ad clicks and buries in-stock items. Shopify Flow can react to inventory changes, but it does not have a clean built-in action to unpublish a product based on the summed stock of all its variants, and it cannot run the periodic full-catalog sweep that catches products drafted before you turned automation on.

Paid hide-out-of-stock apps solve it but charge a recurring fee and lock the logic inside their dashboard. The n8n version costs nothing beyond the server you may already run for other automations, and every rule is visible and editable. Want to keep products with a “backorder” tag live even at zero stock? That is one line in a Code node, not a support ticket. This is one of the cleaner builds in the wider world of n8n Shopify automation, because it touches only the standard products and inventory endpoints.

What you need

  • An n8n instance, either self-hosted or n8n Cloud.
  • A Shopify custom app access token with read_products and write_products scopes. Create it through the 2026 Shopify Dev Dashboard method described in our connect Shopify to n8n guide; that connection is the prerequisite for everything below.
  • Products whose variants have inventory tracking enabled, so quantities are real numbers rather than null.
  • Optional: a Telegram bot token and a Google Sheet if you want a change log and an alert each run.

We use Admin API version 2026-04 throughout. All requests go to https://your-store.myshopify.com/admin/api/2026-04/ with the access token sent in the X-Shopify-Access-Token header, stored as an n8n Header Auth credential.

Node-by-node list

  1. Schedule Trigger (n8n-nodes-base.scheduleTrigger) fires every 15 minutes.
  2. Get products (HTTP Request v4.2) GETs the product list with id, title, status, tags, and variants.
  3. Evaluate stock (Code node) sums variant inventory per product and outputs only the products that need to change, with the target status and tags.
  4. Needs update? (IF node) passes only items the Code node flagged, so a quiet run makes zero write calls.
  5. Update product (HTTP Request v4.2) PUTs the new status and tags back to Shopify.
  6. Log to Sheet (Google Sheets, append) records what was hidden or restored.
  7. Notify (Telegram) sends a short summary so you know the sweep ran.

Step-by-step build

  1. Add a Schedule Trigger. Set the rule to an interval of 15 minutes. A 15-minute cadence keeps sold-out pages live for a quarter of an hour at most while staying far under any rate limit.
  2. Add an HTTP Request node named Get products. Method GET, URL https://your-store.myshopify.com/admin/api/2026-04/products.json?limit=250&fields=id,title,status,tags,variants. Under Authentication choose your Shopify Header Auth credential. If your catalog is larger than 250 products, enable pagination in the node using the Link response header so every page is fetched.
  3. Add a Code node named Evaluate stock, language JavaScript, and paste the logic below. It reads the products array, totals the inventory of every variant, and returns one item per product that needs a change:
    const out = [];
    for (const item of $input.all()) {
      const products = item.json.products || [item.json];
      for (const p of products) {
        const total = (p.variants || [])
          .reduce((s, v) => s + (Number(v.inventory_quantity) || 0), 0);
        const tags = (p.tags || '').split(',').map(t => t.trim()).filter(Boolean);
        const isHidden = tags.includes('auto-hidden');
    
        if (total <= 0 && p.status === 'active') {
          out.push({ json: { id: p.id, title: p.title, status: 'draft',
            tags: [...tags, 'auto-hidden'].join(', '), action: 'hidden' } });
        } else if (total > 0 && p.status === 'draft' && isHidden) {
          out.push({ json: { id: p.id, title: p.title, status: 'active',
            tags: tags.filter(t => t !== 'auto-hidden').join(', '), action: 'restored' } });
        }
      }
    }
    return out;
  4. Add an IF node named Needs update?. Condition: {{ $json.id }} is not empty. When the Code node returns nothing, the run ends here with no write calls, which is what you want on a quiet sweep.
  5. Add an HTTP Request node named Update product on the IF true branch. Method PUT, URL https://your-store.myshopify.com/admin/api/2026-04/products/{{ $json.id }}.json, same Header Auth credential. Set Body Content Type to JSON and use this JSON body:
    {
      "product": {
        "id": {{ $json.id }},
        "status": "{{ $json.status }}",
        "tags": "{{ $json.tags }}"
      }
    }

    Sending both fields in one call flips the status and updates the tag together.

  6. Add a Google Sheets node in append mode mapping title, action, and a timestamp, so you have an audit trail of every hide and restore.
  7. Add a Telegram node with your chat ID and a message such as {{ $json.title }} was {{ $json.action }}. Save and activate the workflow.

Test it by setting one product’s inventory to zero, waiting for the next run, and confirming it moves to draft with the tag; then restock it and confirm it returns to active on the following sweep.

Common mistakes

Republishing products you drafted on purpose

Without the auto-hidden tag check, a restock sweep would set every drafted product to active, including seasonal items and unfinished listings. The tag is the guardrail: the workflow only ever republishes products it hid itself.

Reading only the first variant

A product is out of stock only when every variant is at zero. Summing all variants, as the Code node does, avoids hiding a shirt just because the small is gone while medium and large are in stock.

Untracked inventory returning null

If a variant has inventory tracking off, its inventory_quantity is null and would never count as zero. Enable tracking on the products you want managed, or the workflow will treat them as permanently in stock.

Skipping pagination

The products endpoint returns at most 250 items per page. A store with 600 products that ignores the Link header only ever sees the first 250, so the rest are never hidden or restored.

Cost at realistic volume

The moving parts are Shopify API calls and your n8n runtime. Every 15 minutes is 96 runs a day. Each run is one product-list read (a few calls with pagination) plus a write only for products that changed, which is usually a handful. That is well inside Shopify’s standard REST rate limit of two requests per second per store, with no extra charge from Shopify for these calls.

On n8n Cloud the Starter plan covers roughly 2,500 executions a month; 96 runs a day is about 2,900, so a heavy catalog may want the next tier or a 20-minute interval. On self-hosted n8n the executions are free and the only cost is the server you already run. Google Sheets and Telegram are free at this volume. Compared with a hide-out-of-stock app at five to fifteen dollars a month, the workflow pays for itself immediately on self-hosted n8n.

Ready-to-import template

We package this as an importable n8n JSON with the Schedule Trigger, HTTP Request nodes, Code logic, and IF branch already wired, plus a setup guide and a credentials guide for the Shopify token. If you would rather have it installed, configured against your store, and tested for you, that is exactly what our done-for-you automation service covers. Either way you end up with a self-owned workflow instead of another monthly app subscription.

FAQ

Does hiding a product delete it or lose its SEO?

No. Setting a product to draft only removes it from the online store, collections, and search. The product, its URL, reviews, images, and history all stay intact. When you restock, the same page returns live with its existing ranking signals, so you lose nothing permanent.

How fast does a sold-out product get hidden?

At the 15-minute schedule in this guide, a product is hidden within 15 minutes of hitting zero across all variants. Shorten the interval for faster reaction, or lengthen it to save executions. For instant reaction you would trigger on the inventory webhook instead of a scheduled scan.

Will it republish products I set to draft myself?

No. The workflow only republishes products carrying the auto-hidden tag, which it adds when it hides them. Anything you drafted manually has no such tag, so the restock branch skips it. This is the single most important safeguard in the build.

What if a product has multiple variants?

The Code node sums inventory across every variant, so a product is hidden only when all its variants are at zero and restored as soon as any variant has stock again. A single size being sold out never hides the whole product.

Do I need to code to set this up?

You paste one short JavaScript block into the Code node and change your store URL; the rest is clicking nodes together. If you prefer not to touch it at all, import the ready-made template or have us set it up against your store.

Related guides

How to Bulk Upload Shopify Products From a CSV With n8n

Shopify bulk product upload from CSV workflow in n8n








Shopify bulk product upload from CSV with n8n turns a Google Sheet or exported CSV into live Shopify products, one row at a time, with no paid importer app. You map spreadsheet columns to product fields, loop through each row, and POST to the Shopify Admin API on a rate-safe delay. This guide builds the whole flow from scratch and hands you a template you can import in minutes.

If you have ever pasted 200 rows into Shopify’s native CSV importer and watched it silently drop half of them, you know the pain. The built-in importer is rigid about column names, gives you almost no error feedback, and cannot touch anything Shopify did not plan for. This n8n workflow reads your spreadsheet, builds a clean product payload per row, and creates each product through the Admin API so you see exactly what succeeded and what did not.

Prefer to skip the build? Grab the ready-made template and connect it to your store in under ten minutes.

What it does

The workflow reads a list of products from a Google Sheet (or a CSV, with one small swap), then walks through the rows one by one and creates each product in your Shopify store. Here is the flow from the merchant’s point of view:

  1. You fill a Google Sheet with one product per row, using columns like title, price, SKU and vendor.
  2. You click Execute in n8n, or wire the workflow to a schedule so it runs on its own.
  3. n8n reads every row and loops through them at a pace Shopify is happy with.
  4. Each row becomes a real product in your catalog, complete with a variant, price and SKU.
  5. You watch the run finish in the n8n execution log, row by row, with a clear pass or fail on each one.
┌──────────────────────────────────────────────────────────────────┐
│  BULK PRODUCT UPLOAD                                               │
│                                                                   │
│  [Execute] → [Read rows] → [Loop Over Items]                      │
│                                 │  (each row)                      │
│                                 ▼                                  │
│                          [Build payload] → [Create product]       │
│                                 ▲                    │             │
│                                 └──── [Wait 0.6s] ◀──┘             │
│                                 │  (done)                          │
│                                 ▼                                  │
│                          [All products created]                   │
└──────────────────────────────────────────────────────────────────┘
  

Why it beats the default

Shopify already ships a CSV importer, so why build this? Because the native tool trades control for convenience, and a growing store usually needs the control back.

Job Native Shopify CSV import n8n workflow
Column naming Must match Shopify’s exact headers Map any column name you like
Error feedback Vague, often silent skips Per-row pass or fail in the run log
Extra steps None possible Add tagging, alerts or AI copy in the same flow
Data source One CSV upload at a time Live Google Sheet, CSV, Airtable or an API
Cost Free but limited Free and extensible

The real win is that the upload becomes a step you own. Once the rows flow through n8n you can bolt on a Google Gemini node to write descriptions, a tagging step, or a Slack ping when the batch finishes, all without leaving the workflow. For the bigger picture of what else n8n can drive on a store, see the n8n Shopify automation hub.

What you need

  • A Shopify store with a custom app created in the 2026 Shopify Dev Dashboard, giving you an Admin API access token with write_products scope.
  • An n8n instance, cloud or self-hosted, on version 1.0 or newer.
  • A Google account with a Sheet of products, or a CSV file if you prefer.
  • About 30 minutes to build from scratch, or 10 minutes with the template.

If you have not connected Shopify to n8n yet, follow connect Shopify to n8n (2026 method) first. It walks through the Dev Dashboard custom app and the Admin API token this workflow needs. The old admin custom-app screen is gone, so ignore any tutorial that still points there.

The nodes, one by one

The whole flow is seven nodes. Nothing exotic, and no Code node required.

# Node Type Job
1 When clicking Execute Manual Trigger Starts the run on demand
2 Read product rows Google Sheets (v4.7) Pulls every row from the sheet
3 Loop Over Items Split In Batches (v3) Feeds one row at a time
4 Build product payload Edit Fields / Set (v3.4) Maps columns to clean typed fields
5 Create product HTTP Request (v4.2) POSTs the product to the Admin API
6 Wait 0.6s Wait (v1.1) Keeps you under the rate limit
7 All products created No Operation Marks the loop as finished

Build it step by step

1 When clicking Execute (Manual Trigger)

Add a Manual Trigger node. It gives you a button to run the upload on demand while you test. Later you can swap it for a Schedule Trigger if you want the sheet drained automatically every night.

2 Read product rows (Google Sheets)

Add a Google Sheets node, set the operation to Get Row(s), and point it at your document and sheet tab. Connect your Google credential. When it runs, each row comes back as one item. A clean source sheet looks like this:

Title Body Vendor Type Price SKU Tags Status
Maple Cutting Board Solid maple, 18 x 12 in. Northwood Kitchen 39.00 NW-CB-18 kitchen,wood active
Cast Iron Skillet Pre-seasoned, 10 in. Northwood Kitchen 29.00 NW-SK-10 kitchen,iron active
📌

Note: column headers become the field names n8n reads. Keep them simple and consistent, because you will reference them by name in the next node.

3 Loop Over Items (Split In Batches)

Add the Loop Over Items node and set Batch Size to 1. This is the piece that keeps Shopify happy. Instead of firing 200 create calls at once and hitting a rate-limit wall, the loop hands the workflow a single product, waits for it to finish, then serves the next.

💡

Tip: the loop has two outputs. The loop output runs once per row and connects to your build step. The done output fires once at the very end and connects to the final No Operation node.

4 Build product payload (Edit Fields)

Add an Edit Fields (Set) node to translate spreadsheet columns into the exact field names Shopify expects. Create these assignments, each with the matching type:

title            (string)  =  {{ $json.Title }}
body_html        (string)  =  {{ $json.Body }}
vendor           (string)  =  {{ $json.Vendor }}
product_type     (string)  =  {{ $json.Type }}
tags             (string)  =  {{ $json.Tags }}
status           (string)  =  {{ $json.Status }}
price            (string)  =  {{ $json.Price }}
sku              (string)  =  {{ $json.SKU }}
💡

Tip: keep price as a string, not a number. Shopify’s API expects the price as a quoted value like "39.00", and forcing it to a number can drop trailing zeros.

5 Create product (HTTP Request)

Add an HTTP Request node. This is the node that talks to Shopify. Configure it like this:

  1. Method: POST
  2. URL: https://YOUR-STORE.myshopify.com/admin/api/2026-04/products.json
  3. Authentication: Generic Credential Type, then Header Auth. Set the header name to X-Shopify-Access-Token and the value to your Admin API access token.
  4. Send Body: on. Body Content Type: JSON. Specify Body: Using JSON.
  5. Paste this into the JSON body field as an expression:
{
  "product": {
    "title": "{{ $json.title }}",
    "body_html": "{{ $json.body_html }}",
    "vendor": "{{ $json.vendor }}",
    "product_type": "{{ $json.product_type }}",
    "tags": "{{ $json.tags }}",
    "status": "{{ $json.status }}",
    "variants": [
      { "price": "{{ $json.price }}", "sku": "{{ $json.sku }}" }
    ]
  }
}
📌

Note: the response returns the new product, including its id and each variant’s inventory_item_id. Keep that in mind if you plan to set stock levels in a later step.

6 Wait 0.6s (Wait)

Add a Wait node set to 0.6 seconds. Shopify’s REST Admin API allows two requests per second on standard plans, so a pause a little over half a second keeps you safely under the ceiling even with retries. Connect the Wait node back into the Loop Over Items node so the loop moves to the next row.

7 All products created (No Operation)

Connect the loop’s done output to a No Operation node named something clear like All products created. It does nothing functional, but it gives the workflow a tidy end point and a spot to add a Slack or Gmail summary later.

Common mistakes

  • Leaving Batch Size at its default instead of 1, which fires every row at once and trips Shopify’s rate limit with a wave of 429 errors.
  • Wiring the Wait node forward instead of back into the loop, so the workflow processes one product and stops.
  • Expecting stock to appear. The create call ignores inventory quantity in current API versions. Stock is set on the inventory_levels endpoint after the product exists.
  • Using the old admin custom-app token flow. Create the app in the 2026 Dev Dashboard and use the X-Shopify-Access-Token header instead.
  • Sending the body as a raw object or a hand-built string. Use Specify Body: Using JSON so n8n serializes it correctly.
  • Re-running on the same sheet and creating duplicates. Add a Status column and an IF node to skip rows already marked Done.

Cost at realistic volume

This is one of those rare automations that costs essentially nothing to run.

Piece Cost Notes
n8n (self-hosted) $0 Runs on your own server, no per-execution fee
n8n Cloud 1 execution per run A whole batch counts as a single manual execution
Shopify Admin API $0 Included with any paid Shopify plan
Google Sheets $0 Free tier is more than enough

At two requests per second, 500 products upload in roughly five minutes and 5,000 in under an hour. Compared with a paid bulk-import app charging a monthly fee, the running cost here rounds to zero.

Get the ready-to-import template

You now know how every node fits together. If you would rather not wire it by hand, the template drops the whole flow onto your canvas ready to connect.

🚀 Get the Bulk Product Upload template

The guide above is free to follow. If you would rather skip the build, download the ready-to-import workflow, then just add your credentials. Prefer it installed and tuned for you? See our done-for-you services.

Download the template ($12) →

Instant download · Works on n8n Cloud and self-hosted

Frequently asked questions

Can I upload products from a CSV file instead of a Google Sheet?

Yes. Drop the CSV in a folder and swap the Google Sheets node for a Read/Write Files node plus an Extract From File node set to CSV. Every other node stays the same because the rows arrive as the same JSON items the loop expects.

How many products can n8n upload to Shopify at once?

There is no hard cap. The workflow loops one row at a time with a short wait, so 500 products take about five minutes and 5,000 take under an hour. The only real limit is Shopify’s two requests per second on the REST Admin API, which the Wait node respects.

Does this set inventory quantity for each product?

The create call sets price, SKU and product details, but stock levels live on a separate inventory endpoint in current Shopify API versions. Add a follow-up HTTP Request to inventory_levels/set once each product returns its inventory_item_id, or set stock by hand for a small catalog.

Will re-running the workflow create duplicate products?

Yes, because products create always makes a new product. To make it safe to re-run, add an IF node that checks a Status column in your sheet and skips rows already marked Done, or search by SKU first and update instead of create.

Do I need a paid n8n plan for this?

No. Self-hosted n8n is free and runs this with no per-execution cost. On n8n Cloud the whole job counts as one execution when triggered manually, so even a large upload stays comfortably inside the Starter plan limits.

Related guides

n8n
Shopify
Google Sheets
bulk upload
automation

How to Sync Inventory Between Two Shopify Stores With n8n

Sync inventory between two Shopify stores workflow in n8n









If you run two Shopify stores that sell the same products, a wholesale storefront and a retail one, or a US and an EU store, you already know the pain: a unit sells on one store and the other keeps advertising stock it no longer has. This guide shows you how to sync inventory between two Shopify stores with n8n, so every sale or manual stock edit on one store updates the matching product on the other in seconds. You will build a free, real-time workflow that catches each inventory change, matches products by SKU, and writes the new quantity to your second store. No paid multi-store app, no nightly spreadsheet reconciliation.

Prefer to skip the setup? Have us build and install it for you → and be live in a day.

What it does

The workflow listens for stock changes on your primary store and mirrors them onto your second store. When a variant’s quantity changes, Shopify sends an inventory_levels/update webhook to n8n. The workflow reads which item changed, looks up that item’s SKU, then searches the second store for the variant carrying the same SKU and sets its available quantity to match.

Here is the full path a single change travels:

┌──────────────────────────────────────────────────────────────────┐
│  SYNC INVENTORY BETWEEN TWO SHOPIFY STORES                         │
│                                                                    │
│  Store A sells 1 unit                                              │
│        │                                                           │
│        ▼                                                           │
│  [Webhook: inventory_levels/update]                               │
│        │  { inventory_item_id, available }                        │
│        ▼                                                           │
│  [Get SKU from Store A]  ──►  sku: "TSHIRT-BLK-M"                  │
│        │                                                           │
│        ▼                                                           │
│  [Has SKU?] ──► yes                                               │
│        │                                                           │
│        ▼                                                           │
│  [Find variant in Store B by SKU]  ──►  inventoryItem id          │
│        │                                                           │
│        ▼                                                           │
│  [Variant exists?] ──► yes                                        │
│        │                                                           │
│        ▼                                                           │
│  [Set quantity in Store B]  ──►  available = new stock            │
│        │                                                           │
│        ▼                                                           │
│  [Log to Slack]                                                   │
└──────────────────────────────────────────────────────────────────┘
  

Why it beats the default

The two usual answers to multi-store stock are a paid sync app or a manual export. Dedicated apps such as the popular inventory-sync tools charge a monthly fee that climbs with your product count and often bill per synced SKU. A CSV export and re-import is free but slow, error-prone, and always out of date the moment a customer checks out.

This n8n build gives you a middle path that most merchants never realize is available:

  • Real time. Stock moves within seconds of the sale, not on a nightly batch.
  • Flat cost. You pay for one n8n instance regardless of whether you sync 50 SKUs or 5,000.
  • SKU based, so it works even when the two stores have different product IDs, handles, and themes.
  • Fully yours. The logic sits in your own workflow, so you can add rules, buffers, or a Slack log without waiting on an app’s feature roadmap.

It slots neatly into a wider stack too. If you already run any of our other n8n Shopify automations, this uses the same connection and the same Admin API you have already set up.

What you need

  • Two Shopify stores where you are the admin on both.
  • An n8n instance, either n8n Cloud or a self-hosted install.
  • An Admin API access token on each store, created with the 2026 Shopify Dev Dashboard method. If you have not connected Shopify to n8n yet, follow how to connect Shopify to n8n in 2026 first, once per store.
  • The read_inventory and write_inventory access scopes on both tokens, plus read_products.
  • Matching SKUs. Every variant you want to keep in sync must carry the identical SKU string on both stores. This is the join key, so it has to be exact.
  • The location ID of the destination store, which you will drop into a config node once.

All API calls in this guide use Admin API version 2026-04. Build time is around 40 minutes from scratch, or a few minutes if you import the ready-made template.

Node-by-node list

Eight nodes carry a change from the first store to the second. Here is the whole map before we build it.

# Node Type Job
1 Store A inventory webhook Webhook Receives inventory_levels/update from the source store
2 Config Set / Edit Fields Holds the destination store domain and location ID
3 Get SKU from Store A HTTP Request Looks up the SKU for the changed inventory item
4 Has SKU? Filter Skips items with no SKU set
5 Find variant in Store B HTTP Request GraphQL search for the matching SKU in the destination store
6 Variant exists? Filter Continues only if the destination store has that SKU
7 Set quantity in Store B HTTP Request GraphQL mutation that writes the new available quantity
8 Log to Slack Slack Posts a one-line record of each sync

Step-by-step build

1 Store A inventory webhook (Webhook)

This node is the entry point. Add a Webhook node, set the method to POST, and give it a path like store-a-inventory. Copy the production URL n8n shows you. Then register it on your source store by creating a webhook subscription for the inventory_levels/update topic pointing at that URL. When a variant’s stock changes, Shopify posts a small payload here.

{
  "inventory_item_id": 44827362891,
  "location_id": 71034829,
  "available": 7,
  "updated_at": "2026-07-06T14:32:08-05:00"
}
💡

Register the webhook once with a single Admin API call, or use the webhook screen in your store admin. Point it only at the store you treat as the source of truth for that change.

2 Config (Set / Edit Fields)

Add a Set node right after the webhook so the store-specific values live in one place. In an Edit Fields node, add three string assignments so future edits never touch the request nodes.

storeADomain     = "acme-wholesale.myshopify.com"
storeBDomain     = "acme-retail.myshopify.com"
storeBLocationId = "gid://shopify/Location/62918342"
💡

The destination location ID is a GraphQL global ID, not the plain number. You can read it once by running a locations query against Store B, then paste it here.

3 Get SKU from Store A (HTTP Request)

The webhook tells you which inventory item changed but not its SKU, so fetch it. Use an HTTP Request node with a GET to the source store’s inventory item endpoint.

GET https://{{ $('Config').item.json.storeADomain }}/admin/api/2026-04/inventory_items/{{ $('Store A inventory webhook').item.json.body.inventory_item_id }}.json

Authenticate with a generic header credential that sends X-Shopify-Access-Token set to Store A’s token. The response carries the SKU you will match on.

{
  "inventory_item": {
    "id": 44827362891,
    "sku": "TSHIRT-BLK-M",
    "tracked": true
  }
}

4 Has SKU? (Filter)

Some items have no SKU, and those cannot be matched. Add a Filter node with one condition: {{ $json.inventory_item.sku }} is not empty. Only rows that pass move on, which keeps blank-SKU noise out of the rest of the flow.

5 Find variant in Store B (HTTP Request)

Now search the destination store for the variant that shares this SKU. Use an HTTP Request node with a POST to Store B’s GraphQL endpoint, https://{{ $('Config').item.json.storeBDomain }}/admin/api/2026-04/graphql.json, authenticated with Store B’s token. Set the body type to JSON and send this JSON body.

{
  "query": "query($q: String!) { productVariants(first: 1, query: $q) { edges { node { id sku inventoryItem { id } } } } }",
  "variables": {
    "q": "sku:{{ $('Get SKU from Store A').item.json.inventory_item.sku }}"
  }
}

A match returns the destination variant’s inventory item ID, which is the target for the write.

{
  "data": {
    "productVariants": {
      "edges": [
        { "node": { "id": "gid://shopify/ProductVariant/70051839",
                    "sku": "TSHIRT-BLK-M",
                    "inventoryItem": { "id": "gid://shopify/InventoryItem/58120394" } } }
      ]
    }
  }
}
💡

Because the search uses the SKU and not a product ID, the two stores can have completely different products, handles, and themes. Only the SKU has to line up.

6 Variant exists? (Filter)

Guard against SKUs that live in one store but not the other. Add a Filter node that continues only when the edges array has a result: {{ $json.data.productVariants.edges[0]?.node?.inventoryItem?.id }} is not empty. If nothing matched, the item is skipped cleanly with no error.

7 Set quantity in Store B (HTTP Request)

This is the write. Use an HTTP Request node with a POST to the same Store B GraphQL endpoint, JSON body type, and the inventorySetQuantities mutation. It sets the destination variant’s available count to whatever Store A now reports.

{
  "query": "mutation($input: InventorySetQuantitiesInput!) { inventorySetQuantities(input: $input) { inventoryAdjustmentGroup { reason } userErrors { field message } } }",
  "variables": {
    "input": {
      "name": "available",
      "reason": "correction",
      "ignoreCompareQuantity": true,
      "quantities": [
        {
          "inventoryItemId": "{{ $('Find variant in Store B').item.json.data.productVariants.edges[0].node.inventoryItem.id }}",
          "locationId": "{{ $('Config').item.json.storeBLocationId }}",
          "quantity": {{ $('Store A inventory webhook').item.json.body.available }}
        }
      ]
    }
  }
}
📌

Keep ignoreCompareQuantity set to true so the mutation overwrites the destination value rather than rejecting the change when the two counts differ. Always read the userErrors field in testing to catch scope or location problems early.

8 Log to Slack (Slack)

Finish with a Slack node so you have a running record and catch failures fast. Set the resource to message, pick your channel with the channel selector, and post a short line.

Synced {{ $('Get SKU from Store A').item.json.inventory_item.sku }} to {{ $('Store A inventory webhook').item.json.body.available }} units on the retail store.

Prefer email or a spreadsheet log instead? Swap this node for a Gmail node or a Google Sheets append and keep the rest of the flow untouched.

Common mistakes

  • SKUs that do not match exactly. A trailing space, a different case, or a missing suffix means the lookup finds nothing. Standardize SKUs across both stores before you switch this on.
  • Using the numeric location ID where a GraphQL global ID is required. The inventorySetQuantities mutation wants gid://shopify/Location/…, not the bare number.
  • Forgetting write_inventory scope on the destination token. The read side will work and the write will fail with a permissions error in userErrors.
  • Untracked inventory. If a variant is not set to track quantity in the destination store, Shopify has nothing to update. Enable inventory tracking on those variants first.
  • Building a two-way sync without a loop guard. If both stores write to each other, one change can bounce back and forth. Skip the write when the incoming quantity already equals the destination count.
  • Hitting the Admin API rate limit during a bulk edit. If you re-sync hundreds of items at once, add a short wait or batch the calls so you stay under the limit.

Cost at realistic volume

The moving parts here are the two Shopify Admin APIs, which cost nothing beyond your existing plans, and the n8n instance that runs the logic. Say a busy pair of stores sees 500 inventory changes a day. Each change fires three API calls, roughly 1,500 calls a day, well within Shopify’s normal limits.

Option Monthly cost Notes
Self-hosted n8n $0 to $10 A small VPS handles this volume with room to spare
n8n Cloud Starter Flat plan fee 500 changes a day is around 15,000 executions a month, inside the entry tier
Shopify Admin API $0 Included on every plan
Typical paid sync app $20 to $100+ Often scales with SKU count, which this build does not

There is no AI model in this workflow and no per-message billing, so the cost stays flat whether you sync 50 SKUs or 5,000. The savings compared with a per-SKU sync app grow as your catalog does.

🚀 Get the two-store inventory sync, done for you

The guide above is free to follow. If you would rather skip the build, download the ready-to-import workflow, then just add your credentials. Prefer it installed and tuned for you? See our done-for-you services.

Download the template ($19) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

Can this workflow sync inventory in both directions?

Yes. Duplicate the workflow, point the second copy at the opposite store, and register the inventory webhook on that store too. To stop an endless update loop, add a small guard that skips a write when the incoming quantity already matches the target store’s stock.

What matches a product across the two stores?

The SKU. Every variant you want to keep in sync must share the exact same SKU string on both stores. The workflow reads the SKU from the store that changed, then searches the second store for the variant that carries the same SKU.

How fast does the second store update?

Almost immediately. Shopify fires the inventory webhook within a few seconds of a sale or manual edit, and n8n processes it in one pass. In practice the second store reflects the new quantity within five to ten seconds of the original change.

Do I need a paid Shopify plan or a multi-store app?

No. This runs on any Shopify plan using the built-in Admin API. You skip recurring multi-store sync apps entirely. Your only cost is the n8n instance, which is free when self-hosted and a flat monthly fee on n8n Cloud.

What happens if a SKU exists in one store but not the other?

The lookup returns no match and the workflow stops for that item without error. A filter node checks that the second store returned a variant before it tries to write. Nothing breaks, and the change is simply skipped until both stores share the SKU.

Related guides

n8n
Shopify
inventory sync
multi-store
automation

Shopify shipping confirmation email with tracking in n8n

A Shopify shipping confirmation email with tracking in n8n fires the moment an order is fulfilled, pulling the tracking number and carrier straight from the fulfillment and sending a branded message your customer actually opens. This guide builds the whole workflow: a fulfillment trigger, an order lookup for the customer email, a clean HTML email through Gmail, and a shipment log in Google Sheets. No paid app, no monthly fee, full control over the wording.

What it does

When you mark an order as fulfilled in Shopify and attach a tracking number, Shopify sends its own shipping notification. It works, but it looks like every other Shopify store and you cannot change much about it. This workflow replaces that generic message with one you own end to end.

The flow listens for the fulfillments/create event. As soon as a shipment is created, n8n reads the tracking number, tracking URL, and carrier from the fulfillment, looks up the matching order to get the customer name and email, then sends a branded HTML email through your own Gmail account. Every shipment also gets logged to a Google Sheet so you have a running record of what shipped, when, and with which carrier.

Because the whole thing runs in n8n, you decide the subject line, the tone, the layout, and what extra content rides along, such as a review request link or a discount for the next order.

Why it beats the default

Shopify’s built-in shipping confirmation is fine for a hobby store. Once you care about brand and repeat purchases, its limits show:

  • The template lives inside Shopify’s notification editor with rigid Liquid blocks. Real layout changes are painful.
  • You cannot easily branch on order value, product type, or destination country to change the message.
  • You get no side record of shipments unless you pay for an app or export by hand.
  • Adding a post-purchase upsell, a review ask, or a support link means fighting the template.

With n8n you write plain HTML, insert any field you want, and add steps such as a Google Sheets log or a Slack ping to your ops channel without touching Shopify’s editor. You also keep the sending under your own domain through Gmail, which many small stores prefer over Shopify’s shared sending infrastructure.

What you need

  • A Shopify store where you can create an app and read orders and fulfillments.
  • An n8n instance, either self-hosted (free) or n8n Cloud.
  • A Gmail or Google Workspace account for sending, connected to n8n with the Gmail OAuth2 credential.
  • A Google Sheet with a header row for the shipment log (optional but recommended).
  • Your Shopify connection set up the current way. Follow how to connect Shopify to n8n in 2026 first. It uses the Shopify Dev Dashboard custom-app method and an Admin API access token, which is the only method that still works. The old in-admin custom-app screen was removed.

All API calls in this build target Admin API version 2026-04.

Node-by-node list

  • Shopify Trigger — subscribes to the fulfillments/create topic. Fires once per shipment, carrying the tracking number, tracking URL, carrier, line items, and the parent order_id.
  • IF (has tracking number) — checks that tracking_number is not empty. Fulfillments created without a tracking number take the false branch and stop, so customers never get an email with a blank tracking link.
  • HTTP Request (get order) — a GET to /admin/api/2026-04/orders/{order_id}.json to read the order name, the customer email, and the customer first name. The fulfillment payload alone is not a reliable source for the customer email, so we fetch the order.
  • Edit Fields (build email data) — normalizes everything into clean fields: recipient email, first name, order name, tracking number, tracking URL, and carrier. This keeps the Gmail node readable.
  • Gmail (send confirmation) — sends the branded HTML shipping confirmation to the customer.
  • Google Sheets (append log) — writes one row per shipment: date, order name, email, carrier, tracking number.

Step-by-step build

  1. Create the Shopify Trigger. Add a Shopify Trigger node, select your Shopify credential from the connect guide, and set the topic to fulfillments/create. Save and activate the workflow later so n8n registers the webhook with Shopify.
  2. Add the IF gate. Connect an IF node. Add one condition: value 1 = {{ $json.tracking_number }}, operator is not empty. Wire only the true output onward. This drops fulfillments that carry no tracking, such as digital or manual ones.
  3. Look up the order. Add an HTTP Request node. Method GET, URL https://YOUR-STORE.myshopify.com/admin/api/2026-04/orders/{{ $json.order_id }}.json. Under Query Parameters add fields = name,email,customer to keep the response small. For authentication use your Shopify header-auth credential that sends X-Shopify-Access-Token. This returns the order name (like #1002), the order email, and the customer object.
  4. Normalize the fields. Add an Edit Fields node (Set). Add string assignments:
    • to_email = {{ $json.email }}
    • first_name = {{ $json.customer.first_name }}
    • order_name = {{ $json.name }}
    • tracking_number = {{ $('Shopify Trigger').item.json.tracking_number }}
    • tracking_url = {{ $('Shopify Trigger').item.json.tracking_url }}
    • carrier = {{ $('Shopify Trigger').item.json.tracking_company }}

    The tracking values come from the trigger node, the customer values from the HTTP Request. Referencing the trigger node by name keeps them intact.

  5. Compose and send the Gmail message. Add a Gmail node, resource Message, operation Send. Set To to {{ $json.to_email }}. Set the subject to Your order {{ $json.order_name }} has shipped. In Message, switch to HTML and paste a short template, for example:
    Hi {{ $json.first_name }},
    
    Good news, order {{ $json.order_name }} is on its way.
    
    Carrier: {{ $json.carrier }}
    Tracking number: {{ $json.tracking_number }}
    
    Track it here: {{ $json.tracking_url }}
    
    Thanks for shopping with us.

    Wrap it in your own HTML and inline styles for a branded look. Keep the tracking link visible as text too, so it survives email clients that strip buttons.

  6. Log the shipment. Add a Google Sheets node, operation Append. Point it at your log sheet and map the columns to date, order_name, to_email, carrier, and tracking_number. Use {{ $now.toISO() }} for the date.
  7. Activate and test. Turn the workflow on. In Shopify, fulfill a test order and add any tracking number. Within seconds the email should land and a new row should appear in your sheet. Check the IF branch by fulfilling one order without a tracking number, it should send nothing.

Common mistakes

  • Reading tracking from the order instead of the fulfillment. The tracking number lives on the fulfillment, which is the trigger payload. Pull it from the Shopify Trigger node, not the HTTP Request response.
  • Skipping the IF gate. Manual and digital fulfillments often have no tracking. Without the gate you send emails with an empty tracking link and confuse customers.
  • Using an outdated API version. Old version strings return deprecation warnings or drop fields. Keep the URL on 2026-04.
  • Wrong Gmail scope. If sending fails, reconnect the Gmail credential and make sure the send scope is granted. The Google account you authorize is the one the email comes from.
  • Firing on the wrong topic. Use fulfillments/create, not orders/fulfilled or orders/updated. Only the fulfillment event carries tracking cleanly.
  • Not activating the workflow. Shopify only registers the webhook when the workflow is active. A saved-but-inactive workflow never receives events.

Cost at realistic volume

This build is close to free at small and mid volume:

  • Shopify: no extra cost, apps and webhooks are included in every plan.
  • n8n: self-hosted is free on your own server. n8n Cloud starts around 20 to 24 dollars a month if you prefer managed hosting, and one email plus one log per shipment is a light load.
  • Gmail: free Gmail allows about 500 sends a day, Google Workspace about 2000. A store shipping 20 to 50 orders a day sits well inside the free limit.
  • Google Sheets: free.

At 30 shipments a day, that is roughly 900 emails and 900 log rows a month for zero marginal cost on a self-hosted setup. Compare that to shipping-notification apps that charge a monthly fee for the same job with less control.

Ready-to-import template

Want this built for you instead of wiring it by hand? We package tested n8n workflows and set them up on your store. See the done-for-you automation services to get this shipping confirmation flow, plus the Google Sheets log and any branding, installed and running on your account.

FAQ

Does this replace Shopify’s own shipping email?

It runs alongside it. Many stores turn off Shopify’s default shipping confirmation in the notification settings so customers get only your branded n8n version. If you leave both on, the customer receives two emails, so pick one to keep.

Where does the tracking number come from?

Straight from the fulfillment that Shopify creates when you ship an order. The fulfillments/create webhook carries the tracking number, tracking URL, and carrier, so n8n reads them directly without any extra lookup or third-party app.

Can I send from my store domain instead of Gmail?

Yes. Swap the Gmail node for an SMTP node and point it at your provider, or set up Gmail send-as with your domain. The rest of the workflow stays the same. Gmail is used here because it is the fastest free option to get running.

What if an order ships in two packages?

Shopify creates a separate fulfillment for each package, so the workflow fires once per shipment. The customer gets one email per tracking number, which is usually what you want for split shipments. Add an IF branch if you need different wording for partials.

Do I need to know how to code?

No. Every step uses standard n8n nodes with fields you fill in. The only text you write is the email body, which is plain HTML you can copy from the example above and adjust to match your brand and tone.

Related guides







Best n8n AI Agent Templates for Shopify (2026): Tested and Free to Run

Searching for n8n AI agent templates usually ends one of two ways: a GitHub dump of untested JSON that fails on import, or a demo workflow that needs a paid OpenAI key before it does anything. This list is the opposite. Every template here is import-tested against n8n 2.6, built for a real Shopify job, and runs on Google Gemini’s free tier, so there is no per-message bill waiting for you.

What makes a template an AI agent, not just a workflow

A normal n8n workflow follows a fixed path: trigger, transform, output. An AI agent template is built around the AI Agent node, which gets a goal, a chat model, and sometimes memory and tools, then decides what to do with each input. That difference matters in a store: an agent can read a messy order and decide which tags apply, or read a half-written product entry and decide what a good description looks like. The wiring is also different (models and tools connect to the agent’s sub-inputs, not the main flow), which is exactly where hand-rolled imports usually break.

1. Shopify AI Product Description Agent

The job: every product you create without a description gets one written automatically. The Shopify trigger fires on product creation, a guard checks that the description is actually empty, the agent writes structured SEO copy in your brand voice on Gemini, and the result is saved straight back to the product.

Cost to run: free at typical store volume on Gemini’s free tier. Get it: Shopify AI Product Description Agent ($14, one time). Prefer to wire it yourself? The full build is in the product description generator guide.

2. Shopify AI Customer Support Agent

The job: a chat widget for your storefront that answers shopper questions around the clock. It uses the Chat Trigger, an AI Agent with session memory so follow-up questions work, and Gemini as the reasoning model. You paste one embed snippet into your theme and it is live.

Cost to run: free, there is no per-conversation fee because Gemini’s free tier covers normal support volume. Get it: Shopify AI Customer Support Agent ($19, one time). The do-it-yourself version, with the order lookup and email handoff explained step by step, is in the n8n AI chatbot guide.

3. Shopify AI Order Tagging Agent

The job: every new order gets tagged the moment it lands. The agent reads the order (items, value, customer, shipping) and applies consistent tags like VIP, first-time, or high-value, so your fulfillment filters and reports stay clean without anyone touching the admin.

Cost to run: free on Gemini’s free tier, even on busy days. Get it: Shopify AI Order Tagging Agent ($14, one time). Background reading: how Shopify order tagging automation works.

The rest of the collection: twelve agents and counting

The three above are the flagships, but the full library now runs to twelve Shopify AI agents, each built and import-tested the same way and each running free on Gemini. Here is the rest of the line, grouped by the job it does.

Order operations

  • Product Tagging Agent: every new product gets clean, consistent tags the moment it is created, so your collections and filters build themselves.
  • Order Risk Flagger: reads each new order and adds a risk tag when something looks off, so you can review before you ship.
  • Draft Order Quote Agent: turns a draft order into a polished quote email with a one-click pay link, built for wholesale and made-to-order sales.

Customer emails on autopilot

Team order alerts

  • Order Alert to Telegram: a short, smart summary of every new order in your Telegram, with a flag when one is worth a second look.
  • Order Alert to Slack: the same order summary posted straight to your team Slack channel, no raw field dumps.

Free starter templates to test your setup

If you want to prove the plumbing before running an agent, start with the free ones. The Shopify order and low stock alerts for Telegram are free importable JSONs. There are also full free build guides for the orders to Google Sheets log and the new customer welcome email. Same connection, same credentials, so everything you set up carries over to the agents.

Before you import: the 2026 checklist

  • An n8n instance on a recent version (everything here is import-tested against n8n 2.6).
  • A free Gemini API key from Google AI Studio. No OpenAI subscription needed.
  • Your store connected the current way. Shopify removed admin custom apps in January 2026, so follow the 2026 Shopify to n8n connection guide first. Ten minutes, once, and every template reuses it.

Browse the full AI agents collection, or start with the broader n8n Shopify automation guide if you are mapping out your whole stack. And to see whether all this AI work is landing, track your store visibility in AI answers.

Frequently asked questions

Are n8n AI agent templates free to run?

These are. They use Google Gemini’s free tier, and the API key from Google AI Studio costs nothing, so there is no per-message or per-conversation fee at typical store volume. Self-hosted n8n adds no cost either.

Do I need an OpenAI key?

No. Every agent on this list runs on Gemini by default. If you prefer OpenAI or Claude, the chat model is one swappable node and the rest of the agent stays the same.

How do I know a template will actually import?

Every template here is validated by importing it into a clean n8n 2.6 instance before it is published. That catches the classic failures in scraped template collections: renamed node fields, wrong sub-input connections, and credentials that reference nothing.

Do they work on n8n Cloud and self-hosted?

Yes, both. The Shopify triggers, the AI Agent node, and the Gemini model work the same on n8n Cloud and on a self-hosted instance. Self-hosted keeps the running cost at zero.

What changed with Shopify in 2026?

Shopify removed custom app creation from the store admin in January 2026. New connections are made through the Dev Dashboard with OAuth, and tokens now expire every 24 hours. Our connection guide walks through the whole new flow with screenshots.

Shopify Weekly Best Sellers Report With n8n

Shopify weekly best sellers report workflow in n8n










A Shopify weekly best sellers report in n8n pulls last week’s orders, ranks your products by units sold, writes the top ten to a Google Sheet, and emails you the list every Monday morning. It runs on any paid Shopify plan with a free n8n instance, needs no analytics add-on, and takes about 35 minutes to build. Below is the full node-by-node build, the exact Code node logic, common mistakes, and real running costs.

What it does

Every Monday at 8am, this workflow asks Shopify for all orders from the previous seven days, adds up how many units each product sold, and ranks them. The top ten land in a running Google Sheet so you build a week-over-week history, and the same list arrives in your inbox as a clean HTML table. You open your email and immediately know what moved, without logging into the admin or clicking through the Analytics tab.

It is a small piece of the larger picture covered in our n8n Shopify automation guide: instead of pulling reports by hand, you let a scheduled workflow deliver the numbers you actually act on. Best-seller data drives restock decisions, ad budget, and which products deserve a homepage slot, so having it pushed to you beats remembering to go fetch it.

Why it beats the default

Shopify already shows best sellers under Analytics, so why build this? Three reasons.

  • It comes to you. A report you have to remember to open is a report you stop reading after week two. Email plus a Sheet means the numbers show up whether or not you log in.
  • It builds history you own. The native dashboard is a live view. This workflow appends every week to a Google Sheet, so after a couple of months you can chart trends, spot a product fading, and pivot a table however you like.
  • It is yours to shape. Want the top 20, a revenue ranking, only a single collection, or the report sent to three people? Those are one-line edits here. The built-in dashboard gives you none of that control.

The native report is fine for a quick glance. This is for the owner who wants the data delivered, logged, and reshaped on their terms.

What you need

  • An n8n instance, cloud or self-hosted. A free self-hosted setup is enough.
  • A Shopify store on any paid plan, plus an app access token with read_orders and read_products scopes.
  • A Google account for Google Sheets, connected to n8n by OAuth2.
  • A Gmail account for the email, also connected by OAuth2.
  • A blank Google Sheet with a header row: Week Of, Rank, Product, Variant, Units Sold, Revenue.

Build time: about 35 minutes from scratch, or under 10 minutes with the ready-made template.

Node-by-node list

Six nodes, one clean line with a short branch at the end so the Sheet gets every row while the email gets one message.

┌───────────────────────────────────────────────────────────────┐
│  SHOPIFY WEEKLY BEST SELLERS REPORT                            │
│                                                               │
│  [Schedule Trigger]  weekly, Mon 08:00                        │
│         ↓                                                     │
│  [Shopify: Get Many Orders]  created in last 7 days           │
│         ↓                                                     │
│  [Code: Rank Best Sellers]  aggregate + sort + top 10         │
│         ├──────────────→ [Google Sheets: Append]  all rows    │
│         ↓                                                     │
│  [Limit: Keep 1]  →  [Gmail: Send Report]  one email          │
└───────────────────────────────────────────────────────────────┘
  
  1. Schedule Trigger (n8n-nodes-base.scheduleTrigger) — fires weekly, Monday 08:00.
  2. Shopify (n8n-nodes-base.shopify) — Get Many orders created in the last 7 days.
  3. Code (n8n-nodes-base.code) — ranks products by units sold and builds the report HTML.
  4. Google Sheets (n8n-nodes-base.googleSheets) — appends one row per top product.
  5. Limit (n8n-nodes-base.limit) — reduces the ranked list to a single item for the email.
  6. Gmail (n8n-nodes-base.gmail) — sends the HTML best-sellers report.

Step-by-step build

1 Schedule Trigger

Add a Schedule Trigger. Set the interval field to Weeks, trigger day to Monday, and trigger hour to 8. This gives you a fresh report at the start of every week, covering the seven days that just closed.

💡

Tip: Set your n8n instance timezone under Settings so 08:00 means 08:00 in your store’s time, not UTC.

2 Shopify — Get Many Orders

Add a Shopify node. Set resource to Order and operation to Get Many. Turn on Return All. Under Options, add Created At Min and set it to an expression that resolves to seven days ago:

={{ $now.minus({ days: 7 }).toISO() }}

Also add the Status option and set it to Any so you capture every order, then let the Code node decide what counts. Each order returned carries a line_items array, which is what we rank on.

📌

Create your Shopify credential from an app in the Shopify Developer Dashboard (dev.shopify.com). Give it read_orders and read_products Admin API scopes.

3 Code — Rank Best Sellers

Add a Code node (Run Once for All Items, JavaScript). This is the heart of the workflow. It flattens every line item, sums units and revenue per product, sorts, keeps the top ten, and builds one HTML report attached to every row.

// Aggregate best-selling products from the past week's orders
const stats = {};

for (const item of $input.all()) {
  const order = item.json;
  if (order.cancelled_at) continue; // skip cancelled orders
  for (const li of order.line_items || []) {
    const key = li.product_id + '|' + (li.variant_title || '');
    if (!stats[key]) {
      stats[key] = { product: li.title, variant: li.variant_title || '—', unitsSold: 0, revenue: 0 };
    }
    stats[key].unitsSold += li.quantity;
    stats[key].revenue  += li.quantity * parseFloat(li.price);
  }
}

const weekOf = $now.minus({ days: 7 }).toFormat('LLL d')
  + ' – ' + $now.toFormat('LLL d, yyyy');

const ranked = Object.values(stats)
  .sort((a, b) => b.unitsSold - a.unitsSold)
  .slice(0, 10)
  .map((p, i) => ({ rank: i + 1, weekOf, ...p, revenue: Math.round(p.revenue * 100) / 100 }));

const rows = ranked.map(p =>
  `<tr><td>${p.rank}</td><td>${p.product}</td><td>${p.variant}</td>`
  + `<td>${p.unitsSold}</td><td>$${p.revenue.toFixed(2)}</td></tr>`
).join('');

const reportHtml = `<h2>Best sellers — ${weekOf}</h2>`
  + `<table border="1" cellpadding="6" cellspacing="0">`
  + `<tr><th>#</th><th>Product</th><th>Variant</th><th>Units</th><th>Revenue</th></tr>`
  + `${rows}</table>`;

return ranked.map(p => ({ json: { ...p, reportHtml } }));

After this node runs on a sample week, one output item looks like this:

{
  "rank": 1,
  "weekOf": "Jun 23 – Jun 30, 2026",
  "product": "Cedar Wax Melts",
  "variant": "Vanilla Bourbon",
  "unitsSold": 214,
  "revenue": 2461.50,
  "reportHtml": "<h2>Best sellers — Jun 23 ... </table>"
}
💡

Tip: To rank by money instead of units, change the sort to b.revenue - a.revenue. To show 20 products, change .slice(0, 10) to .slice(0, 20).

4 Google Sheets — Append

Add a Google Sheets node. Set operation to Append, pick your document and sheet, and set mapping to Map Each Column Manually. Map the columns to the fields from the Code node:

Sheet column Value
Week Of ={{ $json.weekOf }}
Rank ={{ $json.rank }}
Product ={{ $json.product }}
Variant ={{ $json.variant }}
Units Sold ={{ $json.unitsSold }}
Revenue ={{ $json.revenue }}

Because the Code node returns ten items, this node appends ten rows in one run, one per top product. Over the weeks the sheet becomes your best-seller history.

5 Limit — Keep 1

Connect a second line from the Code node into a Limit node and set Max Items to 1. The email should be a single message, not ten. Because every ranked item carries the identical reportHtml, keeping just the first item still gives you the whole table.

📌

Draw the Limit connection from the Code node, not from Google Sheets. Both branches then read the full ranked data directly.

6 Gmail — Send Report

Add a Gmail node, operation Send. Set the recipient to your address, the subject to an expression, and the message to the report HTML with the email type set to HTML:

  • Subject: =Best sellers this week {{ $json.weekOf }}
  • Message: ={{ $json.reportHtml }}

Save the workflow and toggle it Active. Next Monday at 8am the report lands on its own.

Common mistakes

  • Wiring Limit after Google Sheets. The append node’s output does not carry the report HTML, so the email comes out empty. Branch Limit straight off the Code node instead.
  • Leaving the timezone on UTC. The schedule then fires at the wrong local hour and Created At Min covers a shifted window. Set the instance timezone once.
  • Forgetting read_products scope. Orders return but some fields are thin. Grant both order and product read scopes on the app.
  • Counting cancelled orders. Without the cancelled_at skip, a large cancelled order can crown a false best seller. The Code node handles this, so do not remove that line.
  • Not turning on Return All. With it off you only get the first page of orders and undercount every product in a busy week.

Cost at realistic volume

This workflow uses no paid AI and no metered messaging, so the running cost is close to zero.

Component Cost Notes
n8n (self-hosted) $0 One weekly execution; well inside any free setup.
n8n Cloud From ~$20/mo Four executions a month barely touches the quota.
Shopify Admin API $0 Included on every paid Shopify plan.
Google Sheets $0 Free with any Google account.
Gmail $0 Four emails a month is nothing.

Even at thousands of weekly orders the only variable is how long the Shopify pull takes, which costs you time, not money. Compare that to a paid analytics app charging a monthly fee for a view you can generate yourself.

Get the Shopify Best Sellers Report, done for you

The guide above is free to follow. If you would rather skip the build, download the ready-to-import workflow, then just add your credentials. Prefer it installed and tuned for you? See our done-for-you services.

Download the template ($12) →

Instant download · Works on n8n Cloud and self-hosted

Frequently asked questions

Does this Shopify best sellers report work on the Basic plan?

Yes. The workflow reads orders through the standard Shopify Admin API, which every paid Shopify plan exposes. You only need an app access token with read_orders and read_products scopes. There is no dependency on Shopify Plus or any paid analytics add-on.

Can I rank by revenue instead of units sold?

Yes. In the Code node, change the sort comparator from b.unitsSold - a.unitsSold to b.revenue - a.revenue. The report already tracks both fields, so revenue ranking is a one-line change and needs no new nodes or extra data pull.

How many orders can the workflow handle in one run?

The Shopify node paginates automatically when Return All is on, so it will pull thousands of weekly orders. For very high volume stores, request fewer fields and raise your n8n execution timeout so a large pull has room to finish cleanly.

Will cancelled or refunded orders skew the ranking?

The Code node skips any order with a cancelled_at value, so cancellations are excluded. Refunds are not deducted line by line in this version, but you can subtract refund quantities inside the same loop if your store refunds often enough to matter.

Can I send the report to Slack or Telegram instead of email?

Yes. Swap the Gmail node for a Slack or Telegram node and pass the same data. Telegram wants Markdown or HTML parse mode, so send a short text summary rather than the full HTML table for the cleanest result in a chat window.

Related guides

n8n
Shopify
Google Sheets
Gmail
automation

Shopify Failed Payment Recovery Email With n8n

Shopify failed payment recovery email workflow in n8n










This guide builds a Shopify failed payment recovery email in n8n that watches for orders stuck on payment pending, waits a couple of hours, then emails the customer a friendly nudge to finish paying. It uses a Schedule Trigger, the Shopify Admin API, a Gmail send, and an order tag so nobody gets chased twice. You can have it live in about 35 minutes, and it quietly claws back revenue that would otherwise just sit there unpaid.

Prefer to skip the setup? Grab the ready-made template and be running in under 10 minutes.

What it does

Not every Shopify order gets paid the moment it is placed. If your store accepts bank transfers, cash on delivery, manual payment methods, or you invoice bigger customers through draft orders, those orders land in your admin marked Payment pending. The customer meant to pay, then got distracted, and now that order is just sitting there. Multiply it across a month and it adds up to real money you already earned but never collected.

This workflow runs on a timer. Every hour it asks Shopify for open orders that are still pending, keeps only the ones that have been waiting a sensible amount of time, and sends each customer a short reminder email with a link back to their order so they can complete payment. Once an order has been contacted, the workflow tags it inside Shopify so the next run skips it. No spreadsheets to babysit, no double emails, no manual chasing.

It sits naturally alongside the rest of an automated store. If you are building out a stack of these, the n8n Shopify automation hub collects the related order and customer workflows in one place.

Why it beats the default

Shopify will happily show you a pending order in the admin, but it will not chase the customer for you. Out of the box your only options are to manually scan the Orders page and email people one at a time, or to ignore the problem and write the money off. Neither scales past a handful of orders a week.

Paid recovery apps from the Shopify App Store solve it, but they charge a monthly fee or a slice of every order they recover, and they lock the logic inside their dashboard. With n8n you own the workflow end to end. You decide how long to wait, what the email says, and when to stop. There is no per-order cut and no extra subscription, because the same n8n instance can run all of your store automations together.

The tagging step is the part most homegrown attempts miss. Because the reminder writes a tag straight back to the order, the system has a memory. That single detail is what turns a script that spams customers every hour into a tool that contacts each person exactly once.

What you need

  • An n8n instance, either n8n Cloud or self-hosted, on version 1.0 or newer.
  • A Shopify store with admin API access, so n8n can read orders and write tags.
  • A Gmail account connected to n8n for sending the reminder. Outlook or SMTP work just as well.
  • At least one payment method that produces pending orders, such as bank deposit, cash on delivery, or draft order invoices.

Build time is roughly 35 minutes from scratch, or under 10 minutes if you import the template and drop in your credentials.

How it works, the big picture

  SHOPIFY FAILED PAYMENT RECOVERY EMAIL

  [Schedule Trigger]  hourly
        |
        v
  [HTTP Request] -- GET pending orders from Shopify
        |
        v
  [Split Out] -- one item per order
        |
        v
  [Filter] -- 2 to 48h old, has email, not yet tagged
        |
        v
  [Gmail] -- send "finish your payment" reminder
        |
        v
  [HTTP Request] -- PUT tag "payment-reminder-sent" on the order
  

Node-by-node list

Six nodes, in a single straight line. Nothing branches, which keeps it easy to reason about.

# Node Type Job
1 Every Hour Schedule Trigger Starts the run once an hour
2 Get Pending Orders HTTP Request (Shopify Admin API) Pulls open, pending orders
3 Split Orders Split Out Turns the orders array into one item each
4 Stalled And Not Contacted Filter Keeps only orders worth emailing
5 Send Reminder Gmail Emails the customer a payment nudge
6 Tag As Reminded HTTP Request (Shopify Admin API) Marks the order so it is skipped next time

Step-by-step build

1. Add the Schedule Trigger

Create a new workflow and drop in a Schedule Trigger node. Set it to a fixed interval of every 1 hour. This is the only trigger in the workflow, and it sits at the very start so the run always begins on the timer, never in the middle of the flow.

2. Get pending orders from Shopify

Add an HTTP Request node wired to the trigger. Configure it like this:

  1. Method: GET
  2. URL: https://YOUR-STORE.myshopify.com/admin/api/2026-04/orders.json
  3. Authentication: Header Auth, with header X-Shopify-Access-Token set to your admin API token.
  4. Send Query Parameters: on. Add status = open, financial_status = pending, limit = 250, and fields = id,name,email,created_at,order_status_url,tags,total_price,currency.

The response hands back an object with an orders array. A single order looks roughly like this:

{
  "id": 5123456789012,
  "name": "#1042",
  "email": "james.carter@gmail.com",
  "created_at": "2026-06-27T13:10:42-04:00",
  "order_status_url": "https://your-store.myshopify.com/.../orders/abc123",
  "tags": "",
  "total_price": "129.00",
  "currency": "USD"
}
💡

Asking Shopify for only the fields you need with the fields parameter keeps each response small and your runs fast, especially on stores with a long order history.

3. Split the orders into single items

Add a Split Out node. Set the field to split out to orders. n8n now emits one item per order, so every node after this point works on a single order at a time instead of one big array.

4. Filter down to orders worth emailing

Add a Filter node with three conditions, all of which must be true. This is where you avoid both nagging people too early and chasing dead orders forever.

  1. Older than 2 hours: {{ $now.diff($json.created_at, 'hours') >= 2 }} is true.
  2. Younger than 48 hours: {{ $now.diff($json.created_at, 'hours') <= 48 }} is true.
  3. Has an email and is not already tagged: {{ $json.email && !$json.tags.includes('payment-reminder-sent') }} is true.

Orders that fail any check drop out silently, which is exactly what you want.

5. Send the reminder with Gmail

Add a Gmail node set to send a message. Map the fields to the current order:

  1. To: {{ $json.email }}
  2. Subject: Your order {{ $json.name }} is almost done
  3. Message: a short, warm note. For example: Hi, we are holding order {{ $json.name }} for you but have not received payment yet. You can review it and finish up here: {{ $json.order_status_url }}. Reply to this email if anything went wrong and we will sort it out.
📌

Keep the tone helpful, not pushy. This customer already wanted to buy. A single clear link and a reply-to offer converts far better than a stern payment demand.

6. Tag the order so it is only emailed once

Add a final HTTP Request node to write the tag back to Shopify:

  1. Method: PUT
  2. URL: https://YOUR-STORE.myshopify.com/admin/api/2026-04/orders/{{ $json.id }}.json
  3. Authentication: the same Shopify Header Auth credential.
  4. Body: set Send Body on, Body Content Type to JSON, then provide the JSON body below.
{
  "order": {
    "id": {{ $json.id }},
    "tags": "{{ $json.tags }}, payment-reminder-sent"
  }
}

Because Shopify replaces the whole tag string on update, you pass the existing tags plus the new one. Next hour, the Filter sees payment-reminder-sent and skips the order. Save the workflow and switch it on.

Common mistakes

  • Expecting it to catch Shopify Payments card declines. A declined card usually means no order exists, so there is nothing to find. For that case, an abandoned checkout sequence is the right tool. Browse the Shopify automation guides for that flavor.
  • Forgetting the time floor. Without the 2 hour minimum, you email people who are literally still on the bank transfer screen. Give them room to finish on their own first.
  • Skipping the tag step. If you do not write a tag back, the hourly schedule will email the same customer every single hour until they pay or you intervene. The tag is the memory that prevents this.
  • Overwriting tags by accident. Always include {{ $json.tags }} in the PUT body so you append rather than wipe any tags the order already had.
  • Setting the limit too low. Use limit=250 so a busy store does not leave pending orders unprocessed at the bottom of the list.

Cost at realistic volume

Say your store takes 60 pending orders a month and roughly a third of them stall long enough to need a nudge, so about 20 reminder emails go out monthly. Here is what that actually costs to run.

Piece Volume Cost
n8n (self-hosted) ~720 runs/month $0, your own server
Shopify Admin API calls ~1,440 calls/month $0, within rate limits
Gmail sends ~20 emails/month $0, inside daily limits

Even on n8n Cloud, those 720 executions a month sit comfortably inside the entry plan. If recovering even three or four stalled orders a month covers the entire stack, and it usually does several times over, the workflow pays for itself immediately.

Ready-to-import template

Get the Shopify Failed Payment Recovery template

The guide above is free to follow. If you would rather skip the build, download the ready-to-import workflow, then just add your credentials. Prefer it installed and tuned for you? See our done-for-you services.

Download the template ($14) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

Does this work with Shopify Payments card declines?

Not directly. A declined card on Shopify Payments usually means no order is created, so there is nothing to recover here. This workflow targets orders that exist but sit on payment pending, which is what you get with bank transfer, cash on delivery, manual payment methods, and draft order invoices.

Will a customer get emailed more than once?

No. After the reminder sends, the workflow tags the order payment-reminder-sent through the Shopify API, and the Filter node skips any order that already carries that tag. Each pending order receives exactly one nudge, even though the schedule runs every hour.

How often should the schedule run?

Once an hour is a good default. The Filter only acts on orders between 2 and 48 hours old, so the customer gets a little breathing room before the first reminder and you stop chasing orders that are clearly never going to convert.

Can I send the reminder from Outlook or SMTP instead of Gmail?

Yes. Swap the Gmail node for the Microsoft Outlook node or the Send Email (SMTP) node. The fields that matter, recipient address and message body, map across one to one, so the rest of the workflow stays exactly the same.

Do I need a paid n8n plan?

No. Every node here ships with n8n core or the free Shopify and Gmail integrations, and it runs fine on a self-hosted instance or n8n Cloud. Your only real cost is the email sending, which Gmail covers inside its normal daily limits.

Related guides

Shopify Abandoned Cart Recovery Tracker in Google Sheets (n8n)

Shopify abandoned cart recovery tracker workflow in n8n










A Shopify abandoned cart recovery tracker in Google Sheets, built with n8n, logs every abandoned checkout to a spreadsheet and flips each row to Recovered the moment the shopper comes back and buys. You always know which carts are still open, which were saved, and how much revenue you clawed back. It runs on two Shopify triggers and one shared sheet, needs no paid recovery app, and costs effectively nothing to run.

Prefer to skip the setup? The ready-made template imports in under 10 minutes. Jump to the template section below.

What it does

Most stores treat abandoned carts as a black box: Shopify sends one or two recovery emails, and whatever happens after that is invisible. This workflow turns that black box into a living ledger.

Every time a shopper starts a checkout and walks away, n8n catches the event and writes a row to a Google Sheet: who they were, what was in the cart, how much it was worth, the recovery link, and a status of Abandoned. When that same shopper later returns and completes an order, a second flow finds the matching row by checkout ID and flips it to Recovered, stamping the date and the amount saved.

The result is a spreadsheet you actually own. Filter it to see every open cart from this week, sort by value to chase the biggest ones first, or sum the Recovered column to see exactly how much abandoned revenue you brought back. It slots neatly alongside the rest of your n8n Shopify automation stack.

Why it beats the default

Shopify already shows an abandoned checkout report, so why build this? Because the native report is a dead end. You cannot edit it, you cannot add a note, you cannot join it to anything, and you certainly cannot hand a filtered slice of it to an assistant to work through.

  • It is yours. The data lives in a sheet you control. Add columns, pivot it, build a chart, or pipe it into another tool later.
  • It tracks outcomes, not just events. The native report lists abandoned checkouts. This one tells you which ones were recovered and how much money came back, in the same row.
  • No per-message billing. There is no recovery app subscription and no email-credit meter. The whole thing runs on webhooks and a free Google Sheet.
  • It is a foundation. Once the ledger exists, layering on a Gmail nudge, a Slack alert for high-value carts, or a weekly digest is a small addition rather than a new project.

What you need

  • A Shopify store on any paid plan, with an app that has read access to orders and checkouts.
  • An n8n instance, either n8n Cloud or self-hosted.
  • A Google account and one Google Sheet for the tracker.
  • About 30 minutes to build from scratch, or under 10 with the template.

How it works — the big picture

ABANDONED CART RECOVERY TRACKER

Flow A — log abandoned carts
  [Shopify Trigger: checkouts/update] -> [Filter: has email, not paid]
        -> [Edit Fields: build row, Status = Abandoned]
        -> [Google Sheets: Append or Update on Checkout ID]
                                |
                                v
                     +----------------------+
                     |  Google Sheet ledger |
                     |  (one row per cart)  |
                     +----------------------+
                                ^
                                |
Flow B — mark recovered
  [Shopify Trigger: orders/create] -> [Edit Fields: map checkout_id, Status = Recovered]
        -> [Google Sheets: Append or Update on Checkout ID]

Node-by-node list

Seven nodes across two independent flows that share one sheet:

# Node Type Job
A1 Catch Abandoned Checkout shopifyTrigger Fires on the checkouts/update topic
A2 Only Real Abandoned Carts filter Keeps items with an email and no completed_at
A3 Build Tracker Row set (Edit Fields) Shapes the row, sets Status to Abandoned
A4 Log to Sheet googleSheets Append or Update, matched on Checkout ID
B1 Catch New Order shopifyTrigger Fires on the orders/create topic
B2 Mark Recovered set (Edit Fields) Maps checkout_id, sets Status to Recovered
B3 Update Sheet googleSheets Append or Update, matched on Checkout ID

Step-by-step build

Build the sheet first, then the two flows.

  1. Create the Google Sheet. Make a new sheet named Tracker with these headers in row 1: Checkout ID, Date, Customer, Email, Items, Cart Value, Currency, Recovery URL, Status, Recovered At. The exact spelling matters because n8n matches on these names.
  2. Add the abandoned checkout trigger. Add a Shopify Trigger node, connect your Shopify credential, and set Topic to Checkout Updated (checkouts/update). Shopify fires this when a cart is started and then sits idle.
  3. Filter to real abandoned carts. Add a Filter node after the trigger. Add two conditions, combined with AND: {{ $json.email }} is not empty, and {{ $json.completed_at }} is empty. This drops anonymous carts and any checkout that already became an order.
  4. Shape the row. Add an Edit Fields node (Set). Switch to Manual mapping and add one assignment per column. For example, map Checkout ID to ={{ $json.id }}, Email to ={{ $json.email }}, Cart Value to ={{ $json.total_price }}, Recovery URL to ={{ $json.abandoned_checkout_url }}, and set Status to the fixed string Abandoned.
  5. Log to the sheet. Add a Google Sheets node, Operation Append or Update Row. Pick your document and the Tracker sheet, then set Column to match on to Checkout ID. Map each column to the field you built in the Edit Fields node. Append or Update is what keeps one row per cart even when Shopify fires the event several times.
  6. Add the recovery trigger. Add a second Shopify Trigger node with Topic Order Created (orders/create). This is a separate entry point, not wired into Flow A.
  7. Mark the cart recovered. Add an Edit Fields node after it. Map Checkout ID to ={{ $json.checkout_id }}, set Status to Recovered, map Recovered At to ={{ $now.toISO() }}, and map Cart Value to ={{ $json.total_price }} so the saved amount reflects the real order.
  8. Update the same row. Add a final Google Sheets node, Operation Append or Update Row, matched on Checkout ID again. Because the abandoned row already carries that checkout ID, this lands on it and flips the status instead of adding a new line.
  9. Save and activate. Save the workflow and toggle it Active. Run a test checkout, then complete it, and watch the row go from Abandoned to Recovered.
💡

Tip: Shopify orders carry the originating checkout_id, which is the clean join key between the two flows. Matching on checkout ID rather than email means two different carts from the same shopper never collide.

The data structure

A row moves through two states. Here is the same cart before and after recovery.

Column Abandoned row Recovered row
Checkout ID 29845011 29845011
Customer Emily Rodriguez Emily Rodriguez
Email emily.rodriguez@gmail.com emily.rodriguez@gmail.com
Cart Value 128.00 128.00
Currency USD USD
Status Abandoned Recovered
Recovered At (empty) 2026-06-26T15:42:00Z
📌

Header names in the sheet must match the names you map in n8n exactly, including capitalization. A mismatch silently creates a new column instead of writing to the one you meant.

Common mistakes

  • Skipping the completed_at filter. Without it, every checkout update, including completed ones, lands in the tracker and inflates your abandoned count.
  • Using Append instead of Append or Update. Plain Append adds a fresh row on every Shopify fire, so one cart becomes five rows. Always match on Checkout ID.
  • Matching the recovery flow on email. Email is not unique per cart. Match on checkout_id so a repeat shopper does not overwrite an unrelated open cart.
  • Wrong Set node version. Use the current Edit Fields node with named assignments. Old tutorials show a key-value table that behaves differently and breaks expressions.
  • Confusing the topics. Use checkouts/update for abandonment and orders/create for recovery. checkouts/create fires too early, before the cart is meaningfully abandoned.

Cost at realistic volume

This is one of the cheapest automations you can run because it never touches a paid messaging service.

Volume Executions / month n8n cost Google Sheets
100 abandoned carts ~150 Free (self-hosted) or Starter Free
500 abandoned carts ~750 Within n8n Cloud Starter Free
2,000 abandoned carts ~3,000 n8n Cloud Pro or self-hosted Free

Self-hosted n8n makes the marginal cost zero. On n8n Cloud, even a busy store stays inside an entry plan, since each cart costs only one or two executions across both flows.

Get the abandoned cart recovery tracker template

The guide above is free to follow. If you would rather skip the build, download the ready-to-import workflow, then just add your credentials. Prefer it installed and tuned for you? See our done-for-you services.

Download the template ($14) →

Instant download · Works on n8n Cloud and self-hosted

Frequently asked questions

Does this work on Shopify Basic?

Yes. The workflow uses standard Shopify webhooks, checkouts/update and orders/create, that are available on every paid Shopify plan, including Basic. You only need an app with read access to orders and checkouts to issue the API credential.

What counts as an abandoned cart here?

Any checkout that has a customer email but no completed_at timestamp. The Filter node enforces that, so anonymous carts and already-paid checkouts never reach your sheet. A cart is treated as recovered only when a real order arrives carrying the same checkout ID.

Will the same cart create duplicate rows?

No. Both Google Sheets nodes use Append or Update matched on Checkout ID. Shopify can fire checkouts/update several times for one cart, but each fire lands on the same row, so the tracker stays one row per cart from open to recovered.

Can I send a recovery email from the same workflow?

Yes. After the log node you can add a Gmail node that uses the Recovery URL field to email the shopper. Many stores keep the tracker pure and run outreach as a separate workflow, so the ledger and the email sequence stay independent and easy to debug.

How is this different from Shopify’s built-in report?

Shopify shows a fixed report you cannot edit or join. This gives you an owned spreadsheet you can filter, pivot, hand to an assistant, and read recovered revenue from directly, with no per-email cost and no third-party recovery app subscription.

Related guides

How to Get Shopify Alerts on Telegram with n8n (Free Templates)

Want a ping on your phone the second a Shopify order comes in, or a heads up before a product sells out? This guide shows you how to get Shopify alerts on Telegram with n8n, free: real time order notifications and inventory alerts, using two ready to import templates. No code, no per message fee, and it works on your phone, your desktop, or a shared team group.

Why Telegram for Shopify alerts

  • Instant. Telegram delivers in real time, so you see an order the second it lands.
  • Free. No per message cost, unlike SMS or some chat apps.
  • On every device, and easy to share with a team in a group chat.
  • Simple to automate. n8n talks to both Shopify and Telegram out of the box.

What you need

  • An n8n instance, cloud or self hosted.
  • Your Shopify store connected to n8n. Shopify changed how this works in 2026, so follow our guide to connect Shopify to n8n in 2026 first.
  • A free Telegram bot (we cover that below).

Template 1: Shopify order notifications on Telegram

This is the one most people want: a Telegram message the moment a new order is placed. It is a two node flow. The Shopify trigger fires on every new order, and the Telegram node sends a message with the order number, total, customer, and item count.

Download (free): Shopify New Order to Telegram Alert (JSON). In n8n, choose Import from File and select it.

Template 2: Shopify inventory alerts on Telegram (low stock)

Never get caught out of stock again. This template turns Shopify inventory changes into Telegram alerts: it watches your stock and pings you when a product drops to or below the number you choose (the default is 5). It uses the Shopify inventory webhook, a simple threshold check, and the Telegram node, so you get an inventory alert in time to reorder.

Download (free): Shopify Low Stock to Telegram Alert (JSON). Set your threshold in the condition node after you import.

Want the full build explained node by node? See the in-depth guides for Shopify order alerts on Telegram and Shopify inventory low stock alerts.

What the alerts look like

Both templates send plain, readable messages you can skim in a second. A new order alert looks like this:

New Shopify order #1043
Total: 128.00 USD
Customer: Sarah M.
Items: 3

And an inventory alert looks like this:

Low stock alert
Product: Organic Cotton Tee (M)
Remaining: 4
Time to reorder.

You can edit the message text in the Telegram node to add or remove any field, or to write it in your own language.

Prefer a daily summary instead of a ping per order?

If a busy store means too many pings, send yourself one digest a day instead. Our Shopify daily sales report to Telegram guide builds a scheduled workflow that posts your orders, revenue, and best sellers to Telegram every morning. Many stores run the order notification template and the daily report together: instant alerts for the big moments, one clean summary for the numbers.

Create your Telegram bot (2 minutes)

  1. Open Telegram and message @BotFather. Send /newbot and follow the prompts to name it.
  2. BotFather gives you a bot token. Copy it.
  3. In n8n, on the Send to Telegram node, create a Telegram credential and paste the token.
  4. Get your chat ID: message @userinfobot on Telegram and it replies with your ID. For a team, add your bot to a group and use the group chat ID. Put that ID in the Telegram node.
  5. Save, then turn the workflow Active. Place a test order or change a stock level to see the alert.

Telegram vs email vs SMS for Shopify alerts

All three can carry a Shopify alert, but they are not equal for this job.

ChannelSpeedCost per messageBest for
TelegramInstantFreeReal time order and inventory alerts
EmailMinutesFreeDaily digests and a written record
SMSInstantPaid per messageA fallback when there is no smartphone

For instant alerts you check dozens of times a day, Telegram wins on cost and speed. Keep email for the daily report, where a searchable record is handy.

Cost

Free. Telegram is free, n8n is free if you self host, and there is no per message charge. The only thing you bring is your Shopify connection.

Want the alert to think, not just notify? Our Shopify AI Order Alert to Telegram agent reads each order and writes a short smart summary that flags anything worth a look, like a large total or an international address. Or browse the full Shopify AI agents collection and the n8n Shopify automation guide.

Frequently asked questions

How do I get Shopify order alerts on Telegram?

Import the free order notification template above into n8n, connect your Shopify store and a Telegram bot, then activate it. Every new order then sends a Telegram message with the order details. No code needed.

How do I set up Shopify inventory alerts on Telegram?

Import the inventory (low stock) template, set your threshold in the condition node, and connect the same Telegram bot. When a product drops to or below that number, you get a Telegram alert so you can reorder in time.

Can I send Shopify alerts to a Telegram group or channel?

Yes. Add your bot to a Telegram group and use the group chat ID in the Telegram node instead of your personal ID. Everyone in the group then sees each alert, which is ideal for a fulfillment or store team.

Is Telegram better than SMS for Shopify order alerts?

For most stores, yes. Telegram is instant and free, while SMS charges per message and adds up fast on a busy store. SMS only wins as a fallback when the person receiving alerts has no smartphone.

Is it free to get Shopify alerts on Telegram?

Yes. Telegram has no per message fee, and n8n is free when self hosted. The templates are free to download and import.

Do I need to know how to code?

No. You import the template, add your Shopify and Telegram credentials, and activate. The setup is a few minutes.

How do I find my Telegram chat ID?

Message @userinfobot on Telegram and it replies with your numeric chat ID. For team alerts, add your bot to a group and use the group chat ID instead.

Comparing options first? See the best n8n AI agent templates for Shopify, all tested and free to run.