Shopify New vs Returning Customer Revenue Report in n8n












A Shopify new vs returning customer revenue report in n8n answers a question your default dashboard hides: how much of last week’s money came from first-time buyers versus people coming back for more. Shopify shows you a single revenue figure, but that number blends acquisition and loyalty into one blur. This guide builds a free weekly workflow that pulls your paid orders, splits them into new and returning buyers, and drops the split into a Google Sheet and your inbox every Monday morning, so you can see whether growth is coming from new customers or repeat ones.

What it does

The workflow runs on a schedule, reads your recent paid orders straight from the Shopify Admin API, and classifies each one by whether the buyer is a first-time or repeat customer. It then adds up revenue and order count for each group and hands you two outputs: a new row in a running Google Sheet, and a short HTML email summarizing the week.

Concretely, every Monday at 7 AM you get a message like this in your inbox:

Segment Orders Revenue Share
New customers 38 $4,120.00 46.8%
Returning customers 29 $4,684.00 53.2%
Total 67 $8,804.00 100%

Over a few weeks the Google Sheet becomes a trend line. If new-customer revenue is climbing, your marketing is working. If returning-customer revenue is flat, your retention and email flows need attention. That is the kind of signal that changes where you spend next month, and it is the reason this report belongs in your n8n Shopify automation stack.

Why it beats the default

Shopify Analytics does have a returning-customer rate card, but it lives behind a login, refreshes on Shopify’s schedule, and does not push anything to you. You have to remember to go look. It also mixes the revenue figure into reports you cannot easily export into your own spreadsheet next to your ad spend or email numbers.

This workflow flips that. The data comes to you, in the format you choose, next to whatever other columns you want to track. Because the numbers land in a plain Google Sheet, you can chart them, pivot them, or feed them into a wider marketing report without copying anything by hand. And because it runs in n8n, the whole thing is free to operate and yours to customize: change the schedule, add a Slack copy, or split the segments further whenever you like.

What you need

  • A Shopify store with Admin API access. Follow the 2026 Dev Dashboard method in our connect Shopify to n8n guide to generate an access token. This is the prerequisite for every step below.
  • An n8n instance, either the free self-hosted edition or n8n Cloud.
  • A Google account for Google Sheets and Gmail.
  • About 30 minutes to build from scratch, or a few minutes if you import the ready-made template at the end.
📌

Use a current Admin API version in your request URL. This guide uses 2026-04. Older removed versions will return errors.

Node-by-node list

Six nodes, wired in a straight line that forks at the end so the sheet and the email both receive the same summary:

Schedule Trigger  ->  Code (date window)  ->  HTTP Request (get orders)
        ->  Code (split new vs returning)  ->  Google Sheets (append row)
                                           ->  Gmail (send report)
# Node Type Job
1 Every Monday 7 AM Schedule Trigger Fires the workflow once a week
2 Prepare date window Code Builds the last-seven-days date range
3 Get paid orders HTTP Request Pulls paid orders from the Shopify Admin API
4 Split new vs returning Code Classifies each order and sums the two groups
5 Append to Google Sheets Google Sheets Logs the weekly summary as a new row
6 Email the report Gmail Sends the HTML summary to you

Step-by-step build

1. Schedule Trigger

Add a Schedule Trigger node. Set the interval to Weeks, trigger day to Monday, and trigger hour to 7. This gives you a clean weekly reading period. If you would rather see numbers more often, you can change this later without touching any other node.

2. Code — prepare the date window

Add a Code node named Prepare date window. It computes the ISO timestamps for the last seven days, which the next node passes to Shopify as a filter.

const now = new Date();
const end = now.toISOString();
const start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString();
return [{
  json: {
    created_at_min: start,
    created_at_max: end,
    period_label: start.slice(0, 10) + ' to ' + end.slice(0, 10)
  }
}];

After this node runs, the data looks like this:

{
  "created_at_min": "2026-08-22T07:00:00.000Z",
  "created_at_max": "2026-08-29T07:00:00.000Z",
  "period_label": "2026-08-22 to 2026-08-29"
}

3. HTTP Request — get paid orders

Add an HTTP Request node. Set the method to GET and the URL to your store’s orders endpoint:

https://YOUR_STORE.myshopify.com/admin/api/2026-04/orders.json

Under Authentication, choose Generic Credential Type, then Header Auth, and select a credential whose name is X-Shopify-Access-Token and whose value is the token from the connect guide. Turn on Send Query Parameters and add these:

Name Value
status any
financial_status paid
created_at_min ={{ $json.created_at_min }}
created_at_max ={{ $json.created_at_max }}
limit 250
fields id,total_price,customer,created_at,financial_status
💡

Tip: The fields parameter keeps the response small and fast by asking Shopify for only the five properties this workflow reads. The customer object is the one that carries orders_count, which the next node needs.

4. Code — split new vs returning

Add a second Code node named Split new vs returning. Shopify returns all the orders under an orders array in a single item, so this node reads that array, classifies each order, and returns one summary object.

const resp = $input.first().json;
const orders = resp.orders || [];
let newRev = 0, newCnt = 0, retRev = 0, retCnt = 0;
for (const o of orders) {
  const price = parseFloat(o.total_price || '0');
  const oc = o.customer && o.customer.orders_count ? Number(o.customer.orders_count) : 1;
  if (oc <= 1) { newRev += price; newCnt++; } else { retRev += price; retCnt++; }
}
const totalRev = newRev + retRev;
const round = n => Math.round(n * 100) / 100;
const pct = n => totalRev ? Math.round((n / totalRev) * 1000) / 10 : 0;
return [{
  json: {
    period_label: $('Prepare date window').first().json.period_label,
    new_customers: newCnt,
    new_revenue: round(newRev),
    returning_customers: retCnt,
    returning_revenue: round(retRev),
    total_orders: newCnt + retCnt,
    total_revenue: round(totalRev),
    new_revenue_pct: pct(newRev),
    returning_revenue_pct: pct(retRev)
  }
}];

The single output item now carries everything both downstream nodes need:

{
  "period_label": "2026-08-22 to 2026-08-29",
  "new_customers": 38,
  "new_revenue": 4120.00,
  "returning_customers": 29,
  "returning_revenue": 4684.00,
  "total_orders": 67,
  "total_revenue": 8804.00,
  "new_revenue_pct": 46.8,
  "returning_revenue_pct": 53.2
}

5. Google Sheets — append the row

Create a Google Sheet with a header row that matches the fields below, then add a Google Sheets node set to Append. Pick your document and sheet, choose Map Each Column Manually, and map the columns:

Sheet column Value expression
Period ={{ $json.period_label }}
New customers ={{ $json.new_customers }}
New revenue ={{ $json.new_revenue }}
Returning customers ={{ $json.returning_customers }}
Returning revenue ={{ $json.returning_revenue }}
Total revenue ={{ $json.total_revenue }}
New % ={{ $json.new_revenue_pct }}
Returning % ={{ $json.returning_revenue_pct }}

6. Gmail — email the report

Add a Gmail node set to Send. Put your own address in To, set the subject to an expression like ={{ 'New vs returning revenue — ' + $json.period_label }}, switch the email type to HTML, and paste a short template that reads the summary fields into a table. Wire both the Google Sheets node and the Gmail node to the output of the split node so they run from the same summary item.

💡

Tip: Prefer Slack or Telegram over email? Swap the Gmail node for a Slack or Telegram node and reference the same $json fields. Nothing else in the workflow changes.

Common mistakes

  • Treating orders_count as a per-order flag. Shopify’s orders_count is the customer’s lifetime total at the moment of the API call, not their count on the day they ordered. A buyer who was new last week but ordered again since will now read as returning. For a weekly trend this is close enough, but do not present it as an exact historical cohort.
  • Forgetting pagination on a busy store. The request pulls up to 250 orders. If a week ever exceeds that, enable pagination in the HTTP Request node options so n8n follows Shopify’s Link header, otherwise the report quietly undercounts.
  • Leaving status open. Without financial_status=paid, pending and unpaid orders inflate your revenue. Keep the filter so the report reflects money actually collected.
  • Mismatched sheet headers. The Append node maps by column name. If a header in the sheet does not match the mapping exactly, that value lands in the wrong place or gets skipped.
  • Using a removed API version. Point the URL at a current version such as 2026-04. Legacy versions Shopify has retired will fail the request.

Cost at realistic volume

This workflow is effectively free to run. A weekly schedule means four executions a month, each making a single Shopify API call, one Sheets append, and one Gmail send. That is nowhere near any rate limit or paid tier.

Service Usage per month Cost
Shopify Admin API 4 order pulls Included with any plan
n8n 4 executions Free self-hosted, or well within cloud starter
Google Sheets 4 appended rows Free
Gmail 4 emails Free

Even if you switch to a daily schedule, you are looking at about 30 runs a month, which stays comfortably free on self-hosted n8n.

🚀 Get the ready-to-import template

The full guide above is free to follow. If you would rather skip the build, the downloadable template is the exact six-node workflow from this article, ready to import into n8n. Add your Shopify, Google Sheets, and Gmail credentials and you are reporting in minutes. Want it done for you end to end? See our done-for-you automation service.

Download the template ($13) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

How does the workflow decide if an order is from a new customer?

It reads the customer.orders_count field on each order. A value of 1 means this is the buyer’s only order, so it is counted as new. Anything higher is counted as returning. This is Shopify’s own lifetime order count, so it is a close approximation rather than a per-order stamp.

Does this work for guest checkouts without a customer account?

Guest orders can arrive with no customer object. The Code node defaults a missing orders_count to 1, so those orders are treated as new. If you sell mostly to guests, add an email-based grouping step to catch repeat guest buyers before they create an account.

Can I run this daily instead of weekly?

Yes. Open the Schedule Trigger and switch the interval from weeks to days, then adjust the Code node window from seven days to one. Daily runs give faster feedback but noisier numbers, so most stores keep the weekly cadence for a cleaner trend line in the sheet.

What if my store has more than 250 orders in a week?

The HTTP Request pulls one page of up to 250 orders. Higher-volume stores should turn on pagination in the node options so n8n follows Shopify’s Link header and fetches every page. Without it, the report silently counts only the first 250 orders.

Do I need a paid Shopify or n8n plan for this?

No. A standard Shopify plan exposes the Admin API, and the workflow runs fine on n8n’s free self-hosted edition or the starter cloud tier. Google Sheets and Gmail both work on free Google accounts, so the only real cost is the few minutes each run takes.

Related guides

Shopify Average Order Value Trend Report with n8n









A Shopify average order value trend report in n8n turns raw order data into one number you can actually steer the store by, tracked week over week. This guide builds a workflow that pulls last week’s paid orders, computes average order value, compares it against the week before, logs the result to Google Sheets, and emails you the trend. It runs on a schedule, uses only free tools, and takes about 30 minutes to put together from scratch.

Prefer to skip the build? The guide below is free to follow. If you would rather not wire six nodes by hand, grab the ready-made template and import it in under ten minutes.

What it does

Average order value (AOV) is total revenue divided by number of orders. It tells you how much a typical customer spends per checkout, and it is one of the few metrics you can move directly with bundles, free-shipping thresholds, and upsells. The problem is that Shopify shows you today’s AOV but keeps no clean, owned history of how it moves, and it never emails you when the number slips.

This workflow fixes that. Once a week it asks the Shopify Admin API for every paid order in the last seven days and every paid order in the seven days before that. It sums the order totals, divides by the order count for each window, and calculates the change between them. It writes this week’s figures as a new row in a Google Sheet, so the trend line grows on its own, and it emails you a short summary that says whether AOV went up, down, or stayed flat versus last week.

