Client reporting template
What belongs in a monthly client report — and why a link beats an attachment.
A monthly client report has one job: let the client decide whether to keep spending, without a call. That takes about six widgets and one paragraph of narrative, and most templates ship twenty.
The structure below is the six, with the query for each. The delivery is the part that differs from every other template on this page: instead of generating a PDF each month, you pin the set once and the client gets a read-only URL that refreshes itself. No login for them, no rebuild for you.
If what you want is a file to download and hand over — a Looker Studio copy, a Google Sheets tab, a branded PDF — the vendors ranking on this query have those and TableBI does not. This page is the other kind of template: the queries, and a URL that keeps answering them.
Connect the source
npm i -g @tablebi/cli tablebi login tablebi install tablebi connect gsc --site sc-domain:client.com tablebi connect ga4 --property 123456789 tablebi connect google_ads --account 1234567890
Source for this template: whatever the client has connected — Search Console, GA4, Google Ads, Meta. One connected source and one live dashboard fit inside the free tier.
The widgets
1. Headline
Spend, revenue and blended efficiency for the month, against last month.
WITH w AS (SELECT MAX(date) AS anchor FROM metrics)
SELECT SUM(cost) AS spend,
SUM(revenue) AS revenue,
roas(SUM(revenue), SUM(cost)) AS roas
FROM metrics, w
WHERE date > date_trunc('month', w.anchor) - INTERVAL '1 month'
GROUP BY date_trunc('month', date)
ORDER BY 1
2. Channel contribution
Where the money went and what each channel returned.
WITH w AS (SELECT MAX(date) AS anchor FROM metrics)
SELECT platform, SUM(cost) AS spend,
roas(SUM(revenue), SUM(cost)) AS roas,
SUM(conversions) AS conversions
FROM metrics, w
WHERE date > w.anchor - 30
GROUP BY platform ORDER BY spend DESC
3. Organic trend
Whether the non-paid side is compounding or flat.
WITH w AS (SELECT MAX(date) AS anchor FROM search_console_raw WHERE site = 'client.com')
SELECT date_trunc('week', date) AS week,
SUM(clicks) AS organic_clicks,
SUM(impressions) AS impressions
FROM search_console_raw, w
WHERE site = 'client.com' AND date > w.anchor - 90
GROUP BY 1 ORDER BY 1
4. Site behaviour
Did the traffic do anything once it arrived?
WITH w AS (SELECT MAX(date) AS anchor FROM ga4_raw)
SELECT channel,
SUM(sessions) AS sessions,
SUM(conversions) AS conversions,
SUM(revenue) AS revenue
FROM ga4_raw, w
WHERE date > w.anchor - 30
GROUP BY channel ORDER BY sessions DESC
5. What moved
The three biggest changes against the previous period, in either direction.
WITH w AS (SELECT MAX(date) AS anchor FROM metrics)
SELECT platform, campaign,
SUM(cost) FILTER (WHERE date > w.anchor - 30) AS spend_now,
SUM(cost) FILTER (WHERE date <= w.anchor - 30) AS spend_prev,
SUM(revenue) FILTER (WHERE date > w.anchor - 30) AS rev_now,
SUM(revenue) FILTER (WHERE date <= w.anchor - 30) AS rev_prev
FROM metrics, w
WHERE date > w.anchor - 60
GROUP BY platform, campaign
ORDER BY ABS(COALESCE(rev_now,0) - COALESCE(rev_prev,0)) DESC
LIMIT 10
6. Next month
Not a query — one paragraph you write, naming what changes and why.
The widget that is not a query
The last row above has no SQL because it should not have any. A client report without a written recommendation is a data dump, and the client will ask for the recommendation on a call anyway — which is the call the report was supposed to replace.
Three sentences is enough: what changed, what you are doing about it, what you need from them. Write it after reading the six widgets, not before.
Put the caveats in the report, not in the follow-up email
Clients notice discrepancies, and every multi-source report has them. Three are guaranteed:
- Search Console clicks are not GA4 sessions. Different populations, different deduplication. They are not supposed to match.
- Per-query Search Console numbers sum to less than the total, because rare queries are anonymised under a privacy threshold.
- Platform-reported revenue overlaps. Summing it exceeds real revenue whenever a buyer touched two channels.
Stating these in the report costs three lines and prevents the email that costs an hour. Every TableBI answer carries them attached to the number they apply to, so they are not something you have to remember to write.
A link instead of an attachment
A PDF is a snapshot of a moment nobody chose, and it generates the "can you re-send with the latest numbers" request about a week later. tablebi pin publishes the widget set as a public read-only URL that re-runs its own queries, so the client looks whenever they want.
The honest limitation: that URL is on a tablebi.com subdomain and carries no branding of yours. There is no white-label option and no custom domain today. If the report has to look like it came from you, the reporting platforms are built for exactly that and this is not.
Pin the set as one live URL
tablebi pin --title "Client reporting template" → https://you.tablebi.com/d/dsh_… # public, read-only, refreshes itself
The URL needs no login from whoever you send it to, and it re-runs its own queries rather than freezing a moment. Here is one running on real data. It carries no branding of yours — there is no white-label or custom-domain option today, which is the trade against a reporting platform.
Questions people ask
What should be in a monthly client report?
A headline with spend, revenue and blended efficiency against last month; channel contribution; an organic trend; what the traffic did after it arrived; the biggest changes in either direction; and one written paragraph naming what you will do next. Six widgets and a recommendation, not twenty charts.
How do I explain why Search Console and GA4 numbers do not match?
They measure different things. Search Console clicks count a result being clicked in search; GA4 sessions count behaviour after landing, with its own deduplication and attribution. They are different populations and should not be expected to agree — say so in the report rather than reconciling them.
Can I white-label a TableBI client dashboard?
No. A pinned dashboard is a public read-only URL on a tablebi.com subdomain with no custom branding or custom domain. If branded delivery matters, AgencyAnalytics, DashThis and Whatagraph all build their products around it.
Is a live dashboard better than a monthly PDF?
For most clients yes, because it removes the "can you re-send with current numbers" loop and the report is never stale. A PDF still wins where a fixed record of the month is contractually required, or where the reader will not open a link.
Other templates
SEO report example
Seven widgets that earn their slot — and the SQL behind each one.
PPC report template
Six widgets across Google Ads and Meta, on one definition of cost and revenue.
Marketing dashboard examples
Four layouts, the queries behind them, and the test each tile has to pass.
Want the arithmetic before the wiring? The free marketing calculators run these same formulas in the browser.