Build a Marketing Reporting Dashboard with n8n and Google Sheets
Build an automated marketing reporting dashboard with n8n and Google Sheets. Pull data from GA4 and GSC into a live dashboard that updates itself weekly.
Edward Chalupa
Founder, Whtnxt · Dallas, TX
I spent two hours every Monday morning pulling reports. Google Analytics 4 for traffic numbers. Search Console for keyword performance. Social platforms for engagement. Then copy-pasting everything into a Google Sheet that the client would glance at once and ask “is this good or bad?”
That workflow cost me 8 to 10 hours a month. For a single client. Multiply that across a handful of accounts and you are burning a full work week on data janitor work.
So I automated it. I built a marketing reporting dashboard in n8n that pulls data from GA4 and Google Search Console directly into Google Sheets, formats it into readable sections, and updates itself every Monday morning. The whole thing takes about 90 seconds to run end to end. Here is exactly how I built it.
What This Dashboard Covers
The dashboard pulls four data streams into one Google Sheet:
- GA4 traffic summary — sessions, users, pageviews, bounce rate, average engagement time
- GA4 top pages — the 10 highest-traffic pages on your site
- GA4 traffic sources — organic, direct, referral, social, paid breakdowns
- Google Search Console — clicks, impressions, average CTR, average position for your top 50 queries
Each stream lands on its own sheet tab. A summary dashboard tab uses Google Sheets formulas to surface the key numbers at a glance. The whole thing runs on a schedule so the data is always fresh when you open it.
What You Need
To build this you need four things:
- An n8n instance — self-hosted or cloud. I run self-hosted on Docker. The setup takes 15 minutes and I covered the full process in my earlier guide on setting up n8n as your marketing automation engine.
- A Google Cloud Project with the Analytics API and Search Console API enabled. I will walk through the authentication setup.
- A Google Sheet — one blank sheet with a few tabs. I will share the exact tab structure.
- Google service account credentials — these let n8n talk to the Google APIs without needing your personal login.
If you have not set up a Google service account before, head to the Google Cloud Console, create a new project, go to APIs and Services, enable the Analytics Data API and the Search Console API, then create a service account and download the JSON key file. That file is what n8n needs to authenticate.
Step 1: Set Up the Google Sheet Structure
I use five tabs in my reporting sheet. Open a blank Google Sheet and create these:
- Summary — the main dashboard view with key metrics and sparklines
- Traffic — raw GA4 traffic data pulled by the workflow
- Top Pages — the 10 highest-traffic pages
- Sources — traffic channel breakdown
- GSC — Search Console query data
On the Summary tab, I set up a header row with the metric names and a formula row that pulls the latest values from the data tabs. For example, the cell showing total sessions uses a formula like:
=SUM(Traffic!B2:B)
That sums the session column on the Traffic tab. When n8n appends new rows each week, the summary updates automatically. No manual intervention.
I also added a sparkline for each metric showing the last 8 weeks of trend data. The formula looks like this:
=SPARKLINE(Traffic!B2:B, {"charttype","line"; "color","#1CA69A"})
The teal color matches the whtnxt brand. You can change it to match yours.
Step 2: Build the GA4 Traffic Pull in n8n
This is the core workflow. Create a new workflow in n8n and add a Schedule trigger set to run every Monday at 8 AM.
Add a Google Sheets node configured to append a row to the Traffic tab. But we need data first, so the real starting point is the HTTP Request node.
Add an HTTP Request node that calls the GA4 Data API:
- Method: POST
- URL:
https://analyticsdata.googleapis.com/v1beta/properties/YOUR_PROPERTY_ID:runReport - Authentication: Service Account (use the JSON key from Google Cloud)
- Send as JSON Body: yes
The body of the request tells GA4 what data to return. Here is what I send:
{
"dimensions": [{"name": "date"}],
"metrics": [
{"name": "sessions"},
{"name": "totalUsers"},
{"name": "screenPageViews"},
{"name": "bounceRate"},
{"name": "averageSessionDuration"}
],
"dateRanges": [{"startDate": "7daysAgo", "endDate": "yesterday"}],
"limit": 7
}
This returns one row per day for the last 7 days, with sessions, users, pageviews, bounce rate, and average session duration.
The response comes back as nested JSON. You need to flatten it before you can write it to Google Sheets. Add a Code node between the HTTP Request and the Google Sheets node with this transformation:
const response = $input.first().json;
const rows = response.rows || [];
return rows.map(row => {
const values = row.dimensionValues.concat(row.metricValues);
return { json: { values: values.map(v => v.value) } };
});
This converts the GA4 response into flat arrays that Google Sheets can accept.
Wire the output into a Google Sheets node configured to append rows to the Traffic tab. The column order in the node should match the metric order from the API response.
Step 3: Add Top Pages and Traffic Sources
Repeat the same pattern for the other GA4 data streams.
For top pages, change the GA4 API request body to:
{
"dimensions": [{"name": "pagePath"}],
"metrics": [
{"name": "screenPageViews"},
{"name": "totalUsers"},
{"name": "averageSessionDuration"}
],
"dateRanges": [{"startDate": "28daysAgo", "endDate": "yesterday"}],
"orderBy": [{"metric": {"metricName": "screenPageViews"}, "desc": true}],
"limit": 10
}
Note I changed the date range to 28 days. Seven days of page data is too noisy. Twenty-eight days gives a more stable ranking. I also added an orderBy clause to sort by pageviews descending.
Write the results to the Top Pages tab.
For traffic sources, use:
{
"dimensions": [{"name": "sessionDefaultChannelGroup"}],
"metrics": [
{"name": "sessions"},
{"name": "totalUsers"},
{"name": "screenPageViews"}
],
"dateRanges": [{"startDate": "28daysAgo", "endDate": "yesterday"}]
}
This returns rows for Direct, Organic Search, Paid Search, Social, Referral, and Email. Write these to the Sources tab.
Step 4: Pull Google Search Console Data
Search Console data requires a different API. Add another HTTP Request node, this time calling the Webmaster Tools API:
- Method: POST
- URL:
https://searchconsole.googleapis.com/v1/sites/SITE_URL/searchAnalytics/query - Authentication: Same service account
- Send as JSON Body: yes
Replace SITE_URL with your verified Search Console property, like sc-domain:whtnxt.io or https://example.com/.
The request body:
{
"startDate": "28daysAgo",
"endDate": "yesterday",
"dimensions": ["query"],
"rowLimit": 50
}
This returns the top 50 queries by clicks, with impressions, CTR, and position data. The response format is simpler than GA4 — each row is already flat with keys for clicks, impressions, ctr, and position.
Write these to the GSC tab. I add a formula column on the sheet that flags any query with average position below 10 and CTR below 2 percent. Those are the pages that need work.
Step 5: Schedule and Handle Data Cleanup
Set the Schedule trigger to run weekly. I chose Monday at 8 AM so the data lands before the work week starts.
One thing I learned the hard way: appending rows every week creates a long tail of historical data that slows down the sheet. On the first Monday of each month, I clear the data tabs and start fresh. The summary tab keeps running metrics because it tracks the current sheet contents, not absolute values.
To do this, add an IF node after the Schedule trigger that checks the current week number. On week 1 of the month, route to a Google Sheets node that clears the data tabs. On other weeks, route straight to the data pull nodes.
The IF expression in n8n looks like:
{{ $now.format("e") == 1 && $now.format("d") <= 7 }}
This returns true when it is Monday (day 1) and the date is in the first 7 days of the month.
What the Output Looks Like
When the workflow runs, it populates all five tabs in about 3 seconds per data source. Total run time is around 15 seconds for all four pulls.
The Summary tab shows:
- Sessions this week vs last week (with percentage change)
- Users this week vs last week
- Average engagement time trend (8-week sparkline)
- Top 5 pages ranked by traffic
- Traffic source breakdown as a mini pie chart
- Top 5 GSC queries with position and CTR
The formatting is done entirely with Google Sheets features — no n8n formatting needed. Conditional formatting highlights any metric that dropped more than 10 percent week over week. I use red fill for drops and green fill for gains.
Real Numbers from Running This for 3 Months
I have been running this dashboard for three clients since March 2026. Here are the measurable results:
- Time saved: 8 to 10 hours per month per client. The workflow runs itself. I open the sheet on Monday morning and the data is there.
- Errors eliminated: Zero copy-paste errors since deployment. Manual reporting had at least one or two per month — wrong date range, missed row, wrong formula. The automated version does not make those mistakes.
- Client response time: From 24 hours to 15 minutes. When a client asks “how did we do last week,” I open the sheet and have the answer before I finish typing the reply.
- Decision velocity: I added alerts in the sheet that flag when organic traffic drops below the 4-week rolling average. That caught a Google update affecting client traffic within 48 hours instead of waiting for the monthly report.
The ROI on the build time was immediate. It took about 4 hours to set up the first dashboard and connect all the APIs. Building the second one took 30 minutes — just duplicate the workflow, change the property ID, and point it at a new sheet.
Customizing for Your Stack
This pattern works with any data source that has an API. I have extended the same workflow to pull:
- LinkedIn page analytics via the LinkedIn Marketing API
- Instagram account metrics via the Graph API
- Email campaign stats from Listmonk’s API (same self-hosted approach as my other tools)
- Form submission data from Formbricks
- Lead routing data — which sources convert at what rate
Each one follows the same pattern: HTTP Request node to pull data, Code node to flatten the response, Google Sheets node to write it. The API endpoints and authentication methods change, but the flow stays the same.
The Week Over Week Comparison Problem
The hardest part of this build was not the data pull. It was making week-over-week comparisons work in Google Sheets without manual intervention.
Here is the solution: add a Looker Studio connection to the same Google Sheet. Looker Studio reads the data tabs and builds a comparison chart automatically. It handles date range comparison natively, so week over week, month over month, and year over year views all work without additional formulas.
If Looker Studio is overkill for your needs, you can add a second n8n workflow that runs on Monday, pulls the previous week’s summary, and writes it to a comparison table. Then your Google Sheet formulas compare the current row to the previous row.
Why This Beats Dedicated Reporting Tools
I have used SEMrush, Ahrefs, and Databox for client reporting. They are good tools, but they have a fundamental limitation: they show you their data, not yours. If you run email campaigns through Listmonk or track form submissions through Formbricks, those tools cannot see that data. If you manage Google Ads accounts, the reporting tool only shows high-level metrics, not your full campaign structure breakdown.
A custom n8n dashboard shows everything in one place. Your ad platform data, your website analytics, your email performance, your CRM pipeline — all in the same sheet, under your control, no per-seat license fees. For example, I connected my deal-to-cash pipeline data to the same reporting sheet so pipeline velocity shows up next to website traffic.
The Google Sheets approach also means your clients can access the dashboard without needing a login to your reporting tool. Share the sheet as view-only and they see the same numbers you do.
Getting Started
Set up the first workflow this week. Start with just the GA4 traffic pull — that gives you the biggest return with the least setup. Add GSC next. Then layer on your other data sources one at a time.
The build takes longer the first time because you are setting up authentication and figuring out the API responses. After that, it is a 15-minute clone-and-connect process for each new client.
I built my first version of this dashboard in about 4 hours. It has saved me over 120 hours since March. That works out to 30 hours saved for every hour of build time.