Marketing KPI dashboard: define the metrics once, then stop rebuilding
Almost nobody's marketing KPI dashboard fails because the charts were ugly. It fails because two people looked at "conversions" and meant two different things, or because the tab that fed it stopped being updated in March and nobody noticed until a decision got made on a stale number. The charting is the easy 10%. The other 90% is settling what each KPI means, wiring it to sources that keep themselves current, and making the thing readable without a briefing.
This guide is the version that survives: which KPIs earn a slot, how to define them so they hold across four platforms, and how to end up with a live URL instead of a file that needs a maintainer.
Why most KPI dashboards go stale in three weeks
Watch the lifecycle of a typical one. Someone builds it in a spreadsheet or a BI tool in a burst of Q1 enthusiasm. It looks great for a fortnight. Then:
- A source drifts. An ad account gets renamed, a campaign naming convention changes, someone adds a fifth channel. The dashboard doesn't break loudly — it just quietly under-reports, which is worse.
- The refresh becomes a chore. If a human has to export four CSVs on Monday morning, the dashboard has a staffing dependency. Staffing dependencies lose to busy weeks.
- Definitions fork. Paid reports ROAS from the platform. Finance computes revenue over spend from the invoice. Both are labelled "ROAS" and they don't match, so people stop trusting the panel and go back to opening four tabs.
Notice these are all maintenance failures, not design failures. The fix has to be structural: definitions that live in one place, sources that pull themselves, and a surface that regenerates instead of being edited.
The KPIs that actually earn a slot
A KPI dashboard is a decision instrument, not an inventory of every field your platforms expose. The useful test: has this number ever changed what someone did? If not, cut it. In practice a marketing KPI dashboard needs to answer three standing questions, and the metrics fall out of them.
1. Is demand growing?
Impressions and non-brand clicks from Search Console, sessions from GA4. Brand and non-brand behave differently enough that blending them hides the only interesting half — brand search usually tracks what your other channels did last month, while non-brand tracks whether your content is winning new ground.
2. Is paid efficient?
Spend, CPA and ROAS by platform, with clicks and CTR one level down as the diagnostic. CPA answers "what did a customer cost", ROAS answers "did the money come back", and you want both because they fail in opposite directions — a campaign can hit target CPA on cheap, low-value conversions and still lose money.
3. Is anything broken?
The delta view: what moved more than usual since last week. A page that lost half its impressions, a campaign whose CTR fell off a cliff, a source that stopped reporting. This is the panel people actually scan first, and it's the one most dashboards omit.
Rule of thumb: seven KPIs on the front panel, everything else one query away. A dashboard with thirty tiles gets read like wallpaper.
The definition problem nobody puts on the dashboard
Here's the part that quietly ruins cross-channel KPIs. Every platform counts with its own rules, and none of them are wrong — they're answering different questions:
- Attribution windows differ. Meta and Google Ads both credit conversions back to a click, but not over the same window and not with the same view-through treatment. Summing their conversion counts gives you a number that belongs to no one.
- GSC clicks are not GA4 sessions. One counts clicks on a search result, the other counts what happened after the landing, with its own deduplication. Expecting them to match is a category error, and it's the single most common way a KPI dashboard loses credibility on first read.
- "Revenue" is at least three things. Platform-reported conversion value, GA4 purchase value, and what actually cleared in your billing system.
The structural answer is a definition layer: one place where spend is spend, clicks are clicks, and derived KPIs are macros rather than arithmetic retyped per chart. That's how TableBI is organized — data lands in two altitudes. There's a unified facts table where every platform's columns are normalized into shared definitions, and there are per-platform raw tables underneath that keep everything lossless for when you need to see exactly what a platform reported.
KPIs come from macros over the unified table, so nobody re-derives the arithmetic slightly differently on the fourth chart:
# the paid-efficiency panel, in one query across every ad platform tablebi ask "SELECT platform, SUM(cost) AS spend, roas(SUM(conversion_value), SUM(cost)) AS roas, cpa(SUM(cost), SUM(conversions)) AS cpa, ctr(SUM(clicks), SUM(impressions)) AS ctr FROM facts WHERE date >= (SELECT MAX(date) FROM facts) - 28 GROUP BY platform ORDER BY spend DESC"
Build it: connect once, then query
Setup is a CLI install and one OAuth round-trip per source. The install step plants a skill so Claude Code knows the tool exists and which command to reach for — after that you can ask in English and the agent writes the SQL.
npm i -g @tablebi/cli tablebi login tablebi install # one browser round-trip each, then data syncs in tablebi connect gsc --site sc-domain:example.com tablebi connect ga4 tablebi connect google_ads tablebi connect meta_ads # anything without a live connector arrives as CSV, same definitions tablebi connect csv --file tiktok-export.csv --platform tiktok_ads
Before building anything, get the lay of the land in one call — connected sources, available definitions, how fresh each source is:
tablebi context --json
That freshness field matters more than it looks. Search Console finalizes on a multi-day lag; ad platforms settle within about a day. A KPI dashboard that shows today as a cliff is usually just showing an incomplete day, and the fastest way to destroy trust in a panel is to let someone read that cliff as a collapse. Every answer here ships with a trust block carrying that freshness plus the caveats — including the GSC-clicks-are-not-GA4-sessions one — so the agent reading it stops short of the wrong comparison.
Pin it: the KPI dashboard as a URL
Once the queries are right, freeze them into a dashboard rather than screenshotting the terminal. Each widget is a title plus a query; the dashboard re-runs them as new data syncs, so it's current without anyone touching it:
tablebi pin --title "Marketing KPIs" \ --widget "Spend by platform (28d)=SELECT platform, SUM(cost) AS spend FROM facts …" \ --widget "ROAS trend::line=SELECT date, roas(SUM(conversion_value), SUM(cost)) AS roas FROM facts …" \ --widget "Non-brand clicks::line=SELECT date, SUM(clicks) AS clicks FROM search_console_raw …" ✓ published → https://dk.tablebi.com/d/dsh_… (public, read-only, self-refreshing)
Below is a real pinned dashboard built exactly this way — live Search Console data across a portfolio of sites. It's not a mockup; it refreshes as the sources sync: