Here's a ritual every marketing team knows: Monday morning, someone opens Meta Ads Manager, Google Ads, and GA4, exports three CSVs, pastes them into a spreadsheet, fixes the date formats, and rebuilds the same charts they rebuilt last week. It takes an hour, it's error-prone, and nobody enjoys it.
This guide walks through automating that entire pipeline with n8n (self-hosted workflow automation) and Looker Studio (free dashboards). The pattern: n8n pulls spend and performance data from each ad platform on a schedule, normalizes it into one Google Sheet, and Looker Studio visualizes it — refreshed automatically, forever.
A note on honesty up front: this is a build walkthrough based on my real n8n + Looker Studio automation work — I've wired n8n workflows into Google Sheets and visualized the results in Looker Studio. The Meta Ads and GA4 legs below are walked through from the API docs and working n8n patterns, but I haven't run those two legs end-to-end against live ad accounts myself. The Google Ads API leg I have not run all the way through either — Google's developer-token approval is the known hard part there, and I'll flag exactly where that bites.
The architecture
Meta Marketing API ─┐
├─▶ n8n (scheduled) ─▶ normalize ─▶ Google Sheets ─▶ Looker Studio
GA4 Data API ───────┘ ▲
│ (Google Ads API — see caveat below)
Why Google Sheets in the middle instead of writing straight to Looker Studio? Because Looker Studio's native ad-platform connectors refresh on their schedule and give you limited control over blending. A Sheet you own means one schema, one timezone, and blended cross-platform metrics (total spend, blended ROAS) computed before visualization. For a weekly marketing report, Sheets is plenty — upgrade to BigQuery when the row counts get silly.
Step 1: Pull Meta Ads data with the HTTP Request node
Meta's Marketing API has an insights edge that returns exactly what a report needs. In n8n, use the HTTP Request node (not the Meta Ads node — the generic HTTP node gives you full control over fields and breakdowns):
- Method: GET
-
URL:
https://graph.facebook.com/v21.0/act_<AD_ACCOUNT_ID>/insights -
Query parameters:
-
fields:campaign_name,spend,impressions,clicks,actions -
date_preset:last_7d -
level:campaign -
access_token: your token (store it in n8n Credentials, never hardcoded)
-
Request actions and you'll get conversions broken down by type — filter for purchase or lead in the next step. One gotcha: Meta returns actions as an array of {action_type, value} objects, not flat columns. You'll flatten that in the normalize step.
Token setup: create a Meta app, add the Marketing API product, generate a token with ads_read scope. For a scheduled job, you want a system user token (doesn't expire when someone changes their Facebook password) — this is the detail that breaks most first attempts when the report silently stops updating two months in.
Step 2: Pull GA4 data with the Data API
GA4's Data API uses OAuth2 service accounts, which n8n supports natively:
- In Google Cloud Console, enable the Google Analytics Data API, create a service account, download the JSON key.
- In GA4 Admin → Property Access Management, add the service account email as a Viewer.
- In n8n, create a Google Analytics credential (OAuth2 / service account) and use the Google Analytics node with the
Run Reportoperation:- Dimensions:
date,sessionDefaultChannelGroup - Metrics:
sessions,conversions,totalRevenue - Date range: last 7 days
- Dimensions:
The failure mode to expect here is the property-access step. If n8n returns a 403, it's almost always the service account missing the Viewer role in GA4, not your n8n config.
Step 3: Google Ads — the hard one (partially tested)
Google Ads data comes from the Google Ads API, and this is where you should budget real time. The API requires a developer token, and Google approves those only for accounts in good standing — the application asks about your use case and intended API usage. Test accounts get an unapproved token with heavy limits.
The honest status: the n8n side is straightforward (HTTP Request node against googleads.googleapis.com, GAQL query like SELECT campaign.name, metrics.cost_micros, metrics.impressions FROM campaign WHERE segments.date DURING LAST_7_DAYS), but I have not completed a live pull against a production account because of the developer-token gate. If you plan to run this in production, start the developer-token application on day one — it's the longest pole in the tent.
Pragmatic fallback: until the API is approved, the Google Ads scheduled email reports (CSV to inbox) plus n8n's IMAP Email trigger can land the same data in your Sheet. Less elegant, works Monday morning.
Step 4: Normalize everything in a Code node
The three APIs return three different shapes, three different date formats, and two different currencies if you're not careful. One n8n Code node (JavaScript) standardizes them into a single schema:
// Normalize one platform's rows into the canonical schema
function normalize(rows, platform) {
return rows.map(r => ({
date: r.date, // YYYY-MM-DD, coerced per platform
platform, // 'meta' | 'google_ads' | 'ga4'
campaign: r.campaign_name || r.campaign || '(organic)',
spend: Number(r.spend || 0), // micros → units for Google Ads!
impressions: Number(r.impressions || 0),
clicks: Number(r.clicks || 0),
conversions: Number(r.conversions || 0),
}));
}
return normalize($input.all().map(i => i.json), 'meta');
The Google Ads cost_micros trap deserves its own warning: costs come back in micro-units, so divide by 1,000,000 or your spend column will claim you spent $4 billion.
Also normalize timezones here — pull everything in the ad account's timezone and stamp the Sheet with it, or your Monday report will attribute Sunday night spend to the wrong week.
Step 5: Write to Google Sheets
n8n's Google Sheets node with the Append operation (service-account credential again) writes the normalized rows to a tab like raw_spend. Keep one tab per platform plus one blended tab, or just one tab with the platform column — the latter blends more easily in Looker Studio.
Idempotency matters for scheduled runs: before appending, use IF + Google Sheets: Read to check whether this week's rows already exist (match on date + platform + campaign). Otherwise a retried run double-counts spend, and nobody notices until the invoice meeting.
Step 6: Build the Looker Studio dashboard
Connect Looker Studio to the Sheet (native connector, 15-minute minimum refresh — fine for weekly reporting). The charts that actually get used:
- Scorecards: total spend, total conversions, blended ROAS, week-over-week deltas
- Time series: spend vs. conversions by day, with platform breakdown
- Table: campaign-level spend / ROAS, sortable, with conditional formatting on ROAS < target
Blend the platforms with a calculated field for blended ROAS: SUM(conversions * value) / SUM(spend) — or simpler, a blended cost per conversion if conversion values aren't tracked consistently across platforms (they usually aren't at first).
Step 7: Schedule it and handle failures
n8n's Schedule Trigger (cron: Mondays 6 AM in the account timezone) kicks off the workflow. Then add the unglamorous parts:
- Error branch: n8n's Error Trigger workflow → send yourself a Telegram/Slack message with the failed node name. A report pipeline that fails silently is worse than no pipeline.
- Stale-data check: a final node that reads the Sheet and alerts if the newest row is older than 8 days — catches an expired Meta token before anyone relying on the report notices.
- Credential rotation reminder: Meta system-user tokens and Google service-account keys should be rotated; put a quarterly reminder in the workflow's notes.
What this actually buys you
About an hour a week back, per report — but the real win is trust. When the numbers are pulled by a machine on a schedule instead of copy-pasted by a human on a Monday, the "are these numbers right?" conversation disappears. The dashboard becomes the source of truth instead of the spreadsheet someone edited.
Start with Meta + GA4 (both testable today with free-tier accounts), add Google Ads once the developer token lands, and keep the Sheet schema stable from day one — every downstream chart depends on it. The n8n workflow itself is maybe 10 nodes. The data hygiene around it is the actual project.
Top comments (0)