Templates · Queries, not files

SEO report example

Seven widgets that earn their slot — and the SQL behind each one.

Updated September 5, 2026

A useful SEO report answers four questions: is search traffic growing, which queries and pages moved, what is close to page one, and what broke. Everything else is decoration that makes the report longer without making it more decidable.

The example below is seven widgets against Google Search Console, with the query for each. It is deliberately not a file to download: a static report is stale the day after it is generated, and the next client means rebuilding it. These queries run against your connected Search Console property and tablebi pin turns the set into one public URL that refreshes on its own — this one is live now, running on real portfolio data.

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

terminal
npm i -g @tablebi/cli
tablebi login
tablebi install

tablebi connect gsc --site sc-domain:example.com

Source for this template: Google Search Console. One connected source and one live dashboard fit inside the free tier.

The widgets

1. Clicks and impressions trend

Is search traffic growing, and is it growing because of visibility or because of relevance?

tablebi ask
WITH w AS (SELECT MAX(date) AS anchor FROM search_console_raw WHERE site = 'example.com')
SELECT date_trunc('week', date) AS week,
       SUM(clicks) AS clicks,
       SUM(impressions) AS impressions,
       ctr(SUM(clicks), SUM(impressions)) AS ctr
FROM search_console_raw, w
WHERE site = 'example.com' AND date > w.anchor - 90
GROUP BY 1 ORDER BY 1

2. Query movers

Which queries gained or lost the most impressions against the previous period?

tablebi ask
WITH w AS (SELECT MAX(date) AS anchor FROM search_console_raw WHERE site = 'example.com')
SELECT query,
       SUM(impressions) FILTER (WHERE date > w.anchor - 28) AS now_impr,
       SUM(impressions) FILTER (WHERE date <= w.anchor - 28) AS prev_impr
FROM search_console_raw, w
WHERE site = 'example.com' AND date > w.anchor - 56
GROUP BY query
HAVING SUM(impressions) > 50
ORDER BY (now_impr - prev_impr) DESC
LIMIT 25

3. Striking distance

Which queries sit just off page one, where a small move is worth real clicks?

tablebi ask
WITH w AS (SELECT MAX(date) AS anchor FROM search_console_raw WHERE site = 'example.com')
SELECT query,
       avg_position(SUM(position * impressions), SUM(impressions)) AS pos,
       SUM(impressions) AS impressions
FROM search_console_raw, w
WHERE site = 'example.com' AND date > w.anchor - 28
GROUP BY query
HAVING avg_position(SUM(position * impressions), SUM(impressions)) BETWEEN 8 AND 20
   AND SUM(impressions) > 100
ORDER BY impressions DESC
LIMIT 25

4. Page performance

Which pages carry the traffic, and which are indexed but earning nothing?

tablebi ask
WITH w AS (SELECT MAX(date) AS anchor FROM search_console_raw WHERE site = 'example.com')
SELECT page,
       SUM(clicks) AS clicks,
       SUM(impressions) AS impressions,
       ctr(SUM(clicks), SUM(impressions)) AS ctr
FROM search_console_raw, w
WHERE site = 'example.com' AND date > w.anchor - 28
GROUP BY page ORDER BY impressions DESC LIMIT 30

5. Low-CTR at good position

Which pages rank well but are not being clicked — a title and snippet problem, not a ranking one?

tablebi ask
WITH w AS (SELECT MAX(date) AS anchor FROM search_console_raw WHERE site = 'example.com')
SELECT page,
       avg_position(SUM(position * impressions), SUM(impressions)) AS pos,
       ctr(SUM(clicks), SUM(impressions)) AS ctr,
       SUM(impressions) AS impressions
FROM search_console_raw, w
WHERE site = 'example.com' AND date > w.anchor - 28
GROUP BY page
HAVING avg_position(SUM(position * impressions), SUM(impressions)) < 10
   AND ctr(SUM(clicks), SUM(impressions)) < 0.02
   AND SUM(impressions) > 200
ORDER BY impressions DESC

6. Branded against non-branded

Is growth coming from people who already knew you, or from demand you earned?

tablebi ask
WITH w AS (SELECT MAX(date) AS anchor FROM search_console_raw WHERE site = 'example.com')
SELECT CASE WHEN lower(query) LIKE '%yourbrand%' THEN 'branded' ELSE 'non-branded' END AS kind,
       SUM(clicks) AS clicks,
       SUM(impressions) AS impressions,
       ctr(SUM(clicks), SUM(impressions)) AS ctr
FROM search_console_raw, w
WHERE site = 'example.com' AND date > w.anchor - 28
GROUP BY 1

7. Query and page pairs

Which query is actually carrying each page — the pairing that tells you what a page is really ranking for.

tablebi ask
WITH w AS (SELECT MAX(date) AS anchor FROM search_console_raw WHERE site = 'example.com')
SELECT page, query,
       SUM(impressions) AS impressions,
       avg_position(SUM(position * impressions), SUM(impressions)) AS pos
FROM search_console_raw, w
WHERE site = 'example.com' AND date > w.anchor - 28
GROUP BY page, query
HAVING SUM(impressions) > 20
ORDER BY impressions DESC LIMIT 30

What to leave out

Three things appear in most SEO report templates and change no decision:

  • A domain authority score. A third-party estimate of a Google signal that Google does not publish. It moves slowly, you cannot act on it directly, and it invites arguments about the vendor's model rather than about the site.
  • Total keyword counts. "Ranking for 4,182 keywords" counts positions 90 through 100 the same as position 3.
  • A backlink count without context. Referring domains that matter and spam that arrived on its own land in the same number.

If a widget cannot finish the sentence "if this moves, we will…", it is decoration.

The caveat that belongs on the report itself

Search Console runs two to three days behind, and it applies a privacy threshold that anonymises very low-frequency queries — which means the sum of your per-query clicks is smaller than your total clicks, always. That is not missing data and it is not a bug in the report.

Every answer TableBI returns carries that caveat with it, alongside per-source freshness, so a reader who notices the discrepancy finds the explanation attached rather than emailing you about it. Anything you hand to a client should carry the same note.

From seven queries to one URL

Once the widgets are right, pinning turns the set into a hosted dashboard that re-runs its own queries on a schedule. The client gets a link rather than an attachment, and next month is not a rebuild.

Pin the set as one live URL

terminal
tablebi pin --title "SEO report example"
→ 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 an SEO report include?

Four things: a clicks and impressions trend, the queries and pages that moved most against the previous period, queries sitting just off page one where a small gain is worth real clicks, and anything that broke — pages that lost their status or fell out of the index. Everything else usually makes the report longer without making it more decidable.

Why do my per-query clicks add up to less than my total clicks?

Search Console anonymises very low-frequency queries under a privacy threshold, so any breakdown by query sums to less than the reported total. It is expected behaviour, not lost data, and the gap is usually larger on smaller sites.

How often should an SEO report be updated?

Monthly for a client-facing narrative, because Search Console data lags two to three days and weekly noise swamps the signal on most sites. A live dashboard that refreshes itself removes the question — the reader looks whenever they want and the numbers are as current as the source allows.

Is there a free SEO report template?

Plenty of vendors publish free Looker Studio and Google Sheets templates you copy and connect. This page is the other kind: the queries themselves, which run on TableBI free tier against one connected Search Console property and pin to one live public URL.

Want the arithmetic before the wiring? The free marketing calculators run these same formulas in the browser.