The result is a self-updating AOV trend you own, sitting in your inbox every Monday morning, with a Google Sheet behind it that you can chart or hand to a bookkeeper. This is the analytics half of running a store on n8n Shopify automation: small scheduled reports that replace a spreadsheet chore you keep forgetting to do.

Why it beats the default

Shopify’s built-in Analytics does show an average order value tile, and on paid plans you can view it over a date range. So why build this?

  • You own the history. Shopify’s dashboard is a live view, not a log you control. This workflow appends one row per week to your own Google Sheet, so you build a permanent trend that survives plan changes and export limits.
  • It comes to you. Nobody logs into Analytics every Monday. An email that lands in your inbox with “AOV up 4.2% vs last week” gets read. A dashboard you have to remember to open does not.
  • It is comparative by design. The default tile shows a number. This report always frames it against the prior week, which is the context that tells you whether a change is worth acting on.
  • It is free and extendable. No third-party analytics app subscription. Because the math lives in a Code node, you can switch to net AOV, add a 4-week average, or split by sales channel later without paying anyone.

What you need

  • An n8n instance (Cloud or self-hosted). Every node here is a core node, so there is nothing extra to install.
  • A Shopify custom app access token. Create it through the 2026 Shopify Dev Dashboard and give it the read_orders scope. If you have not connected Shopify to n8n yet, follow connect Shopify to n8n (2026 method) first, then come back.
  • A Google account for Google Sheets, to hold the trend log.
  • A Gmail account to send the weekly report (or any email/Slack/Telegram node if you prefer another channel).

Estimated build time: about 30 minutes from scratch, or under 10 minutes with the template.

Node-by-node list

Six nodes, wired in a straight line. The Code node is where all the math happens, and it reads from both HTTP nodes by name.

[Every Monday 8am]  Schedule Trigger
        |
        v
[Fetch this week orders]  HTTP Request  -> Shopify Admin API, last 7 days, paid
        |
        v
[Fetch last week orders]  HTTP Request  -> Shopify Admin API, days 7-14, paid
        |
        v
[Compute AOV + trend]  Code  -> revenue / orders for each window, % change
        |
        v
[Append to AOV trend]  Google Sheets  -> one new row per week
        |
        v
[Email AOV report]  Gmail  -> "AOV up 4.2% vs last week"
  
# Node Type Job
1 Every Monday 8am Schedule Trigger Fires the report weekly
2 Fetch this week orders HTTP Request Paid orders from the last 7 days
3 Fetch last week orders HTTP Request Paid orders from days 7 to 14
4 Compute AOV + trend Code AOV per window and the change
5 Append to AOV trend Google Sheets Logs one row per week
6 Email AOV report Gmail Sends the summary email

Step-by-step build

Step 1 — Add the Schedule Trigger

Create a new workflow and add a Schedule Trigger. Set it to run weekly, on Monday, at 08:00. This gives you a clean seven-day window that always ends when the report runs. If your customers shop mostly on weekends, run it Monday morning so the previous week is complete.

Step 2 — Fetch this week’s paid orders

Add an HTTP Request node named Fetch this week orders. Configure it as a GET request to your Shopify Admin API:

URL:  https://YOUR_STORE.myshopify.com/admin/api/2026-04/orders.json
Auth: Predefined Credential Type -> Shopify Access Token API
Query parameters:
  status            = any
  financial_status  = paid
  created_at_min    = {{ $now.minus({ days: 7 }).toISO() }}
  created_at_max    = {{ $now.toISO() }}
  limit             = 250
  fields            = id,total_price,created_at

The fields parameter keeps the payload small by asking only for what the math needs. Using financial_status=paid means unpaid draft orders and pending checkouts never inflate the numbers.

💡

Tip: The expressions use n8n’s built-in $now (a Luxon date), so you never hardcode dates. $now.minus({ days: 7 }) is always exactly one week back from run time.

Step 3 — Fetch last week’s paid orders

Add a second HTTP Request node named Fetch last week orders, identical to the first except for the date window:

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

This gives you the previous seven-day block, which is the baseline the report compares against. Wire it after the first HTTP node so both run before the math.

Step 4 — Compute AOV and the trend (Code node)

Add a Code node named Compute AOV + trend. It reads both HTTP nodes by name, sums each window, divides to get AOV, and builds both the sheet row and the email body:

const thisOrders = $('Fetch this week orders').first().json.orders || [];
const lastOrders = $('Fetch last week orders').first().json.orders || [];

function summarize(orders) {
  const revenue = orders.reduce((s, o) => s + parseFloat(o.total_price || 0), 0);
  const count = orders.length;
  const aov = count > 0 ? revenue / count : 0;
  return { revenue, count, aov };
}

const current = summarize(thisOrders);
const previous = summarize(lastOrders);
const delta = current.aov - previous.aov;
const pctChange = previous.aov > 0 ? (delta / previous.aov) * 100 : 0;
const direction = delta > 0 ? 'up' : delta < 0 ? 'down' : 'flat';

return [{ json: {
  report_date: $now.toFormat('yyyy-LL-dd'),
  orders_this_week: current.count,
  revenue_this_week: Number(current.revenue.toFixed(2)),
  aov_this_week: Number(current.aov.toFixed(2)),
  aov_last_week: Number(previous.aov.toFixed(2)),
  pct_change: Number(pctChange.toFixed(1)),
  direction
}}];

The count > 0 guard is what keeps a zero-order week from crashing the workflow. Notice the node never makes its own API calls; it just reaches back to the two HTTP nodes with $('Node Name'), which is why they must sit upstream on the same path.

Step 5 — Append the row to Google Sheets

Add a Google Sheets node named Append to AOV trend. Set the operation to Append, pick your spreadsheet and tab, and map the columns:

Date       = {{ $json.report_date }}
Orders     = {{ $json.orders_this_week }}
Revenue    = {{ $json.revenue_this_week }}
AOV        = {{ $json.aov_this_week }}
PctChange  = {{ $json.pct_change }}

Create a sheet with a header row of exactly Date, Orders, Revenue, AOV, PctChange before the first run. Each week adds one row, so after a few months you can select the AOV column and drop in a chart to see the trend at a glance.

Step 6 — Email the report with Gmail

Add a Gmail node named Email AOV report. Set the recipient to yourself, a subject like Weekly AOV report - {{ $json.report_date }}, and a short HTML body that reads the fields:

This week AOV: {{ $json.aov_this_week }}  ({{ $json.orders_this_week }} paid orders)
Last week AOV: {{ $json.aov_last_week }}
Trend: {{ $json.direction }}  ({{ $json.pct_change }}% vs last week)

Save the workflow and toggle it Active. That is the whole build: a scheduled AOV report that logs itself and emails you the direction of travel.

Common mistakes

  • Forgetting the 250-order limit. A single Admin API call returns at most 250 orders. If a window holds more, you will undercount. Add cursor pagination with the Link header, or run the report more often on a shorter window.
  • Counting the wrong orders. Leaving out financial_status=paid lets abandoned and unpaid draft orders slip in and drag AOV down. Keep the filter so you measure real revenue.
  • Refunds not accounted for. total_price is the gross amount at checkout; a later refund does not change it. If you want net AOV, use current_total_price instead, which reflects refunds.
  • Timezone drift. $now follows your n8n instance timezone. If that differs from your store's timezone, your seven-day window can start a few hours off. Set the instance timezone to match the store.
  • Missing header row. If the Google Sheet has no header row that matches the mapped column names, the append silently writes to the wrong place. Add the header before the first run.

Cost at realistic volume

This report is effectively free to run. It fires four times a month and makes two Shopify API calls per run, which is nothing against Shopify's rate limits. Google Sheets and Gmail both sit inside their free tiers at this volume. The only real cost is your n8n instance, which you are already paying for if you self-host or run n8n Cloud.

Component Usage per month Cost
Shopify Admin API 8 order calls Free (well under limits)
Google Sheets 4 appended rows Free
Gmail 4 emails Free
n8n executions 4 runs Free tier / existing plan

Download the AOV trend report template

The guide above is free to follow node by node. The template is the same validated workflow as a single import file, so you skip the wiring, drop in your credentials, and have a weekly AOV report running in minutes.

Download the template ($14) →

Instant download · Works on n8n Cloud and self-hosted · Want it built and connected for you? See our done-for-you service.

Frequently asked questions

Does this report include refunds and taxes in the AOV figure?

By default it uses each order's total_price, the gross paid amount including tax and shipping. Refunds issued after the sale are not subtracted. If you want net AOV, swap total_price for current_total_price in the Code node, which reflects post-refund totals.

What if my store gets more than 250 orders in a week?

The template pulls up to 250 orders per request, which covers most small and mid-size stores. For higher volume, add cursor pagination using the Link response header, or filter to a shorter window. The guide notes exactly where the 250 limit lives so you can extend it.

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

Yes. Replace the Gmail node with a Slack or Telegram node and pass the same fields as the message body. The Code node output stays identical, so only the final delivery node changes.

Why is my AOV zero, or the workflow errors on an empty week?

If a window has no paid orders, the count is zero and dividing would break. The Code node guards against this by returning an AOV of zero when the count is zero, so the workflow still logs a row and sends the email instead of failing.

Does this work on n8n Cloud and self-hosted?

Both. Every node used is a core n8n node, so there is nothing to install. On self-hosted you supply your own Shopify, Google, and Gmail credentials; on Cloud the setup is identical through the built-in credential manager.

Related guides

Shopify Customer Lifetime Value Report with n8n

A Shopify customer lifetime value report in n8n answers the one question your dashboard hides: which customers are actually worth the most over their entire history, not just this week. This guide builds a workflow that pulls every order, sums revenue per customer, ranks your top spenders, writes the full list to Google Sheets, and emails you the top ten every Monday morning. No paid analytics app, no manual export, and it runs on the n8n free tier.

What it does

The workflow runs on a weekly schedule. It fetches all orders from your Shopify store, groups them by customer, and adds up total revenue and order count for each person. It then sorts everyone by lifetime spend, keeps the top twenty, and calculates each customer’s average order value along the way. The ranked list lands in a Google Sheet you can filter and chart, and a formatted HTML email drops the top ten straight into your inbox.

Lifetime value, or CLV, is the total amount a customer has spent with you across every order they have ever placed. Knowing it changes how you spend on retention: a VIP tier, an early-access invite, or a hand-written thank-you note is worth far more aimed at a customer who has spent 4,000 over two years than at a one-time buyer. This report surfaces that list on autopilot.

Why it beats the default

Shopify’s admin shows you lifetime spend one customer at a time. There is no native screen that ranks every customer by total revenue and hands you a sortable list, and the reports that come close sit behind the Shopify or Shopify Plus reporting tiers. Third-party CLV apps solve it but charge a monthly fee and often want to email your customers on your behalf.

The n8n version costs nothing beyond what you already run, keeps your customer data inside your own Google account, and is fully yours to change. Want CLV over the last twelve months instead of all time? Adjust one filter. Want to push the top spenders into a Shopify customer tag or a Klaviyo segment? Add one node. You own the logic, not a vendor.

What you need

  • A running n8n instance (self-hosted or n8n Cloud). The free self-hosted tier handles this comfortably.
  • A Shopify custom app with Admin API access, created through the 2026 Shopify Dev Dashboard. If you have not connected Shopify to n8n yet, follow connect Shopify to n8n (2026 method) first. You need the read_orders and read_customers scopes and a current Admin API version such as 2026-04.
  • A Google account with Google Sheets, connected to n8n by OAuth2, plus one blank sheet with a header row.
  • A Gmail account connected by OAuth2 to send the report email.

