Claude Code for SEO: query Search Console from your terminal
Claude Code becomes genuinely useful for SEO the moment it can read your Search Console data — and that takes one OAuth command. Most Claude Code SEO advice stops at crawl scripts and content briefs. Fine, but the questions that decide what you actually do on Monday — which queries are slipping, which pages earn less CTR than their position should, whether that dip is real or just Google's reporting lag — are data questions. This guide wires Claude Code to Google Search Console, walks through three queries worth stealing, covers the 2–3 day GSC lag that fools naive automations, and ends with a live pinned dashboard (embedded below — real data, not a mockup).
Where Claude Code fits in an SEO workflow
A coding agent already covers more of SEO than most people expect, because so much of technical SEO is code: it can audit your templates for canonical and hreflang mistakes, trace a redirect chain through your config, regenerate a sitemap, or grep server logs for crawl anomalies. It's also a competent drafting partner for briefs and on-page rewrites. What it cannot do, bare, is see performance — clicks, impressions, positions live in Search Console, behind OAuth and an API with its own quirks. So the loop breaks exactly where it matters: the agent can propose fixes but can't check what needs fixing or whether last month's fix worked.
You can bridge that with pasted CSV exports, but every export is a snapshot that's stale by tomorrow, and the agent burns context re-learning your column conventions each session. The durable fix is a data backend the agent can query directly — that's what TableBI is, and the general version of this argument is in Claude Code for marketing. Below is the SEO-specific path.
Set up the Claude Code SEO skill
Two minutes of setup: install the CLI, sign in, and plant the skill that teaches your agent the whole vocabulary. (Skills are markdown runbooks in ~/.claude/skills — the mechanism is worth understanding, and we unpack it in Claude Code marketing skills.)
npm i -g @tablebi/cli tablebi login tablebi install
Then connect Search Console. One command per property; the browser opens for OAuth:
tablebi connect gsc --site sc-domain:example.com
Data syncs into two altitudes. search_console_raw holds rows exactly as GSC reports them — query, page, position, clicks, impressions, by date — nothing averaged away. The unified facts table holds the cross-channel view, useful once GA4 or ad platforms join the party. Each new Claude Code session starts by rehydrating context — definitions, connected sources, freshness, command cheatsheet:
tablebi context --json
From here you stop typing commands and start stating intent — "which queries are slipping?" — and the agent writes the SQL. The next section shows what it writes.
Three Search Console queries worth stealing
1. Queries gaining or losing impressions. Compare the last 14 days against the 14 before — the fastest way to see a core update or a decaying page portfolio in one table. Note the window anchors to MAX(date) in the data, not the wall clock:
tablebi ask "SELECT query,
SUM(CASE WHEN date >= (SELECT MAX(date) FROM search_console_raw) - 13
THEN impressions ELSE 0 END) AS imp_recent,
SUM(CASE WHEN date < (SELECT MAX(date) FROM search_console_raw) - 13
THEN impressions ELSE 0 END) AS imp_prior
FROM search_console_raw
WHERE date >= (SELECT MAX(date) FROM search_console_raw) - 27
GROUP BY query
ORDER BY (imp_recent - imp_prior) ASC LIMIT 20"
2. Pages whose CTR underperforms their position. Ranking in the top five but getting clicked like page two usually means the title or snippet is losing the SERP — the cheapest wins in SEO, because a rewrite needs no new links:
tablebi ask "SELECT page, AVG(position) AS avg_pos,
ctr(SUM(clicks), SUM(impressions)) AS ctr,
SUM(impressions) AS impressions
FROM search_console_raw
WHERE date >= (SELECT MAX(date) FROM search_console_raw) - 28
GROUP BY page
HAVING AVG(position) <= 5
ORDER BY ctr ASC LIMIT 20"
That ctr() is a definition macro — the backend computes it the same way in every query, so the agent never re-derives it slightly differently on a Tuesday.
3. The portfolio view. If you run several properties — client sites, a programmatic portfolio, docs plus marketing site — aggregate by site for the one-glance answer to "where did this week's traffic move?":
tablebi ask "SELECT site, SUM(clicks) AS clicks, SUM(impressions) AS impressions,
ctr(SUM(clicks), SUM(impressions)) AS ctr
FROM search_console_raw
WHERE date >= (SELECT MAX(date) FROM search_console_raw) - 28
GROUP BY site ORDER BY clicks DESC"
The 2–3 day GSC lag, and how not to get fooled by it
Here's the trap every homegrown Search Console automation falls into at least once: Google's final GSC data officially lags around two to three days. A script that checks "yesterday's clicks" reads zero, and someone gets paged over a traffic collapse that never happened.
Two mechanisms keep the agent honest here. First, every query answer carries a trust block: per-source freshness (the exact date each source runs through), caveats, and lineage. Your agent reads "search_console: data through Monday" and reports the window it actually analyzed instead of raising a false alarm — and the caveats stop category errors like comparing GSC clicks with GA4 sessions, which are different populations and shouldn't be expected to match. Second, the query layer itself refuses clock-anchored date math like current_date; windows are written against the data's own high-water mark:
-- anchors to the freshest row, not the wall clock, -- so a 28-day window is 28 days of *actual* data WHERE date >= (SELECT MAX(date) FROM facts) - 28
Rule of thumb: treat GSC's last 2–3 days as provisional. Anchor every window to MAX(date), and let the freshness readout — not your memory — say how current the numbers are.
Pin a weekly SEO dashboard
Once a query earns its keep, stop re-running it — pin it. One command turns SQL into a dashboard at a public, read-only URL that refreshes itself as new data syncs in (on a schedule, and when someone opens it):
tablebi pin --title "SEO weekly" \ --widget "Daily clicks (28d)::line=SELECT date, SUM(clicks) AS clicks FROM search_console_raw WHERE date >= (SELECT MAX(date) FROM search_console_raw) - 28 GROUP BY date ORDER BY date" \ --widget "Top queries=SELECT query, SUM(clicks) AS clicks, SUM(impressions) AS impressions FROM search_console_raw WHERE date >= (SELECT MAX(date) FROM search_console_raw) - 28 GROUP BY query ORDER BY clicks DESC LIMIT 20" ✓ published → https://dk.tablebi.com/d/dsh_… (public, read-only, self-refreshing)
::line renders a line chart; widgets without it render as tables. The result is a link you can hand to a client or check from your phone. Here's a real one — an SEO portfolio overview pinned from live Search Console data exactly this way: