HomeShopify & E-commerceShopify New vs Returning Customer Revenue…

Shopify New vs Returning Customer Revenue Report in n8n

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