Node-by-node list

  • Every Monday 8am — Schedule Trigger. A cron expression (0 8 * * 1) fires the workflow once a week.
  • Get Shopify Orders — Shopify node, resource Order, operation Get All, Return All enabled, status set to any so paid, fulfilled, and archived orders are all counted.
  • Calculate CLV per Customer — Code node. Groups orders by customer.id, sums total_price, counts orders, computes average order value, sorts by total spend, and keeps the top twenty with a rank.
  • Append to Sheet — Google Sheets node, operation Append, auto-mapping the ranked rows into your sheet.
  • Build Email Summary — Code node. Turns the top ten rows into a clean HTML table for the email body.
  • Email the Report — Gmail node, operation Send, email type HTML, using the summary as the message.

Step-by-step build

  1. Create a new workflow in n8n and add a Schedule Trigger. Set the rule to a cron expression and enter 0 8 * * 1 so it runs every Monday at 08:00 in your instance timezone.
  2. Add a Shopify node. Select your Shopify credential, set Resource to Order and Operation to Get All. Turn on Return All so pagination is handled for you, and under Options set Status to any. This is the heaviest node, so on a large store expect it to take a minute.
  3. Add a Code node named Calculate CLV per Customer. Paste the aggregation script (included in the template). It walks every order, skips guest orders with no customer record, keys a running total on the customer id, then returns the top twenty ranked by total spent with an added average order value field.
  4. Add a Google Sheets node set to Append. Pick your document and sheet, and set the mapping mode to Map Automatically. Give your sheet a header row that matches the field names the Code node outputs: rank, customerId, name, email, orders, totalSpent, avgOrderValue.
  5. Add a second Code node named Build Email Summary. It reads the appended rows, slices the top ten, and builds an HTML table plus a short intro line into a single output item.
  6. Add a Gmail node set to Send. Enter your own address in Send To, set Email Type to HTML, and put {{ $json.html }} in the Message field. Give it a subject like “Weekly Shopify CLV report”.
  7. Run the workflow once by hand with Execute Workflow. Check the sheet fills with a ranked list and the email arrives. Once it looks right, save and toggle the workflow Active so the Monday schedule takes over.

Common mistakes

Counting guest checkouts as separate people. Orders placed without an account have no customer.id, and if you key on email instead you will merge or split people unpredictably. The template skips orders with no customer record so your ranking stays clean.

Forgetting Return All. Without it the Shopify node returns only the first page, usually fifty orders, and your CLV totals silently understate every customer. Always enable Return All for a full-history report.

Header row mismatch in Sheets. Auto-mapping matches input field names to column headers exactly. If your header says “Total Spent” but the field is totalSpent, the column stays empty. Copy the field names verbatim into row one.

Using the wrong price field. total_price includes tax and shipping. If you want product revenue only, switch to subtotal_price in the Code node. Decide once so your trend stays consistent week to week.

Cost at realistic volume

Everything here runs on free tiers. Self-hosted n8n has no per-execution charge, so one weekly run is free regardless of store size. The Shopify Admin API is included with your Shopify plan and the read calls this workflow makes sit far inside the rate limits. Google Sheets and Gmail are free on a standard Google account, and one appended block of rows plus one email per week is nothing against their quotas.

If you run n8n Cloud instead, this is a single scheduled execution per week, comfortably inside the Starter plan’s monthly allowance even alongside your other workflows. For a store doing thousands of orders the only real cost is the extra few seconds the Get All step spends paging through order history, which the schedule absorbs because it runs while you sleep.

Ready-to-import template CTA

Skip the build. The guide above is free to follow end to end. If you would rather not wire six nodes and paste two scripts, the ready-to-import template drops the whole workflow into n8n in under a minute. Import the JSON, plug in your three credentials, set your email address, and activate.

Download the template ($14) →

Want it built, connected, and running in your own store without touching n8n? Our done-for-you setup service handles the whole thing.

FAQ

What is customer lifetime value in Shopify?

Customer lifetime value, or CLV, is the total revenue one customer has generated across every order they have ever placed with your store. It is a history metric, not a forecast. This workflow calculates it directly from your real order data, so the number reflects actual spend rather than a modelled estimate.

How is CLV different from RFM segmentation?

RFM scores customers on recency, frequency, and monetary value together to sort them into segments for targeting. CLV is the single monetary figure on its own: total lifetime spend. Use this report to find your highest spenders, and an RFM workflow when you also need recency and frequency to decide who to re-engage.

Can I calculate CLV for only the last 12 months?

Yes. Add a created-at filter to the Shopify Get All node, or filter inside the Code node by comparing each order’s created_at to a cutoff date. A rolling twelve-month window is useful when you want value that reflects recent behaviour rather than a customer’s entire history.

Does this send anything to my customers?

No. The only email goes to you, the store owner, as an internal report. Customer names and emails are read from your orders and written to your own Google Sheet and inbox. Nothing is sent to customers and no data leaves your Google and Shopify accounts.

Will it work on WooCommerce?

The pattern is identical. Swap the Shopify node for the WooCommerce node set to get all orders, then adjust the field names in the Code node to match WooCommerce’s order shape, such as total and the billing email. The aggregation, Sheets append, and Gmail steps stay exactly the same.

Related guides







Shopify inventory valuation report with n8n (weekly, at cost)









A Shopify inventory valuation report in n8n tells you what your on-hand stock is actually worth at cost, refreshed automatically every week instead of exported by hand. This guide builds a five-node workflow that queries your catalog through the Shopify GraphQL API, multiplies each variant’s quantity by its unit cost, logs the running total to Google Sheets so you can watch the trend, and emails you the headline number. It runs on n8n Cloud or self-hosted, and every service it touches has a free tier.

What it does

Your inventory is one of the largest assets your store owns, but Shopify never shows you its total value in one place. The admin lists quantities per variant; it does not sum quantity times cost across the whole catalog. So when your accountant asks for the closing stock value, or your insurer wants a figure, or you just want to know how much cash is tied up on the shelves, you end up exporting a CSV and building a spreadsheet formula every single time.

This workflow does that math for you on a schedule. Once a week it reads every product and variant, values the on-hand quantity at the cost you entered in Shopify, and produces three things:

  • A single headline number, the total on-hand value of your stock at cost, emailed to you.
  • A new row appended to a Google Sheet so the weekly figures build into a trend line over time.
  • A count of variants that have no cost set, so you know how complete the valuation is.

It is part of the broader pattern of Shopify automation with n8n: let a scheduled workflow pull the numbers out of the store so you stop doing the export-and-formula dance by hand.

Why it beats the default

The manual route is a Shopify export, a pivot table, and a VLOOKUP against a cost list, repeated whenever someone needs the figure. Reports apps in the Shopify App Store can do it, but they charge a monthly fee for a number you calculate once a week, and they still value at retail unless you pay for a higher tier.

Running it in n8n flips that. You own the logic, so you can change the schedule, the recipients, or the math without a subscription. Valuing at cost is built in, because the workflow reads the unit cost straight from each Shopify inventory item. And because every run appends a dated row to a sheet, you get something no one-off export gives you: a history. Watching on-hand value climb before a holiday season or bloat with dead stock is far more useful than a single snapshot.

New to connecting the two tools? Set up the credential first with our guide on how to connect Shopify to n8n in 2026, then come back here.

What you need

  • An n8n instance (Cloud or self-hosted, version 1.0 or later).
  • A Shopify custom app access token with read_products and read_inventory scopes, created through the 2026 Shopify Dev Dashboard method.
  • Unit costs entered on your product variants in Shopify (the cost per item field). Variants without a cost are skipped and counted.
  • A Google account for the trend sheet and a Gmail account for the email. Both free tiers are plenty.

Build time: about 25 minutes from scratch, or a couple of minutes if you import the ready-made template below.

Node-by-node list

Five nodes, wired in a straight line that forks at the end so the sheet and the email both receive the computed result:

# Node Type Job
1 Weekly schedule Schedule Trigger Fires the workflow every Monday at 7am.
2 Query Shopify inventory HTTP Request Sends one GraphQL query to Shopify for every variant’s quantity and unit cost.
3 Compute valuation Code Flattens the response and sums quantity times cost into the total.
4 Append to trend sheet Google Sheets Adds a dated row with the totals so the figures build a trend.
5 Email the report Gmail Emails you the headline number and the skipped-variant count.
[Weekly schedule] --> [Query Shopify inventory] --> [Compute valuation] --+--> [Append to trend sheet]
                                                                          |
                                                                          +--> [Email the report]

Step-by-step build

1. Weekly schedule (Schedule Trigger)

Add a Schedule Trigger node. Set the interval to a cron expression and enter 0 7 * * 1, which means 7am every Monday. This is the only trigger in the workflow, so everything downstream runs on this cadence.

2. Query Shopify inventory (HTTP Request)

Add an HTTP Request node. Rather than the Shopify node’s paginated product loop, a single GraphQL query pulls exactly the fields you need, quantity and cost, in one call.

  1. Set Method to POST.
  2. Set URL to https://your-store.myshopify.com/admin/api/2026-04/graphql.json (swap in your store’s subdomain).
  3. Under Authentication, choose Predefined Credential Type, then Shopify Access Token API, and select your credential.
  4. Turn on Send Body, set Body Content Type to JSON, and paste this into the JSON body:
{
  "query": "query InventoryValuation { products(first: 250) { edges { node { title variants(first: 100) { edges { node { sku inventoryQuantity inventoryItem { unitCost { amount } } } } } } } } }"
}
📌

The access token must carry the read_products and read_inventory scopes. Without read_inventory the unitCost field comes back null and every variant looks like it has no cost.

3. Compute valuation (Code)

Add a Code node set to run once for all items. It walks the GraphQL response, values each variant at cost, and returns one clean summary object.

const resp = $input.first().json;
const products = (resp.data && resp.data.products && resp.data.products.edges) || [];

let totalValue = 0;
let totalUnits = 0;
let variantsMissingCost = 0;
const rows = [];

for (const p of products) {
  const title = p.node.title;
  const variants = (p.node.variants && p.node.variants.edges) || [];
  for (const v of variants) {
    const node = v.node;
    const qty = node.inventoryQuantity || 0;
    const unitCost = node.inventoryItem && node.inventoryItem.unitCost
      ? parseFloat(node.inventoryItem.unitCost.amount)
      : null;
    if (unitCost === null || Number.isNaN(unitCost)) {
      variantsMissingCost++;
      continue;
    }
    const lineValue = qty * unitCost;
    totalValue += lineValue;
    totalUnits += qty;
    rows.push({ product: title, sku: node.sku || '', quantity: qty, unit_cost: unitCost, line_value: Math.round(lineValue * 100) / 100 });
  }
}

const today = new Date().toISOString().slice(0, 10);

return [{ json: {
  date: today,
  total_on_hand_value: Math.round(totalValue * 100) / 100,
  total_units: totalUnits,
  variants_missing_cost: variantsMissingCost,
  rows
} }];

After this node runs, the data looks like this:

{
  "date": "2026-08-26",
  "total_on_hand_value": 48213.75,
  "total_units": 3140,
  "variants_missing_cost": 4,
  "rows": [
    { "product": "Cedar Camp Mug", "sku": "MUG-CED-12", "quantity": 220, "unit_cost": 3.10, "line_value": 682.00 }
  ]
}

4. Append to trend sheet (Google Sheets)

Add a Google Sheets node, operation Append. Point it at a spreadsheet with a tab named Valuation whose header row reads date, total_on_hand_value, total_units, variants_missing_cost. Map each column to the matching field from the previous node. Every weekly run adds one row, and the column of dated totals becomes your trend line.

5. Email the report (Gmail)

Add a Gmail node, operation Send. Set the recipient to your address, and use expressions for the subject and body so the numbers fill in automatically:

Subject: Inventory valuation: {{ $json.total_on_hand_value }} on-hand ({{ $json.date }})

