Article Information
Category: Development
Published: April 28, 2026
Author: Chris de Gruijter
Reading Time: 10 min
Tags

Shipping a Google Ads Tab: How the Webfluentia Dashboard Brings All Client Data Under One Roof
Published: April 28, 2026
Every client I work with has the same problem: their performance data lives in five different places. Google Ads is in one tab, GA4 is in another, the lead form submissions are somewhere else entirely, and organic rankings require yet another login. Piecing together a coherent picture of what's working means switching between tools, correlating data manually, and hoping nothing falls through the cracks.
The Webfluentia dashboard at dashboard.webfluentia.agency was built to fix that — a single multi-tenant platform where each client logs in and sees all their digital marketing data in one place. Last week I shipped the Google Ads tab, and the first client (KindChiro) synced their data on April 28, 2026. This post is about what that tab does, how it works technically, and where the whole system is heading.
The Problem: Data Fragmentation at Scale
When I first started managing digital marketing at Webfluentia, I was spending a surprising amount of time just navigating — opening Google Ads to check spend, switching to GA4 to see what happened to that traffic, cross-referencing with the lead dashboard to see whether those sessions converted. None of this analysis was difficult in isolation. It was the context-switching that was killing productivity.
Clients had it even worse. A small construction company doesn't want to learn three separate platforms and normalise the data mentally. They want to know: "Is my advertising working?" I wanted to give them a direct answer to that question from a single URL.
- Google Ads — spend, clicks, conversions, ROAS, search terms
- GA4 — sessions, engagement, traffic sources, landing pages
- Self-hosted cookieless analytics — GDPR-safe traffic data outside GA4's sampling
- Lead forms — actual enquiries captured from the website
- SEO rankings — organic keyword positions tracked weekly
All of those were separate logins, separate interfaces, separate mental models. The dashboard consolidates all of them into tabs — and clients see only the tabs I've granted them access to.
The Dashboard Architecture
The dashboard is a Next.js application running on Cloudflare Workers via @opennextjs/cloudflare. Authentication is handled by Supabase Auth — I create accounts manually for each client, no self-registration. A user_client_access table maps each user to their assigned clients and defines which sections they can access as an array of allowed section keys.
This section-level access control is what makes the system practical for a multi-client agency context. One client might have SEO, leads, and cookieless analytics. Another gets Google Ads and GA4. A third might have everything. I enable sections per-client as they become production-ready, which lets me ship incrementally without breaking anyone's existing experience.
Today the dashboard has the following sections, each available as a tab in the navigation:
- SEO — keyword rankings, ranking history, content performance, PSI audits
- Leads / CRM — enquiry capture, pipeline, contacts, quotes, invoices
- Cookieless Analytics — self-hosted, cookie-free tracking, GDPR-safe by design
- GA4 Analytics — Google Analytics 4 data via the Data API
- Google Ads — campaign metrics, search terms, ROAS (newly shipped)
- Reports — cross-section client reports with four templates
- Payments — Stripe checkout integration and transaction history
- Notifications — alert rules and real-time notification system
- Social — Meta Graph API for Facebook and Instagram metrics
The Google Ads Tab: What It Shows
The Google Ads tab is focused on answering the questions a client actually asks: How much am I spending? Are my campaigns driving conversions? What search terms are triggering my ads? It's not trying to replicate the full Google Ads interface — it's a curated read-only view of the metrics that matter.
Stat Cards
The top row shows eight summary cards for the selected date range:
- Total Spend — formatted currency with the account's currency code
- Clicks — total click volume
- Impressions — how many times ads were shown
- Conversions — tracked conversion actions
- Avg. CPC — average cost per click
- CTR — click-through rate as a percentage
- Cost / Conversion — what each conversion cost on average
- ROAS — return on ad spend, where conversion value data is available
Daily Spend Chart
Below the stat cards is a bar chart showing daily spend across the selected date range. Hovering over a bar shows a tooltip with that day's cost, clicks, and conversions — enough context to spot anomalies without needing to query the data directly. This is the view I use when a client asks "why was last Tuesday expensive?" — you can see the spike immediately.
Three-Tab View
Below the chart, a tab bar switches between three views:
- Overview — a period summary combining all the stat card data in table form
- Campaigns — per-campaign breakdown with status badges, spend, clicks, impressions, conversions, and ROAS. Campaign status is color-coded: green for ENABLED, yellow for PAUSED, grey for REMOVED.
- Search Terms — the actual search queries that triggered ad impressions, with their click, impression, and conversion data. This is often the most actionable view — it shows exactly where the budget is going at the keyword level.
Date Presets and Sync Timestamp
The date range selector offers three presets — last 7, 30, and 90 days — which cover the most common reporting windows. The header also shows a "Last synced" timestamp so clients know how fresh the data is. Since sync runs daily at 05:00 UTC, data is typically no more than 24 hours stale.
How the Sync Works Technically
No Google Ads API calls happen from the browser. The client reads data from Supabase — the same database that powers the SEO and analytics sections. A Cloudflare Cron Worker handles the API sync on a daily schedule.
The Cloudflare Cron Worker
The worker runs at 05:00 UTC every day. It queries the Google Ads API using GAQL (Google Ads Query Language) for each configured account. GAQL is SQL-like and surprisingly readable once you understand the resource model:
SELECT
campaign.id,
campaign.name,
campaign.status,
metrics.cost_micros,
metrics.clicks,
metrics.impressions,
metrics.conversions,
metrics.conversions_value,
segments.date
FROM campaign
WHERE segments.date DURING LAST_90_DAYSThe worker runs separate queries for campaign metrics and search term view data, then upserts the results into Supabase. Using ON CONFLICT DO UPDATE means re-running the sync is idempotent — no duplicate rows, no manual deduplication needed.
The Supabase Schema
The Google Ads data lives in an ads schema with four tables:
ads.accounts— one row per Google Ads account, linked to a client byclient_idads.campaigns— one row per campaign, including name, status, and account referenceads.campaign_metrics— daily metric rows per campaign: cost, clicks, impressions, conversions, valueads.search_terms— daily search term rows: the query text, campaign it matched, and its metrics
The dashboard's API route aggregates campaign_metrics and search_terms for the requested date range and returns pre-computed totals. The browser never makes a direct call to the Google Ads API — it only talks to Supabase via server-side Next.js API routes, which are protected by session authentication.
Why This Approach Over Live API Calls
I could have queried the Google Ads API on demand from the server when a client opens the tab. I chose not to for three reasons. First, Google Ads API quota is a real concern — daily syncs are predictable, on-demand queries are not. Second, Supabase queries are fast; Google Ads API calls add 500–2000ms of latency. Third, the sync architecture means historical data is always available locally, even if the API has a hiccup.
The Whole Picture: Why One Dashboard Changes the Conversation
The Google Ads tab didn't ship in isolation. It shipped as part of a system, and that context is what makes it genuinely useful rather than just another reporting view.
Consider a practical scenario: a client's ad spend goes up this week but leads stay flat. With the old multi-tool workflow, you'd need to open Google Ads for spend data, GA4 for conversion path data, and the lead dashboard for enquiry counts — then manually correlate across three sources. In the Webfluentia dashboard, all three tabs are one click apart. You can go from the Google Ads campaign view, to the GA4 landing page breakdown, to the leads inbox in a single session, with the same date range applied across all of them.
The Google Ads search terms tab is particularly powerful in this context. Seeing which queries are generating clicks and conversions, then cross-referencing with organic ranking data in the SEO tab, shows you the full demand picture for a keyword: am I paying for traffic I could rank for organically? Are there high-converting search terms I haven't created ad copy for? These are questions that require multi-source data — and they're now answerable without leaving one screen.
The goal was never to build a better Google Ads UI. It was to eliminate the context-switching that makes multi-channel analysis slow.
— CJ van de Gruijter
What's Next: The AI Chatbot Tab
The next major feature in the pipeline is an AI chatbot tab — and it's already in development. The idea is straightforward: rather than clicking through tabs to build a picture of performance, clients can ask natural language questions and get answers that pull from all data sources at once.
Questions like:
- "Which campaign drove the most leads last month?"
- "What are my top landing pages by conversion rate?"
- "How does this week's ad spend compare to the same period last month?"
- "Which search terms are converting but not in my SEO content yet?"
- "What's my cost per lead from Google Ads compared to organic?"
Answering these questions today requires manual joins across multiple views. The chatbot will do that join automatically — it has access to the same Supabase data the dashboard reads, across all schemas: ads, analytics, seo, leads. The model can query across sources and synthesise an answer that no single tab can produce on its own.
This is the end state the dashboard has been building toward: not just data in one place, but reasoning across that data. The Google Ads tab is a prerequisite — the chatbot needs the data in a queryable, structured form before it can answer questions about it.
First Live Client: KindChiro
The first account to sync live data was KindChiro, a children's chiropractic clinic running Google Ads campaigns in Belgium. Their account synced on April 28, 2026 — the same day this post went up. The search terms view immediately highlighted a handful of high-impression, low-conversion queries worth reviewing, which is exactly the kind of fast actionable insight the tab is designed to surface.
At Webfluentia, we manage Google Ads under a shared MCC account, so the sync worker authenticates once and can pull data for any client account we manage. Adding a new client to the dashboard requires only a new row in ads.accounts — the rest is automatic.
Closing Thoughts
Building a unified client dashboard is a significant ongoing investment — the Google Ads tab alone took multiple phases of development across schema design, the sync worker, the API layer, and the UI. But the payoff compounds over time. Every new data source added to Supabase becomes immediately queryable across the whole system. Every new tab is built on an auth and access-control foundation that already works.
If you're managing digital marketing for multiple clients and find yourself spending more time navigating tools than interpreting data, that's the problem worth solving. Centralising data into a queryable store and building a thin read-only UI on top of it is more approachable than it sounds — and the productivity gain is real from day one.
Frequently Asked Questions
How often does the Google Ads data sync?
The Cloudflare Cron Worker runs once daily at 05:00 UTC. The dashboard header shows a "Last synced" timestamp for each account so clients always know how current the data is. Data is typically no more than 24 hours stale.
Does the dashboard make live calls to the Google Ads API?
No. All Google Ads data is pre-synced into Supabase by the cron worker. The browser reads from Supabase only — there are no live API calls from the client, which keeps response times fast and avoids quota issues.
How is client data kept separate in a multi-tenant setup?
Every Google Ads account row in Supabase has a client_id foreign key. Supabase Row Level Security (RLS) policies ensure queries only return rows matching the authenticated user's assigned client IDs. The user_client_access table controls which clients and sections each user can see.
What does the search terms tab show and why is it useful?
The search terms tab shows the actual text queries that triggered ad impressions, along with their clicks, impressions, and conversions. This is more actionable than the campaign view alone — it shows exactly where spend is going at the query level, which is the starting point for bid adjustments, negative keyword lists, and new ad copy.
Can clients see each other's data?
No. The dashboard is multi-tenant but fully isolated. A client user can only see the client(s) their account has been granted access to, and only the sections within that client that have been enabled. Admins can see all clients via a switcher dropdown.
What is GAQL and why does it matter for a custom integration?
GAQL is Google Ads Query Language — a SQL-like query syntax used to fetch data from the Google Ads API. It gives precise control over which fields, date ranges, and resource types you pull, which is important for keeping sync payloads small and avoiding hitting API rate limits when managing multiple accounts.
What is the AI chatbot tab you mentioned?
It's a feature in active development that lets clients ask natural language questions about their combined performance data — for example "which campaign drove the most leads last month?" or "what are my top landing pages by conversion rate?" The model queries across Supabase schemas (ads, analytics, seo, leads) to synthesise answers that no single tab can produce on its own.