n8n GA4 Reporting Workflow: A Decision-Ready Digest, Not a Metrics Dump
Build an n8n GA4 reporting workflow that sends a color-coded week-over-week digest, not a raw metrics dump — with the OAuth and dimension gotchas spelled out.
A standard GA4 export reads like a phone book: rows of sessions, users, and pageviews broken down by channel, all in neutral black text. The recipient scans it, finds nothing flagged, and closes the tab. The data was there. The decision wasn't.
An n8n GA4 reporting workflow worth building does the opposite. It pulls the same GA4 numbers, computes what changed since last week, and renders a digest where a 30% drop in conversions is red and impossible to miss. The job isn't moving GA4 data into an email. It's turning GA4 data into something someone acts on before the next standup.
Most GA4 templates stop at the export. The gap, and the reason this post exists, is everything after runReport returns: the week-over-week math, the color coding, and the OAuth and dimension traps that make GA4's API quietly return nothing.
What You Can Automate With GA4
The GA4 reporting jobs that fit cleanly into a workflow:
- Pulling sessions, users, and conversions on a schedule via
runReport - Breaking metrics down by channel, device, or landing page dimension
- Computing week-over-week and month-over-month deltas automatically
- Flagging high bounce rates and zero-conversion channels
- Rendering a color-coded HTML digest instead of a raw table
- Emailing the digest to stakeholders before the Monday meeting
- Logging each run's snapshot so trends are visible beyond GA4's own retention
The delta computation and the flagging are what separate a report someone reads from a report someone archives unread.
The GA4 Reporting Pipeline
The structure every GA4 digest follows:
Schedule → runReport (current + prior period) → Compute deltas → Flag → Render → Send
A concrete weekly run:
Schedule Trigger v1.2 (cron: 0 8 * * 1)
→ HTTP Request v4.2: runReport (this week) property=properties/123456789
→ HTTP Request v4.2: runReport (last week)
→ Merge v3: align metrics by dimension
→ Code v2 (jsCode): compute WoW %, flag bounce > threshold, conversions == 0
→ Code v2 (jsCode): build color-coded HTML
→ Gmail v2: send digest
→ Google Sheets v4: append weekly snapshot
The two runReport calls (this week and last week) are deliberate. GA4's API won't hand you a delta; it gives you a period's numbers and nothing else. You fetch both periods and compute the change yourself in a Code node. The properties/123456789 path uses the numeric GA4 property Id, not a view Id from old Universal Analytics — mixing those up is the single most common reason the call returns zero rows.
GA4 property Ids are numeric (123456789) and prefix as properties/123456789 in the API. The old Universal Analytics view Ids look different and are dead. If your runReport returns an empty rows array with a 200 status, check the property Id before anything else — a wrong Id doesn't error, it just returns nothing, which sends people down a debugging rabbit hole that has nothing to do with their query.
Step-by-Step Breakdown
1. Authenticate with the right scope
The Google OAuth2 credential needs the analytics.readonly scope. Without it the API returns a 403, or worse, an empty result that looks like a data problem. Add the scope when you create the credential, not after.
2. Call runReport for both periods
One call for the current week, one for the prior week, same metrics and dimensions. Specify metrics like sessions and conversions, dimensions like sessionDefaultChannelGroup. Keep the two calls identical except for the date range.
3. Align and compute deltas
A Merge node lines up the two periods by dimension value, then a Code node computes the percentage change per metric. A channel that vanished this week (present last week, absent now) needs explicit handling — it won't appear in this week's rows at all.
4. Flag what matters
The same Code node marks bounce rates over a threshold and channels with zero conversions. These flags are the report's reason to exist. A number nobody flagged is a number nobody reads.
5. Render and send
A second Code node builds the HTML with inline CSS so increases are green and drops are red, then the Gmail node sends it. A Google Sheets append stores the snapshot so you have history past GA4's default retention window.
Implementation Patterns That Hold Up
Pattern 1 — Two periods, one delta. GA4 gives you periods, never deltas. Always fetch current and prior, then subtract.
Code v2 (jsCode):
const wow = (cur, prev) =>
prev === 0 ? null : Math.round(((cur - prev) / prev) * 100);
return rows.map(r => ({
json: { channel: r.channel, sessions: r.sessions,
sessionsWoW: wow(r.sessions, r.prevSessions) }
}));
The prev === 0 guard matters: a channel that had zero sessions last week and some this week produces a division-by-zero that renders as Infinity% in the email if you don't catch it.
Pattern 2 — Dimension discipline. GA4 caps the metric-and-dimension combinations a single runReport allows, and incompatible pairings return an error rather than a partial result. Request only the dimensions the report actually shows. Asking for sessionDefaultChannelGroup plus landingPagePlusQueryString plus device in one call is the kind of over-fetch that trips GA4's compatibility rules.
Pattern 3 — Color-coded, flag-first layout. The digest leads with what changed, not with a full table. The App Analytics Dashboard template builds exactly this: it fetches GA4 data daily, breaks sessions, users, and conversions down by channel and device, and flags high bounce rates and zero-conversion channels at the top, so the reader sees the problems before the totals.
n8n Nodes You'll Use Most
| Node | Purpose |
|---|---|
n8n-nodes-base.httpRequest | GA4 runReport calls with the OAuth2 credential |
n8n-nodes-base.googleAnalytics | Native GA4 reporting for common metric pulls |
n8n-nodes-base.scheduleTrigger | Weekly Monday-morning digest run |
n8n-nodes-base.merge | Align current and prior period by dimension |
n8n-nodes-base.code | Delta math, flagging, color-coded HTML render |
n8n-nodes-base.gmail | Send the finished digest |
n8n-nodes-base.googleSheets | Snapshot log for long-term trend history |
The native Google Analytics node handles straightforward pulls fine. Reach for the HTTP Request node (v4.2) when you need precise control over the runReport body — custom date ranges, metric filters, or ordering the node doesn't expose. Both authenticate with the same Google OAuth2 credential. The GA4 Data API runReport reference documents every metric and dimension name.
Getting Started
- Create the Google OAuth2 credential with
analytics.readonly. Confirm the scope is present. This is the most common silent failure. - Find the numeric GA4 property Id. It's in GA4 admin under property settings. Not a Universal Analytics view Id.
- Get one
runReportreturning rows. Sessions by channel, last 7 days. See real numbers in the execution output before building anything on top. - Add the prior-period call and the delta math. Verify the percentages against a manual check in the GA4 UI.
- Build the color-coded HTML. Inline the CSS — email clients strip
<style>blocks. Test against a real inbox. - Schedule, flag, and log. Monday 8am, bounce and zero-conversion flags on, snapshot to a sheet every run.
The App Analytics Dashboard template ships this GA4 digest end-to-end: a daily Schedule Trigger fetches Google Analytics 4 data, a Code node breaks sessions, users, and conversions down by channel and device, and the report flags high bounce rates and zero-conversion channels automatically — the decision-ready layout, already wired. It's part of The Complete n8n Templates Bundle, a one-time lifetime license to the whole catalog (plus every template added later) if you run more than one of these analytics automations.
A caveat worth pinning down before you schedule anything: GA4 processes some metrics with a 24-to-48-hour lag, so a report fired at midnight for "yesterday" can show numbers that shift by Monday afternoon. Either run the digest for a period that's safely closed, or label the report as preliminary. Reporting a number that later changes erodes trust in the whole workflow.
For the multi-client version of this, where GA4 is one of several sources feeding a per-client report, see the guide to automating client reporting with n8n. The broader metric-collection patterns live in the n8n analytics automation guide.
Get one runReport returning real rows first. Everything after that — the deltas, the colors, the flags — is Code-node work you fully control.
Common questions
How does an n8n GA4 reporting workflow pull data from Google Analytics 4?
Why does my GA4 report in n8n return zero rows?
Can n8n send a color-coded GA4 report by email?
Get the workflow templates this guide is built on
Import-ready n8n JSON, step-by-step setup, and tested end-to-end. One-time payment, own it forever.
Get 3 tested n8n templates, free
The full customer package for three real catalog templates — workflow JSON, step-by-step setup guide, credential checklist. Built through the same live-instance release process as everything we sell. Plus new templates and automation guides in your inbox. No spam, unsubscribe anytime.
- 01Smart To-Do List ManagerPre-built n8n workflow template that automates productivity with OpenAI. Live in about 10 minutes.$14
- 02Email Follow-Up AutomatorPre-built n8n workflow template that automates crm with OpenAI. Live in about 15 minutes.$12
- 03Market Trend AnalyzerPre-built n8n workflow template that automates data processing with OpenAI. Live in about 10 minutes.$14
More automation guides

n8n Backup Automation That Actually Verifies the Backup
A Backup You Never Verified Is a Coin Flip Every n8n backup automation tutorial that ranks does the same thing: schedule an export, push the JSON to Google Drive or S3, done. They back up the data and…

How to Build an n8n Log Alerting Workflow (No Grafana Required)
Most n8n Error Setups Tell You Everything, Which Means They Tell You Nothing An n8n log alerting workflow has one job: surface the failures that need a human and quietly file the ones that don't. The…

n8n Uptime Monitoring with Slack Alerts (Without the Alert Storms)
An Uptime Check That Cries Wolf Gets Muted in a Week The point of n8n uptime monitoring with Slack alerts isn't to detect a single outage. It's to be trusted the next hundred times it fires. Most of t…