On-hand inventory value at cost: {{ $json.total_on_hand_value }}
Total units in stock: {{ $json.total_units }}
Variants skipped (no cost set): {{ $json.variants_missing_cost }}

Wire node 3 to both node 4 and node 5 so the sheet and the email each receive the computed summary. Save, toggle the workflow active, and it will report itself every Monday morning.

💡

Want a Slack or Telegram ping instead of email? Swap the Gmail node for a Telegram or Slack node and reuse the same expressions. The upstream logic does not change.

Common mistakes

  • Valuing at retail by accident. This report uses unitCost, not the variant price. If you want retail value instead, read price in the query and the Code node, but keep the two figures separate so you do not confuse asset value with sales value.
  • Missing the read_inventory scope. Cost lives on the inventory item, not the variant. If your token only has read_products, every unitCost returns null and the total reads zero. Add the scope and reissue the token.
  • Assuming it covers a huge catalog. The query fetches the first 250 products and 100 variants each in one call. Above that, add cursor pagination (pageInfo { hasNextPage endCursor }) and loop until hasNextPage is false before computing the total.
  • Sending the body as form data. The HTTP Request must use Body Content Type JSON with the query in the JSON body. Sending it as form fields makes Shopify reject the GraphQL request.
  • Blank costs skewing your read. A large variants_missing_cost count means the total understates reality. Fill costs in Shopify before trusting the figure for accounting.

Cost at realistic volume

This is about as cheap as automation gets. The workflow runs once a week, so four or five executions a month, each making a single GraphQL call to Shopify.

Service Usage per month Cost
Shopify GraphQL API ~5 calls Free (included with your plan)
Google Sheets ~5 appended rows Free
Gmail ~5 emails Free
n8n ~5 executions Free self-hosted, or a handful of Cloud executions

Even on n8n Cloud’s starter plan, five executions a month is a rounding error against your quota. Self-hosted, it is genuinely free.

Get the inventory valuation template

The full guide above is free to follow. If you would rather skip the build, download the ready-made workflow, import the JSON, add your credentials, and have your first valuation in a couple of minutes. Prefer it done for you? Our done-for-you service installs and tailors it to your store.

Download the template ($19) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

Does this value my stock at cost or at retail price?

At cost. The workflow reads each variant’s unit cost from Shopify’s inventory item (the cost per item field) and multiplies it by the on-hand quantity. That gives you the accounting value of your inventory as an asset, which is different from what the same stock would sell for at retail.

What if I have not set a cost per item on my products?

Any variant with no unit cost is skipped and counted in the variants_missing_cost field of the report, so you know how complete the number is. Fill in the cost per item on each variant in Shopify, or use the bulk editor to add costs, and the next weekly run will include them.

My store has more than 250 products. Will it capture everything?

The template fetches the first 250 products and the first 100 variants per product in a single GraphQL call, which covers most small and mid-size catalogs. For larger stores, add cursor-based pagination to the query so it pages through every product before computing the total. The guide explains where that goes.

How often does the report run?

The Schedule Trigger is set to run every Monday at 7am on a weekly cron. You can change it to daily, monthly, or any interval by editing the cron expression in the first node. Inventory value moves slowly, so weekly is a sensible default for most stores.

Does this work on n8n Cloud and self-hosted n8n?

Yes, both. The workflow uses only core nodes plus the Shopify, Google Sheets, and Gmail integrations, all of which ship with n8n Cloud and self-hosted installs. You only need to attach your own credentials after importing the template.

Related guides

Shopify dead stock report in n8n: find unsold inventory









A Shopify dead stock report in n8n tells you which products are quietly eating your cash: units sitting in the warehouse that nobody has bought in weeks. Shopify shows you what sells, but it has no built-in view for what does not. This guide builds a free workflow that runs every week, cross-checks your full catalog against the last 60 days of orders, flags every variant that still has inventory but zero sales, and emails you a ranked digest plus a running log in Google Sheets. The build takes about 30 minutes, or a couple of minutes if you import the ready-made template at the end.

What it does

The workflow answers one uncomfortable question on a schedule: which SKUs am I paying to store while they earn nothing? Every Monday morning it pulls your entire product list and every order from the last 60 days, matches them up, and produces a list of variants that have stock on hand but made no sales in that window.

Each flagged row includes the product name, variant, SKU, units on hand, units sold in the window (zero, by default), and the capital tied up in that stock (unit price times quantity). The list is sorted so the most expensive dead stock sits at the top, then it lands in your inbox as a clean table and appends to a Google Sheet so you can watch the trend over time.

Because it reads live Shopify data through the Admin API, there is nothing to maintain. If a slow product finally sells, it drops off next week’s report on its own. If a new product stalls, it appears. To connect n8n to your store first, follow connect Shopify to n8n (2026 method), which covers the current Dev Dashboard credential flow.

Why it beats the default

Shopify’s own reporting is built around sales. The ABC analysis and sell-through reports rank what moves; nothing in the admin surfaces the inverse, the products that are not moving at all. To find dead stock by hand you would export products, export orders, line them up in a spreadsheet, and subtract, which is exactly the kind of chore that gets done once and then never again.

Third-party inventory apps do offer dead stock reports, but they charge a monthly fee, want broad access to your store, and lock the logic inside their dashboard. This workflow keeps the definition of dead stock in your hands: 60 days and zero sales by default, but a two-number edit away from a 90-day, slow-moving definition. It runs on n8n you already control, writes to a Sheet you own, and costs nothing beyond the schedule it runs on. For the wider picture of what else you can automate on a store, see the pillar guide on n8n Shopify automation.

What you need

  • An n8n instance (Cloud or self-hosted, version 1.0 or newer).
  • A Shopify store with Admin API access, connected to n8n with an access token. Admin API version 2026-04 is current.
  • A Google account for Google Sheets, and a Gmail account for the digest email.
  • A blank Google Sheet with a header row (the workflow appends to it).

Time: about 30 minutes from scratch, or under 5 minutes if you import the template and fill in your credentials.

Node-by-node list

Seven nodes, one clean line with a small fork at the end so the report goes to two places at once.

Weekly schedule (Mon 7am)
        |
   Get products  ──►  Get orders (last 60 days)
                             |
                      Find dead stock  (Code: join + flag)
                        |             |
        Save to Google Sheets     Build email summary
                                       |
                                Send email digest (Gmail)
  
  1. Weekly schedule (Schedule Trigger) fires every Monday at 7am.
  2. Get products (Shopify) pulls the full catalog with all variants.
  3. Get orders (last 60 days) (Shopify) pulls every order created in the window.
  4. Find dead stock (Code) joins the two data sets and outputs one row per dead variant.
  5. Save to Google Sheets (Google Sheets) appends every flagged row to your log.
  6. Build email summary (Code) rolls the rows into one HTML table.
  7. Send email digest (Gmail) emails you the summary.

Step-by-step build

1. Add the weekly schedule

Add a Schedule Trigger. Set the interval to Weeks, every 1 week, trigger day Monday, at hour 7. Monday morning means the report is waiting when you plan the week. You can switch it to daily while you test, then move it back to weekly once you trust the output.

2. Get products

Add a Shopify node, resource Product, operation Get All, and turn on Return All. This returns every product, each with its full variants array. Every variant carries the two fields the report depends on: inventory_quantity and sku.

💡

Tip: Leave the filters empty. You want the whole catalog so that products with zero sales are included; a sales filter here would hide exactly what you are hunting for.

3. Get orders from the last 60 days

Add a second Shopify node, resource Order, operation Get All, Return All on. Under Filters, set Created At Min to an expression and Status to any:

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

In this node’s Settings tab, turn on Execute Once. The products node emits many items; without Execute Once, the orders node would run once per product. Execute Once makes it fetch the order history a single time, which is both correct and far faster.

📌

Note: Status any includes open, closed, and cancelled orders. Cancelled orders still show real demand, so counting them keeps a product that sold and was refunded from being mislabeled as dead.

4. Find dead stock (the Code node)

Add a Code node. It reads both Shopify nodes by reference, sums sales per SKU (with variant ID as a fallback), and outputs one item per variant that still holds stock but sold nothing.

const DAYS = 60;
const SALES_THRESHOLD = 0; // units sold at or below this = dead

const products = $('Get products').all().map(i => i.json);
const orders = $('Get orders (last 60 days)').all().map(i => i.json);

const soldBySku = {};
const soldByVariant = {};
for (const order of orders) {
  for (const li of (order.line_items || [])) {
    const qty = Number(li.quantity) || 0;
    if (li.sku) soldBySku[li.sku] = (soldBySku[li.sku] || 0) + qty;
    if (li.variant_id != null) soldByVariant[li.variant_id] = (soldByVariant[li.variant_id] || 0) + qty;
  }
}

const reportDate = new Date().toISOString().slice(0, 10);
const rows = [];
for (const p of products) {
  for (const v of (p.variants || [])) {
    const onHand = Number(v.inventory_quantity) || 0;
    if (onHand <= 0) continue;
    const sold = (v.sku && soldBySku[v.sku] != null) ? soldBySku[v.sku] : (soldByVariant[v.id] || 0);
    if (sold > SALES_THRESHOLD) continue;
    const price = Number(v.price) || 0;
    rows.push({
      report_date: reportDate,
      product: p.title,
      variant: v.title === 'Default Title' ? '' : v.title,
      sku: v.sku || '',
      on_hand: onHand,
      units_sold_60d: sold,
      unit_price: price,
      tied_up_value: Math.round(price * onHand * 100) / 100
    });
  }
}

rows.sort((a, b) => b.tied_up_value - a.tied_up_value);
return rows.map(r => ({ json: r }));

A single flagged row looks like this:

{
  "report_date": "2026-08-24",
  "product": "Cedar Camp Mug",
  "variant": "12 oz / Slate",
  "sku": "CCM-SLT-12",
  "on_hand": 84,
  "units_sold_60d": 0,
  "unit_price": 18.00,
  "tied_up_value": 1512.00
}
💡

Tip: To catch slow movers rather than only stone-dead stock, raise SALES_THRESHOLD to 2 and DAYS to 90. Now anything that sold two or fewer units in 90 days is flagged.

5. Save to Google Sheets

Add a Google Sheets node, operation Append Row. Pick your document and sheet, and set the mapping mode to Map Automatically. Because the Code node’s field names (report_date, product, sku, and so on) match your header row, each field drops into the right column with no manual mapping. Appending rather than overwriting builds a history you can chart later.

6. Build the email summary

Add a second Code node. It takes all the flagged rows and collapses them into a single item holding one HTML table, so the next node sends one email instead of one per row.

const items = $input.all().map(i => i.json);
if (items.length === 0) return [];

const count = items.length;
const reportDate = items[0].report_date;
const totalValue = items.reduce((s, r) => s + (Number(r.tied_up_value) || 0), 0);

let rowsHtml = '';
for (const r of items.slice(0, 100)) {
  rowsHtml += '<tr><td>' + r.product + '</td><td>' + r.sku +
    '</td><td>' + r.on_hand + '</td><td>' + Number(r.tied_up_value).toFixed(2) + '</td></tr>';
}

const html = '<h2>Dead stock report - ' + reportDate + '</h2>' +
  '<p>' + count + ' SKUs are holding stock but sold nothing in 60 days. ' +
  'Capital tied up: ' + totalValue.toFixed(2) + '.</p>' +
  '<table><tr><th>Product</th><th>SKU</th><th>On hand</th><th>Value</th></tr>' + rowsHtml + '</table>';

return [{ json: { subject: 'Dead stock report: ' + count + ' SKUs (' + reportDate + ')', html } }];

7. Send the digest

Add a Gmail node, operation Send. Set the recipient to your address, set Email Type to HTML, and map the subject and message to the fields from the previous node:

Subject:  ={{ $json.subject }}
Message:  ={{ $json.html }}

Save the workflow and toggle it Active. When there is no dead stock, the summary node returns nothing and no email is sent, so a quiet inbox means a healthy catalog.

Common mistakes

  • Skipping Execute Once on the orders node. Without it, the orders pull runs once per product and the workflow crawls. Turn it on in the node’s Settings tab.
  • Filtering products by inventory in the Shopify node. The point is to see everything and let the Code node decide. Filter early and you hide the stock you are looking for.
  • Header row that does not match. Automatic mapping in Google Sheets keys off exact column names. If your header says Product Name but the field is product, that column stays blank. Match them.
  • Reading inventory_quantity as reliable across locations. This field is the total across locations. If you run multiple warehouses and need a per-location view, that is a different report (see the multi-location guide below).
  • Forgetting cancelled orders. Setting Status to any matters. Leave it at the default and a refunded sale can make a real seller look dead.

Cost at realistic volume

This workflow uses no paid AI and no premium services, so the running cost is essentially zero. A weekly run makes two Shopify API calls (paginated), one Google Sheets append, and one Gmail send.

Service Usage per run Cost
Shopify Admin API 2 paginated pulls Free (within rate limits)
Google Sheets 1 append (many rows) Free
Gmail 1 email Free
n8n 1 execution / week Free self-hosted; ~4 executions/month on Cloud

On n8n Cloud a weekly schedule costs about four executions a month, a rounding error against the tier you are already paying for. Self-hosted, it is free. The real return is the capital you free up by spotting dead stock early and clearing it before it ages further.

Get the dead stock report template

The full guide above is free to follow. If you would rather skip the build, the ready-to-import template drops all seven nodes and both Code scripts straight onto your canvas. Import it, add your Shopify, Google Sheets, and Gmail credentials, and your first report can run today. Prefer it done for you? Our done-for-you service installs and tailors it to your store.

Download the template ($14) →

Instant download · Works on n8n Cloud and self-hosted

Frequently asked questions

What counts as dead stock in this workflow?

A variant is flagged when it still has inventory on hand and sold zero units in the last 60 days. Both the window and the sales threshold are single numbers at the top of the Code node, so you can loosen the definition to slow-moving stock in one edit.

Will the report double-count variants sold under different SKUs?

No. Sales are summed by SKU first and by variant ID as a fallback, so each variant is matched to its own orders. Variants with a blank SKU still match on variant ID, which is how Shopify links a line item back to the exact variant.

Does it work with a large catalog?

Yes. Both Shopify pulls use Return All, so the workflow paginates through every product and every order in the window. For very large stores the run takes longer, but it stays inside the Admin API rate limits because n8n throttles the paged requests automatically.

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

Yes. The Build email summary node produces a single item with the table already built, so you can swap the Gmail node for a Telegram or Slack node and point it at the same field. The Google Sheets branch stays exactly as it is.

How is dead stock different from a low-stock alert?

A low-stock alert fires when you are about to run out of a product that sells. A dead stock report does the opposite: it finds products you have too much of because nobody is buying them. The two reports use the same data but flag opposite problems.

Related guides

Shopify SEO Meta Description Generator With n8n and Gemini










A Shopify SEO meta description generator built in n8n reads every product in your store, asks Google Gemini to write a search-optimized meta title and meta description, then writes both straight back to Shopify as SEO metafields. Instead of hand-typing search snippets for hundreds of products, you run one workflow on a weekly schedule and let it fill the fields that decide how your products look in Google results. This guide builds the full workflow node by node.

What it does

Shopify gives every product two SEO fields under “Search engine listing”: the page title and the meta description. These map to the global.title_tag and global.description_tag metafields. Most stores leave them blank, so Google falls back to the raw product title and a truncated slice of the description. That fallback rarely reads well and rarely includes the words shoppers actually search for.

This workflow closes that gap automatically. On a weekly schedule it pulls your products from the Shopify Admin API, sends each one to Gemini with a tight prompt, and receives a clean meta title (under 60 characters) and meta description (under 155 characters). It parses the model’s JSON response and pushes both values back onto the product as SEO metafields in a single API call. The next time Google crawls the page, it sees a written-for-search snippet instead of a truncated guess.

Why it beats the default

Writing meta descriptions by hand does not scale. A store with 300 products needs 300 titles and 300 descriptions, each under a character limit, each worded for search intent. Most owners write a handful, get bored, and leave the rest empty.

Paid SEO apps in the Shopify App Store will do this, but they charge a recurring monthly fee and lock your snippets inside their dashboard. This n8n version runs on infrastructure you already control, uses the Gemini free tier, and writes directly to native Shopify fields with no third-party app sitting in the middle. You own the prompt, so you control the tone, the keyword emphasis, and the length. Change one line and every future product follows the new rule.

It also stays current. Because it runs on a schedule, new products added during the week get their SEO fields filled on the next run without anyone remembering to do it.

What you need

  • A running n8n instance (self-hosted or cloud).
  • A Shopify custom app access token with read_products and write_products scopes. Follow connect Shopify to n8n (2026 method) to create the app in the Dev Dashboard and copy the token.
  • A Google Gemini API key from Google AI Studio. The free tier covers the request volume most stores need.
  • Two n8n Header Auth credentials: one holding the Shopify X-Shopify-Access-Token header, one holding the Gemini x-goog-api-key header.

For the wider picture of how Shopify and n8n fit together, the n8n Shopify automation pillar guide walks through auth, nodes, and the common patterns this workflow builds on.

Node-by-node list

  • Schedule Trigger: fires the workflow every Monday at 6am.
  • HTTP Request (Get products): GET the Shopify products endpoint, returning id, title, body_html, product_type, and vendor.
  • Split Out: turns the single products array into one item per product so the rest of the flow runs once per product.
  • HTTP Request (Generate SEO meta): POST each product to the Gemini generateContent endpoint and ask for strict JSON.
  • Edit Fields (Parse Gemini output): read the model’s JSON string and pull out seo_title, seo_description, and the original product_id.
  • HTTP Request (Write SEO metafields): PUT the two metafields back onto the product in one call.

Step-by-step build

  1. Add a Schedule Trigger and set it to run weekly on Monday at 06:00. A weekly cadence is enough for a catalog that changes slowly; move it to daily if you add products often.
  2. Add an HTTP Request node named “Get products”. Method GET, URL https://YOURSTORE.myshopify.com/admin/api/2026-04/products.json?limit=50&fields=id,title,body_html,product_type,vendor. Set Authentication to Generic Credential Type, Header Auth, and select your Shopify credential holding X-Shopify-Access-Token.
  3. Add a Split Out node. Set “Field to Split Out” to products. Now each downstream node runs once per product.
  4. Add an HTTP Request node named “Generate SEO meta (Gemini)”. Method POST, URL https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent. Authentication is Generic Credential Type, Header Auth, using your Gemini credential. Set Body Content Type to JSON and paste an expression that builds the request with JSON.stringify so quotes in your product text cannot break the payload. The prompt asks for a meta title under 60 characters and a description under 155, returned as strict JSON with keys seo_title and seo_description, and sets responseMimeType to application/json.
  5. Add an Edit Fields node named “Parse Gemini output”. Add three string assignments: product_id set to {{ $('Split products').item.json.id }}, seo_title set to {{ JSON.parse($json.candidates[0].content.parts[0].text).seo_title }}, and seo_description set to the same parse with .seo_description. Pulling the id from the Split node keeps each product matched to its own meta text.
  6. Add a final HTTP Request node named “Write SEO metafields to Shopify”. Method PUT, URL https://YOURSTORE.myshopify.com/admin/api/2026-04/products/{{ $json.product_id }}.json, Header Auth with the Shopify credential. Body Content Type JSON, sending a product object with a metafields array: one entry for global.title_tag (type single_line_text_field) and one for global.description_tag (type multi_line_text_field).
  7. Run the workflow once manually with a small limit to confirm the SEO fields fill in on a test product, then raise the limit and let the schedule take over.

Common mistakes

Overwriting snippets you already wrote by hand

This build writes to every product it fetches. If you have hand-crafted meta descriptions on your best sellers, add a filter after Split Out that only keeps products where the SEO metafields are empty, or narrow the initial fetch to a specific collection so it never touches pages you already optimized.

Ignoring the character limits

Google truncates titles around 60 characters and descriptions around 155. The prompt asks for those limits, but the model sometimes runs long. Keep the limit in the prompt and, if you want a hard guarantee, add a short expression that slices the string before it reaches Shopify.

Sending malformed JSON to Gemini

Product descriptions contain quotes and line breaks that break a hand-typed JSON body. Building the body with JSON.stringify in an expression escapes everything for you. Do not paste raw product text between quotation marks in the body field.

Missing the write scope

A token with only read_products fetches products fine but fails silently on the write step. Confirm the custom app has write_products before you blame the workflow.

Cost at realistic volume

The only metered cost is Gemini, and gemini-2.5-flash on the free tier handles a large batch of short prompts per day at no charge. A 300-product store makes 300 small text requests on each run. On a weekly schedule that is 300 requests a week, well inside the free tier. Shopify API calls are free within the standard rate limits, and the workflow’s paced, one-product-at-a-time flow stays under them. If you outgrow the free Gemini tier, the paid rate for flash-class models keeps a full-catalog run in the range of a few cents. Compared with a recurring SEO app subscription, the running cost here is effectively zero.

Ready-to-import template

The guide above is free to follow and builds the complete workflow by hand. If you would rather skip the setup, the ready-to-import template drops all six nodes into n8n already wired, so you only add your two credentials and your store URL.

Download the template ($19) →

Want it built and running in your store without touching n8n? Our done-for-you automation service installs and configures it for you.

FAQ

Does this change my product descriptions on the storefront?

No. It only writes the SEO metafields, which control the title and description Google shows in search results. Your on-page product title, body copy, images, and price stay exactly as they are. The workflow never touches the visible product content that shoppers read on the page.

How often should the workflow run?

Weekly suits most stores because catalogs change slowly. If you add products several times a week, switch the Schedule Trigger to daily so new items get their SEO fields filled quickly. There is no cost penalty to running more often within the Gemini free tier and Shopify rate limits.

Will it overwrite meta descriptions I wrote myself?

By default yes, because it processes every product it fetches. Add a filter after the Split Out node that keeps only products with empty SEO metafields, or point the fetch at a single collection. That way the workflow fills the gaps and leaves your hand-written snippets untouched.

Which Gemini model does it use?

The build uses gemini-2.5-flash, a fast, low-cost model that handles short SEO copy well and stays inside the free tier for typical catalog sizes. You can swap the model name in the URL for a larger one if you want longer or more nuanced copy, at a slightly higher cost per run.

Do I need a paid SEO app as well?

No. This workflow writes to the same native Shopify SEO fields a paid app would edit, using the Admin API directly. You keep full control of the prompt and pay nothing recurring. An app still helps with sitewide audits, but for generating product-level meta text this covers the job.

Related guides

Shopify Duplicate SKU Finder in n8n (Weekly Audit)

A Shopify duplicate SKU finder is a small n8n workflow that scans every product variant in your store on a weekly schedule, flags any SKU that is attached to more than one variant, and emails you the list. Duplicate SKUs quietly break inventory sync, fulfillment, and analytics, yet Shopify never warns you when you create one. This guide walks through building the audit in five nodes with a Shopify GraphQL call and a Code node, and hands you a ready-to-import template if you would rather skip the build.

What it does

The workflow runs once a week on a schedule trigger. It calls the Shopify Admin GraphQL API for your product variants, then a Code node groups every variant by its SKU and keeps only the SKUs that appear on two or more variants. If any duplicates exist, you get a plain-text email listing each duplicated SKU and the product variants that share it. If the store is clean, nothing is sent, so a quiet inbox means a healthy catalog.

Duplicate SKUs are the kind of data problem that never announces itself. A bulk CSV import reuses a code, a supplier feed collides with an existing product, or someone copies a variant and forgets to change the SKU. From then on, any tool that keys on SKU, third-party inventory sync, 3PL fulfillment, accounting exports, sees two things as one. This is exactly the sort of background hygiene check that belongs in Shopify automation with n8n rather than a manual spreadsheet audit you will forget to run.

Why it beats the default

Shopify has no built-in duplicate SKU report. The admin lets you save two variants with the same SKU without a single warning, and there is no screen anywhere that lists collisions. Your options inside Shopify are to export the full product CSV and pivot it by hand, or to click through products hoping to spot a repeat. Both are slow, both are easy to skip, and neither runs on its own.

The n8n version flips that. It is proactive instead of reactive: the check runs every week whether or not you remember, and it only speaks up when something is actually wrong. Because it reads straight from the Admin API, it always reflects the live catalog, not a stale export. And because it is just five nodes, you can extend it later to log to Google Sheets, post to Slack, or run daily during a big import week.

What you need

  • An n8n instance (cloud or self-hosted). Any recent version works.
  • A Shopify Admin API access token with read_products scope. Create it with the 2026 Dev Dashboard method described in how to connect Shopify to n8n. The old in-admin custom app flow has been removed.
  • A Gmail account for the alert email (any SMTP or Outlook account works too if you swap the last node).
  • About 20 minutes to build from scratch, or a couple of minutes with the template below.

Node-by-node list

Five nodes, wired in a straight line with one branch at the end:

[Weekly Schedule] -> [Get Shopify Variants] -> [Find Duplicate SKUs] -> [Any Duplicates?]
                                                                             |
                                                                     true -> [Email Duplicate Report]
                                                                     false -> (stop, no email)
  
  1. Weekly Schedule (Schedule Trigger) fires the run every Monday morning.
  2. Get Shopify Variants (HTTP Request) posts a GraphQL query to the Admin API and returns your product variants with their SKUs.
  3. Find Duplicate SKUs (Code) groups variants by SKU and builds a report of the duplicates.
  4. Any Duplicates? (IF) checks whether the Code node found anything.
  5. Email Duplicate Report (Gmail) sends the list, and only runs when duplicates exist.

Step-by-step build

Step 1 — Weekly Schedule (Schedule Trigger)

Add a Schedule Trigger node. Set the rule to an interval of Weeks, every 1 week, triggering on day Monday at hour 7. This gives you a fresh audit at the start of each week. If you are in the middle of a large catalog import, you can temporarily switch the interval to daily.

Step 2 — Get Shopify Variants (HTTP Request)

Add an HTTP Request node named Get Shopify Variants and configure it:

  1. Method: POST
  2. URL: https://YOUR_STORE.myshopify.com/admin/api/2026-04/graphql.json
  3. Authentication: Generic Credential Type, then Header Auth. Create a Header Auth credential with name X-Shopify-Access-Token and your Admin API token as the value.
  4. Send Headers: on. Add Content-Type = application/json.
  5. Send Body: on. Body Content Type: JSON. Paste this into the JSON body:
{
  "query": "{ productVariants(first: 250) { edges { node { id sku displayName product { id title } } } pageInfo { hasNextPage endCursor } } }"
}

After this node runs, each variant arrives shaped like the snippet below, nested under data.productVariants.edges:

{
  "node": {
    "id": "gid://shopify/ProductVariant/4820115...",
    "sku": "TEE-BLK-M",
    "displayName": "Classic Tee - Black / M",
    "product": { "id": "gid://shopify/Product/981...", "title": "Classic Tee" }
  }
}

Step 3 — Find Duplicate SKUs (Code)

Add a Code node named Find Duplicate SKUs, mode Run Once for All Items, and paste this JavaScript. It groups every non-empty SKU, keeps the ones used more than once, and returns a single item describing the result:

const edges = $input.first().json.data.productVariants.edges || [];
const map = {};
for (const e of edges) {
  const sku = (e.node.sku || '').trim();
  if (!sku) continue;
  (map[sku] = map[sku] || []).push(
    e.node.displayName || (e.node.product && e.node.product.title) || 'Unknown product'
  );
}
const dups = Object.entries(map).filter(([, arr]) => arr.length > 1);
if (dups.length === 0) {
  return [{ json: { hasDuplicates: false, count: 0, report: 'No duplicate SKUs found.' } }];
}
const lines = dups.map(([sku, arr]) =>
  `SKU "${sku}" is used on ${arr.length} variants: ${arr.join(', ')}`
);
return [{ json: { hasDuplicates: true, count: dups.length, report: lines.join('n') } }];
💡

Tip: empty SKUs are skipped on purpose. Many stores leave the SKU field blank on some variants, and blank is not a real collision, so counting it would flood the report with false positives.

Step 4 — Any Duplicates? (IF)

Add an IF node named Any Duplicates?. Create one condition with a Boolean type: left value {{ $json.hasDuplicates }}, operation is true. The true output carries the report forward; the false output goes nowhere, so a clean store sends no email.

Step 5 — Email Duplicate Report (Gmail)

Connect the IF node’s true output to a Gmail node named Email Duplicate Report. Configure:

  1. Resource: Message, Operation: Send
  2. To: your address, for example owner@yourstore.com
  3. Subject: Duplicate SKUs found in your Shopify store ({{ $json.count }})
  4. Email Type: Text
  5. Message: {{ $json.report }}

Save the workflow and toggle it Active. A typical alert email reads:

SKU "TEE-BLK-M" is used on 2 variants: Classic Tee - Black / M, Summer Tee - Black / M
SKU "MUG-12OZ" is used on 3 variants: Coffee Mug, Gift Mug, Holiday Mug

Common mistakes

  • The 250-variant page limit. A single productVariants(first: 250) call returns up to 250 variants. Stores larger than that need cursor pagination: read pageInfo.hasNextPage and endCursor, then loop the HTTP Request with after: "CURSOR" until hasNextPage is false. The base template audits the first 250, which covers most small and mid-size catalogs; do not assume it scanned everything if you sell thousands of variants.
  • Using OAuth instead of Header Auth. The HTTP Request node here expects a Header Auth credential named X-Shopify-Access-Token. If you pick the built-in Shopify OAuth credential type on an HTTP node it will not attach the token the way the GraphQL endpoint expects.
  • Wrong API version. Keep the URL on a current version such as 2026-04. An outdated or removed version returns a 404 or a deprecation error rather than your variants.
  • Comparing empty SKUs. If you edit the Code node, keep the blank-SKU skip. Treating empty string as a value marks every blank variant as a giant duplicate group.
  • Expecting an email every week. No email means no duplicates, which is the goal. If you want a weekly all-clear confirmation, branch the IF false output to a second Gmail node.

Cost at realistic volume

This workflow is effectively free to run. It uses no paid AI model and no metered service.

Component Usage per week Cost
Shopify Admin API 1 GraphQL call $0 (included with your plan)
Gmail send 0 to 1 email $0 (free tier)
n8n execution 1 run, a few seconds $0 self-hosted; a single execution on n8n Cloud

Even on n8n Cloud’s lowest tier, one weekly execution is a rounding error against your monthly quota. Running it daily during a heavy import period still costs nothing beyond a handful of executions.

Get the Shopify Duplicate SKU Finder template

The guide above is free to follow. If you would rather not build it node by node, download the ready-to-import n8n template, drop in your Shopify token and email, and run your first audit in a couple of minutes.

Download the template ($12) →

Instant download · works on n8n Cloud and self-hosted. Want it built and connected for you? See our done-for-you setup service.

Frequently asked questions

Does Shopify allow duplicate SKUs at all?

Yes. Shopify treats the SKU as a free-text label, not a unique key, so it will happily save the same SKU on many variants without any warning. That permissiveness is why an external audit is worth running, because nothing in the admin surfaces the collisions for you.

Will this scan every variant in a large store?

The base template reads the first 250 variants in one GraphQL call, which covers most small and mid-size catalogs. For larger stores, add cursor pagination using the pageInfo.hasNextPage and endCursor fields so the workflow loops until it has fetched every variant.

Can I log the duplicates to Google Sheets instead of email?

Yes. Change the Code node to output one item per duplicate group, then connect a Google Sheets Append node after the IF. You can keep the Gmail node too, so you get both a running log and an inbox alert on the same run.

How do I get the Shopify Admin API token?

Create a custom app in the 2026 Shopify Dev Dashboard, grant it the read_products scope, and install it to reveal the Admin API access token. Full steps are in our guide on connecting Shopify to n8n. Paste that token into an n8n Header Auth credential named X-Shopify-Access-Token.

What if I want it to run daily during an import?

Open the Weekly Schedule node and change the interval from weeks to days. The rest of the workflow is unchanged. When the import is finished and the catalog is stable, switch it back to weekly so your inbox stays quiet.

Related guides












Shopify packing slip email automation with n8n









Shopify packing slip email automation sends a clean, printable packing slip to your warehouse or fulfillment desk the instant an order is placed, so nobody has to open Shopify admin and export one by hand. This guide builds that flow in n8n with just three nodes: a Shopify order trigger, a Code node that renders the slip as HTML, and a Gmail node that emails it. It works on n8n Cloud and self-hosted, uses no paid service, and a ready-to-import template is linked at the end.

What it does

The workflow watches for new orders and turns each one into a pick-and-pack document, delivered by email.

  • A new Shopify order fires the workflow in real time through the orders/create webhook.
  • A Code node builds a tidy HTML packing slip: order number, date, shipping address, a line-item table with SKU and quantity, a total unit count, and any order note.
  • A Gmail node emails that slip to your warehouse address as an HTML message the team opens and prints.

The result is a hands-off fulfillment step. Orders reach the people picking them within seconds, formatted for the shop floor rather than buried in the admin. It slots neatly into a wider Shopify automation setup alongside inventory and shipping flows.

Why it beats the default

Shopify can print packing slips, but only one order at a time from the admin, and only when a human goes looking. During a busy drop, that manual step is where slips get missed and the wrong quantity gets picked.

Pushing the slip by email flips the model from pull to push. The warehouse does not log into Shopify at all; the document arrives the moment the order does, already formatted and ready to print. You also control exactly what appears on the slip. The default Shopify template is fixed, but here the layout is plain HTML in a Code node, so you can add a barcode column, a bin location, or a “requires cold pack” flag without fighting a theme editor.

Because it is an HTML email rather than a PDF render, there is no PDF engine to host and nothing that breaks on n8n Cloud. Staff print straight from the email with Ctrl+P. If you later need a true PDF attachment, the Common mistakes section explains how to add one.

What you need

  • A Shopify store with a custom app and an Admin API access token carrying read_orders scope. If you have not connected Shopify to n8n yet, follow connect Shopify to n8n (2026 method) first.
  • An n8n instance, Cloud or self-hosted (version 1.0 or newer).
  • A Gmail account connected to n8n through Gmail OAuth2, and the email address of your warehouse or fulfillment desk.
  • About 15 minutes to build from scratch, or a couple of minutes with the template below.

Node-by-node list

Three nodes, one straight line.

# Node Type Job
1 New Order Shopify Trigger Fires on the orders/create topic for every new order
2 Build Packing Slip HTML Code Turns the order payload into a printable HTML packing slip
3 Email Packing Slip to Warehouse Gmail Sends the slip as an HTML email to your fulfillment address
  [New Order] --> [Build Packing Slip HTML] --> [Email Packing Slip to Warehouse]
  

There is no data store and no schedule. The order payload from the trigger carries everything the slip needs, so the Code node reads it directly and the Gmail node sends the result. That is why the whole thing fits in three nodes and runs in well under a second per order. If you outgrow a single warehouse, every branch you add hangs off the same trigger, so the core stays exactly this simple.

Step-by-step build

  1. New Order (Shopify Trigger). Add a Shopify Trigger node, set Authentication to Access Token, pick your Shopify credential, and set Topic to orders/create. n8n registers the webhook with Shopify automatically. Its output is the full order object: name, created_at, shipping_address, line_items, and note.
  2. Build Packing Slip HTML (Code). Add a Code node set to run once for all items and paste the slip builder. It walks line_items into table rows, assembles the shipping address, sums the quantities into a pick total, and returns an html field:
    const order = $input.first().json;
    const ship = order.shipping_address || {};
    const rows = (order.line_items || []).map(li => {
      const name = li.title + (li.variant_title ? ' - ' + li.variant_title : '');
      return `<tr><td>${li.sku || '(no SKU)'}</td><td>${name}</td><td>${li.quantity}</td></tr>`;
    }).join('');
    const totalUnits = (order.line_items || []).reduce((s, li) => s + (li.quantity || 0), 0);
    // full HTML template with inline CSS ships in the download
    return [{ json: { orderName: order.name, totalUnits, html: '...' } }];

    The full node in the template builds the complete styled slip; the snippet above shows the shape.

  3. Email Packing Slip to Warehouse (Gmail). Add a Gmail node, Operation Send. Set To to your warehouse address, Subject to =Packing slip for order {{ $json.orderName }}, Email Type to HTML, and Message to ={{ $json.html }}. Connect the Code node to it, save, and toggle the workflow Active.

Place a test order in Shopify and the slip lands in the warehouse inbox within seconds, ready to print.

Common mistakes

  • Setting Email Type to Text. The slip is built as HTML with inline styles. Leave Email Type on HTML or the table and layout arrive as raw markup.
  • Missing read_orders scope. Without it the trigger cannot register or receive the order payload. Grant read_orders on the custom app and reconnect the credential.
  • Expecting a PDF attachment. This build emails HTML for portability. If you want a real PDF, add a Convert to File node then an HTTP Request to a Gotenberg /forms/chromium/convert/html endpoint and attach the returned binary in Gmail. That path needs a self-hosted Gotenberg service, so only add it if you run n8n with Docker.
  • Forgetting the order note. Gift messages and delivery instructions live in order.note. The template surfaces it in a highlighted box; do not strip it out or pickers lose that context.
  • Gmail sending limits. A standard Gmail account sends up to roughly 500 emails a day. High-volume stores should point the node at Google Workspace or switch to the SMTP node.

Cost at realistic volume

No paid service touches this workflow. The Shopify Admin API, the n8n Code node, and Gmail are all free within their normal limits.

Volume n8n executions / month Est. monthly cost
50 orders/month, self-hosted n8n 50 $0 beyond your server
300 orders/month, n8n Cloud Starter 300 Inside the Starter execution allowance
1,500 orders/month, n8n Cloud Pro 1,500 A modest slice of the Pro allowance

Each order is exactly one execution and one email. The only ceiling to watch is Gmail’s daily send limit, which a busy store clears by moving to Workspace or SMTP.

Ready-to-import template

Shopify packing slip email automation, ready to import

The full guide above is free to follow. If you would rather skip the build, the downloadable template is the exact three-node workflow from this post, with the complete styled packing-slip Code node and credential placeholders in place, ready to run after you add your Shopify and Gmail credentials.

Download the template ($9) →

Instant download · works on n8n Cloud and self-hosted. Want it built and connected for you? See our done-for-you setup service.

FAQ

Does this send a PDF or an HTML email?

It sends a clean HTML email that your warehouse opens and prints with Ctrl+P or Cmd+P. This keeps the workflow to three nodes and works on n8n Cloud with no extra infrastructure. If you need a true PDF attachment, you can add a Gotenberg render step, which requires a self-hosted PDF engine.

Will it fire on every order, including test orders?

Yes. The orders/create trigger fires for every new order, including manual and test orders. If you want to skip certain orders, add an IF node after the trigger to filter on tags, fulfillment location, or whether the order requires shipping before the email step.

Can I send to more than one warehouse address?

Yes. The Gmail node accepts a comma-separated list in the To field, so you can send the same slip to several addresses. For routing by location, add a Switch node keyed on the line item or order location and point each branch at a different Gmail recipient.

What order fields does the packing slip include?

Out of the box it shows the order number, order date, the full shipping address, a table of every line item with its SKU and quantity, a total unit count for picking, and any customer order note. You can add or remove fields by editing the HTML in the Code node.

Does this cost anything to run?

No paid service is involved. It uses the Shopify Admin API, an n8n Code node, and Gmail, all on their free tiers. Each order is one n8n execution and one email, so even a few hundred orders a month stay well inside a standard n8n plan and Gmail sending limits.

Related guides

Shopify new arrivals collection automation with n8n









Shopify new arrivals collection automation keeps your storefront looking fresh without a single manual edit: every product you create is added to a New Arrivals collection the moment it goes live, and any item older than 30 days drops out on its own. This guide builds that two-part workflow in n8n using only the Shopify Admin API, walks through every node, lists the real costs, and links a ready-to-import template if you would rather skip the build.

What it does

The workflow runs two independent jobs against one custom collection you name “New Arrivals”.

  • Auto-add: when a new product is created in Shopify, it is attached to the New Arrivals collection within seconds, so the collection page and any “just landed” section on your theme fill themselves.
  • Auto-expire: once a day, a scheduled job finds every product in that collection that is older than 30 days and removes it, so the collection never accumulates stale stock.

The result is a merchandising surface that stays current on its own. You get the marketing lift of a rotating New Arrivals row without anyone remembering to add or clear products. This is a classic building block of any Shopify automation stack, and it pairs well with new-product announcement emails.

Why it beats the default

Shopify already offers smart collections with a condition like “product added in the last X days”, so it is fair to ask why bother with n8n. Two reasons.

First, Shopify smart collections have no native “added in the last 30 days” condition. The available product-date conditions are limited, and merchants routinely fall back to manually tagging products with something like new and then remembering to remove the tag a month later. That manual tag cleanup is exactly the chore this workflow removes.

Second, a manual custom collection means you drag products in and out by hand. That is fine for ten products and unworkable for a catalog that adds items weekly. By driving a custom collection through the API, you get precise control over both the add and the expiry, on your own schedule, with a rule you can change in one place. You keep a real, editable collection you can also curate by hand when you want to, unlike a smart collection you cannot touch.

What you need

  • A Shopify store with a custom collection named “New Arrivals” (Products → Collections → Create collection → Collection type: Manual). Note its numeric ID.
  • An n8n instance, Cloud or self-hosted (version 1.0 or newer).
  • A Shopify Admin API access token with read_products and write_products scope, connected to n8n. If you have not connected Shopify yet, follow connect Shopify to n8n (2026 method) first, using the Dev Dashboard custom-app flow.
  • About 20 minutes to build from scratch, or a couple of minutes with the template below.

To find the collection ID, open the collection in Shopify admin and read the number at the end of the URL (/collections/123456789). That number is what goes into the workflow.

Node-by-node list

Eight nodes, in two disconnected branches that live in the same workflow.

# Node Type Job
1 New Product Created Shopify Trigger Fires on the products/create topic when a product is added
2 Add to New Arrivals HTTP Request POST a collect to attach the new product to the collection
3 Daily Prune Schedule Schedule Trigger Runs once a day at 03:00 to start the cleanup branch
4 Compute 30-Day Cutoff Set Builds the cutoff timestamp, now minus 30 days
5 Get Aged Products HTTP Request GET products in the collection created before the cutoff
6 Split Products Split Out Turns the products array into one item per product
7 Find Collect HTTP Request GET the collect ID that links the product to the collection
8 Remove from New Arrivals HTTP Request DELETE that collect to detach the product
  ADD BRANCH
  [New Product Created] --> [Add to New Arrivals]

  EXPIRE BRANCH (daily)
  [Daily Prune Schedule] --> [Compute 30-Day Cutoff] --> [Get Aged Products]
        --> [Split Products] --> [Find Collect] --> [Remove from New Arrivals]
  

Step-by-step build

Build the add branch first, then the expire branch. Every URL uses your store domain and Admin API version 2026-04.

  1. New Product Created (Shopify Trigger). Add a Shopify Trigger node, set Authentication to Access Token, choose your Shopify credential, and set Topic to products/create. n8n registers the webhook with Shopify for you. Its output is the full product object, including the product id.
  2. Add to New Arrivals (HTTP Request). Add an HTTP Request node. Method POST, URL https://YOUR_STORE.myshopify.com/admin/api/2026-04/collects.json. Set Authentication to Predefined Credential Type and pick Shopify Access Token API. Turn on Send Body, set Body Content Type to JSON, and paste this expression into the JSON field:
    ={{ { "collect": { "product_id": $json.id, "collection_id": YOUR_NEW_ARRIVALS_COLLECTION_ID } } }}

    Replace YOUR_NEW_ARRIVALS_COLLECTION_ID with the numeric collection ID. Connect node 1 to node 2. The add branch is done.

  3. Daily Prune Schedule (Schedule Trigger). Add a Schedule Trigger node. Set it to trigger every day, at hour 3. This is the entry point for the cleanup branch and connects to nothing above it.
  4. Compute 30-Day Cutoff (Set). Add a Set node. Create one string field named cutoff with the value ={{ $now.minus({ days: 30 }).toISO() }}. This is the only place the 30-day rule lives. Connect node 3 to node 4.
  5. Get Aged Products (HTTP Request). Method GET, URL https://YOUR_STORE.myshopify.com/admin/api/2026-04/products.json, same predefined Shopify credential. Turn on Send Query Parameters and add four: collection_id = your collection ID, created_at_max = ={{ $json.cutoff }}, fields = id,title,created_at, and limit = 250. This returns only products in the collection that are older than 30 days. Connect node 4 to node 5.
  6. Split Products (Split Out). Add a Split Out node and set Field To Split Out to products. The Shopify response is a single object with a products array, so this turns it into one n8n item per aged product. Connect node 5 to node 6.
  7. Find Collect (HTTP Request). Method GET, URL https://YOUR_STORE.myshopify.com/admin/api/2026-04/collects.json. Query parameters: collection_id = your collection ID, product_id = ={{ $json.id }}, fields = id. Shopify does not let you delete a product from a collection by product ID directly; you delete the collect that joins them, so this step looks up that collect. Connect node 6 to node 7.
  8. Remove from New Arrivals (HTTP Request). Method DELETE, URL =https://YOUR_STORE.myshopify.com/admin/api/2026-04/collects/{{ $json.collects[0].id }}.json, same credential. This detaches the aged product. Connect node 7 to node 8. Save the workflow and toggle it Active.

Once active, the add branch reacts to every new product in real time and the expire branch sweeps the collection each morning.

Common mistakes

  • Pointing at a smart collection. Collects only attach to custom (manual) collections. If your New Arrivals collection was created with conditions, the POST will fail. Recreate it as a Manual collection.
  • Using the collection handle instead of the numeric ID. The API wants the numeric collection_id, not the URL handle like new-arrivals. Read the number from the collection URL.
  • Forgetting write scope. A token with only read_products will read the aged products fine but fail on both the POST and the DELETE. Grant write_products as well.
  • Assuming removal unpublishes the product. Deleting a collect only takes the product out of that one collection. The product stays published and reachable everywhere else, which is the intended behavior.
  • Skipping the Split Out node. Without it, the DELETE branch only ever acts on the first product in the array. Split Out is what fans the batch into one item per product.

Cost at realistic volume

This workflow touches no paid third-party service. The only moving parts are the Shopify Admin API, which is free within its normal rate limits, and n8n itself.

Volume n8n executions / month Est. monthly cost
20 new products/month, self-hosted n8n ~50 (30 daily sweeps + ~20 adds) $0 beyond your server
100 new products/month, n8n Cloud Starter ~130 Well inside the Starter execution allowance
500 new products/month, n8n Cloud Pro ~530 A small slice of the Pro allowance

Each daily sweep is one execution regardless of how many products it prunes, because the loop runs inside a single run. Adds are one execution each. Even a busy store stays comfortably within a standard n8n plan, and self-hosted users pay nothing extra at all.

Ready-to-import template

Shopify new arrivals collection automation, ready to import

The full guide above is free to follow. If you would rather skip the build, the downloadable template is the exact eight-node workflow from this post, credential placeholders in place, ready to import and run after you paste in your store domain and collection ID.

Download the template ($14) →

Instant download · works on n8n Cloud and self-hosted. Want it built and connected for you? See our done-for-you setup service.

FAQ

Do I need a custom collection or a smart collection for this?

You need a custom (manual) collection. The workflow adds and removes products by creating and deleting collects, which is the join record between a product and a custom collection. Smart collections build themselves from rules, so the API cannot attach or detach products from them directly.

Will the workflow remove products a customer is still viewing?

Removing a product from the New Arrivals collection does not unpublish it or change its own product page. The item simply stops showing in that one collection. Customers can still find it through search, its direct URL, and any other collection it belongs to.

Can I change the 30-day window?

Yes. The window lives in one Set node expression, $now.minus({ days: 30 }). Change 30 to 14, 45, or any number of days and save. Nothing else in the workflow needs editing, because the products query filters on that computed cutoff date.

What happens if I add more than 250 aged products in one run?

The products query caps at 250 items per page. If a very large catalog leaves more than 250 aged products in one daily run, the extras are pruned on the following days as the queue clears. For a same-day sweep, add pagination with the page_info cursor.

Does this cost anything to run?

No paid service is involved. It uses only the Shopify Admin API and n8n core nodes. On n8n Cloud it consumes a handful of executions per day. On a self-hosted n8n instance the running cost is effectively zero beyond the server you already pay for.

Related guides

Build a Shopify Product FAQ Generator with n8n and Gemini









A shopify product FAQ generator built in n8n reads every product in your catalog, sends its title and description to Google Gemini, and writes four ready-to-publish question-and-answer pairs straight back into the product description. Instead of writing FAQs by hand for hundreds of products, you run one workflow on a schedule and let it fill the gaps. This guide walks through the exact seven-node build, the Gemini prompt that keeps answers honest, and the marker trick that stops it from ever duplicating work.

What it does

Product FAQs answer the questions that stop a shopper from clicking buy: sizing, materials, care, compatibility, what is in the box. Most stores skip them because writing a good set for every product is slow, boring work. This workflow removes the manual step. On a weekly schedule it pulls your products from Shopify, checks which ones do not yet have a generated FAQ, asks Gemini to draft four concise pairs from the product data you already have, and appends that HTML to the bottom of the product description.

The result is a catalog where every product page answers common buyer questions in its own words, built once and topped up automatically as you add new products. It runs quietly in the background and belongs in a wider n8n Shopify automation stack alongside your inventory, order, and email workflows.

Why it beats the default

The default is one of two bad options: pay a copywriter to work through your catalog product by product, or install a FAQ app that charges a monthly fee and locks the content inside its own widget. Both cost more over time and neither scales cleanly when you add fifty new SKUs.

Building it in n8n changes the economics:

  • The FAQ lives in the native body_html field, so it is indexed with the rest of the description and stays yours if you ever change themes or apps.
  • A hidden marker comment makes every run idempotent. Products that already have a FAQ are skipped, so you can run it as often as you like with zero duplicate blocks.
  • Gemini reads the title and existing description, so the questions are grounded in the actual product rather than generic filler.
  • There is no per-product fee. On the Gemini free tier a small catalog costs nothing, and paid usage is a fraction of a cent per product.

What you need

  • An n8n instance (Cloud or self-hosted, version 1.0 or newer).
  • A Shopify custom app access token with read and write access to products. If you have not connected Shopify to n8n yet, follow connect Shopify to n8n (2026 method) first.
  • A Google Gemini API key. The free tier for gemini-2.5-flash is enough to test and run small catalogs.
  • About 25 minutes to build from scratch, or under 10 minutes with the ready-made template below.

Node-by-node list

Seven nodes, one straight path with a batch loop in the middle:

# Node Type Job
1 Weekly Schedule Schedule Trigger Runs the workflow every Monday at 6am.
2 Get Products Shopify Fetches products from your store.
3 Skip If FAQ Exists Filter Drops products whose description already holds the marker.
4 Loop Over Items Loop Over Items (batch) Processes one product at a time.
5 Generate FAQ (Gemini) HTTP Request Sends product data to Gemini and gets FAQ HTML back.
6 Build New Description Edit Fields Joins the old description, the marker, and the new FAQ.
7 Update Product Shopify Writes the combined HTML back to the product.
Weekly Schedule -> Get Products -> Skip If FAQ Exists -> Loop Over Items
                                                              |  (each item)
                                                              v
                              Generate FAQ (Gemini) -> Build New Description -> Update Product
                                                                                     |
                                                              (loop back to next item)

Step-by-step build

1 Weekly Schedule (Schedule Trigger)

Add a Schedule Trigger. Set the rule to run weekly, on Monday, at hour 6. This paces the workflow so it tops up FAQs on new products once a week without hammering the Shopify or Gemini APIs. You can change the interval to daily if you add products often.

2 Get Products (Shopify)

Add a Shopify node. Set Authentication to Access Token, Resource to Product, and Operation to Get Many. Leave Return All off and set a Limit of 50 so each run works through a manageable batch. Select your Shopify credential.

{
  "id": 8471290361124,
  "title": "Cedar Trail Insulated Water Bottle",
  "body_html": "<p>A 24oz double-walled stainless steel bottle.</p>",
  "product_type": "Drinkware"
}
💡

Tip: Keeping the limit at 50 means a 500-product catalog finishes over ten weekly runs. Because the filter skips finished products, no work is ever repeated.

3 Skip If FAQ Exists (Filter)

Add a Filter node. Create one condition: left value {{ $json.body_html || '' }}, operator String / does not contain, right value <!--faq-generated-->. Only products missing that hidden comment pass through, which is what makes the whole workflow safe to re-run.

4 Loop Over Items (batch)

Add a Loop Over Items node with Batch Size 1. This sends products through the Gemini and update steps one at a time, which keeps you inside API rate limits and makes failures easy to trace. Its lower “loop” output feeds the Gemini node; the update node connects back into it.

5 Generate FAQ (Gemini) (HTTP Request)

Add an HTTP Request node. Method POST, URL https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent. Set Authentication to Predefined Credential Type and pick Google Gemini(PaLM) Api. Under Body, choose JSON and paste this expression:

={{ JSON.stringify({
  contents: [ { parts: [ { text:
    "You are an expert ecommerce copywriter. Using only the product " +
    "information below, write 4 concise, genuinely useful FAQ " +
    "question-and-answer pairs for an online store product page. " +
    "Format the output as clean HTML: wrap each question in <h3> tags " +
    "and each answer in <p> tags. No section heading, no markdown, no " +
    "code fences. Never invent specifications, sizes, prices, or shipping " +
    "claims; if a detail is missing, give a sensible general answer. " +
    "Product title: " + $json.title + ". Product description: " +
    ($json.body_html || "").replace(/<[^>]+>/g, " ").slice(0, 1500)
  } ] } ],
  generationConfig: { temperature: 0.4 }
}) }}
📌

Note: The prompt strips HTML tags out of the description before sending, and the “never invent” instruction is what keeps Gemini from fabricating sizes or shipping promises. Keep both.

6 Build New Description (Edit Fields)

Add an Edit Fields node with two string assignments. The first, productId, pulls the id back from the loop: {{ $('Loop Over Items').item.json.id }}. The second, newBodyHtml, stitches the pieces together:

={{ ($('Loop Over Items').item.json.body_html || '')
    + 'n<!--faq-generated-->n'
    + $json.candidates[0].content.parts[0].text }}

The original description comes first, then the marker comment, then the Gemini FAQ. Referencing the loop node instead of the previous node is what lets you reach the product data that Gemini’s response replaced.

7 Update Product (Shopify)

Add a second Shopify node. Authentication Access Token, Resource Product, Operation Update. Set Product ID to {{ $json.productId }}, then under Update Fields add Body HTML set to {{ $json.newBodyHtml }}. Connect its output back to the Loop Over Items node so the batch advances to the next product. Save, then toggle the workflow Active.

Common mistakes

  • Wiring the update node to the wrong loop output. The FAQ node connects to the lower “loop” output of Loop Over Items, and Update Product connects back into the node, not to the “done” output.
  • Referencing $json.title inside Build New Description. By that point the item is Gemini’s response, so the product fields are gone. Use $('Loop Over Items').item.json to reach them.
  • Giving the Shopify token read-only access. The update step needs write access to products, or it fails with a 403.
  • Removing the marker comment to “clean up” the HTML. The comment is invisible to shoppers and is the only thing preventing duplicate FAQ blocks on the next run.
  • Sending the full raw body_html to Gemini. The prompt trims it to 1500 characters after stripping tags; long descriptions otherwise waste tokens and can confuse the model.

Cost at realistic volume

The only paid piece is Gemini, and each product uses one short call. A 500-product catalog processed at 50 per week finishes in ten weeks, then only new products are touched.

Catalog size Gemini calls (first pass) Rough cost
50 products 50 Free tier
200 products 200 Free tier or a few cents
500 products 500 Under $1
Ongoing (new products) Only new SKUs Effectively free

n8n itself adds no per-run cost on a self-hosted instance, and the Shopify API calls are free. Compared with a FAQ app at $10 to $20 a month, the workflow pays for itself in the first billing cycle it replaces.

Get the Shopify Product FAQ Generator template

The guide above is free to follow end to end. If you would rather skip the build, download the ready-to-import n8n template: all seven nodes wired exactly as described, with the Gemini prompt and marker logic already in place. Drop in your two credentials and run it. Want it installed and tuned to your catalog for you? See our done-for-you service.

Download the template ($19) →

Instant download · Works on n8n Cloud and self-hosted

Frequently asked questions

Does this overwrite my existing Shopify product descriptions?

No. The workflow appends the FAQ block to the end of the current body_html and leaves everything above it untouched. It also inserts a hidden marker comment, so a product that already has a generated FAQ is skipped on the next run and never gets a duplicate block.

Will the FAQ show as a real accordion on my product page?

It appends plain HTML headings and paragraphs to the product description, so the content renders wherever your theme prints the description. It does not populate a theme metafield or a dedicated FAQ section. If you want a collapsible accordion, wrap the output in your theme’s accordion markup.

How much does it cost to run Gemini for my whole catalog?

Google Gemini has a free tier for gemini-2.5-flash that covers small catalogs at no cost. Even on paid usage, one product FAQ is a fraction of a cent because each call sends only the title and a trimmed description. A 500-product catalog stays well under a dollar.

Can I run it only on new products instead of the whole catalog?

Yes. The marker-comment filter already skips products that were processed before, so re-runs only touch new items. To react instantly to new products, swap the Schedule Trigger for a Shopify Trigger on the products/create event and remove the Get Products node.

Does it work on n8n Cloud and self-hosted?

Both. Every node used is a core n8n node, so the workflow imports and runs identically on n8n Cloud and any self-hosted instance. You only need a Shopify access token credential and a Google Gemini API key, both entered once in the credential fields.

Related guides

n8n
Shopify
Gemini
product FAQ
automation