Connect Google Analytics 4 to Claude (Code): three ways, and what each one is for
There are three ways to connect Google Analytics 4 to Claude or Claude Code, and none of them starts with a GA4 export. Google's official Google Analytics MCP server runs on your own machine and answers from the GA4 Data and Admin APIs with a read-only scope. Hosted connectors from data vendors such as Supermetrics and Windsor.ai add GA4 to Claude from its connector directory and fetch the numbers when you ask. A synced data backend such as TableBI copies GA4 into tables that your agent queries with read-only SQL, next to Search Console and your ad spend. The first two ask GA4 live, which suits one-off questions; the third stores the numbers under fixed definitions, which suits questions that come back every week. Below: a side-by-side comparison, the setup for each route, and four GA4 queries that work in Claude Code once the data is synced.
Three ways to connect GA4 to Claude, compared
"Connect Google Analytics to Claude" now names three different products. Google publishes an official MCP server that can only read. Data-connector vendors sell hosted Claude connectors that read GA4 and, for ad platforms, can also make changes. A synced backend — the route the second half of this page sets up — keeps a copy of GA4 that your agent queries in SQL alongside your other channels.
| Route | What the agent reads | Can it change anything? | What you set up | Price |
|---|---|---|---|---|
| Google's official Google Analytics MCP server | Live GA4 Data API and Admin API results: account and property details, core, funnel and realtime reports, custom dimensions and metrics | No — it requests only the analytics.readonly scope | pipx, a Google Cloud project with the Analytics Admin and Data APIs enabled, and Application Default Credentials; then one claude mcp add. It runs on your machine | Free, open source (Apache 2.0); the repository labels it experimental |
| Supermetrics' Claude connector | GA4 reporting data, fetched each time you ask, through a connector listed in Claude's directory | Not GA4 — its campaign management covers ad platforms such as Google Ads and Facebook Ads | Click Connect on the Supermetrics listing in Claude's directory, then authorize GA4 | 14-day free trial, then a paid plan |
| Windsor.ai's Claude connector | GA4 metrics and dimensions through Windsor.ai's hosted MCP connector, pulled when you ask | Not GA4 — its write actions cover ad platforms | Link the property at onboard.windsor.ai, then add the Windsor connector in Claude | Free plan; paid plans from $19/month billed yearly, as of September 2026 |
| TableBI (this page) | A synced copy of GA4 — date, property, channel and country with sessions, users, page views, conversions and revenue — in read-only SQL, beside Search Console, Google Ads and Meta Ads (beta) | No — read-only by design, and nothing is installed on your website | tablebi install once, then one OAuth: tablebi connect ga4. Works in Claude Code | Free for one source and one live dashboard; see pricing |
Sources, read September 26, 2026 (Google's README re-read October 1, 2026, unchanged on tools, scope and setup): googleanalytics/google-analytics-mcp (tools, read-only scope, setup, license), Supermetrics' GA4 to Claude page, Windsor.ai's GA4 to Claude guide and Windsor.ai pricing.
Pick by the job. If you already run a Google Cloud project and want ad-hoc GA4 answers in Claude Code for free, Google's MCP server is the direct route: set up credentials once, and every question becomes a live Data API call. If you want GA4 inside the Claude app with no terminal — or in the same connector as ad platforms you may also want Claude to operate — a vendor connector is the shortest path. If the question comes back every week and crosses sources — sessions against Search Console clicks, GA4 conversions against ad spend — a synced backend is built for it, because the numbers are stored once under shared definitions instead of being re-fetched and re-reconciled in every conversation. GA4 alone rarely answers "was that campaign worth it"; the join is the work.
Route 1: Google's GA4 MCP server in Claude Code
Google's server is a Python package, analytics-mcp, that Claude Code starts as a local process. Setup, per its README, has four parts: install pipx; enable the Google Analytics Admin API and the Google Analytics Data API in a Google Cloud project; configure Application Default Credentials for a user who can see your GA4 properties, including the read-only scope; then register the server with Claude Code:
# Application Default Credentials with the read-only Analytics scope gcloud auth application-default login \ --scopes https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform \ --client-id-file=YOUR_CLIENT_JSON_FILE # register the server with Claude Code claude mcp add analytics-mcp --scope user \ -e "GOOGLE_APPLICATION_CREDENTIALS=PATH_TO_CREDENTIALS_JSON" \ -e "GOOGLE_PROJECT_ID=YOUR_PROJECT_ID" \ -- pipx run analytics-mcp
From then on Claude can call tools such as run_report, run_funnel_report and run_realtime_report. Two things to know before relying on it. Each answer is a fresh Data API request in GA4's own dimension and metric names, so nothing is stored between sessions. And it sees GA4 only — putting sessions next to ad spend means a second connector and a join that the model does in its head.
Route 2: a vendor connector in the Claude app
Supermetrics and Windsor.ai both list a connector in Claude's directory. With Supermetrics you click Connect on its listing and authorize GA4. With Windsor.ai you link the GA4 property at onboard.windsor.ai first, then add the Windsor connector in Claude. Both fetch GA4 data when you ask a question, and both carry many other sources on the same connector. Supermetrics starts with a 14-day trial and Windsor.ai has a free plan; beyond that, both are paid. Their write features exist for ad platforms; neither changes your GA4 configuration.
Route 3: sync GA4 into a backend Claude Code queries
TableBI is a data backend built to be driven from the terminal by an agent like Claude Code. GA4 is a live connector: one OAuth round-trip in the browser, and syncs run on a schedule from then on. Three moves — Pipe the source in, Ask in read-only SQL, Pin the answer as a live dashboard.
# install the CLI, then teach your agent to drive it npm i -g @tablebi/cli tablebi login tablebi install # browser opens for OAuth → first sync (one property is picked for you; # with several, it lists them — re-run with --account <property id>) tablebi connect ga4 # what's connected, and how fresh is each source? tablebi context
tablebi install plants a skill in ~/.claude/skills; after that, "how did organic do last week?" inside Claude Code is enough — the agent knows which commands to run. The connector requests Google's read-only Analytics scope and reads GA4 through the API, so there is no tag, script or plugin to add to your site, no Data API client to maintain and no service account key in your repo.
No second model in the loop: TableBI hosts no LLM. Your Claude Code session is the brain; the backend returns deterministic SQL results plus a trust block with each source's freshness and caveats.
Two altitudes: ga4_raw and facts
GA4 data lands at two levels, and knowing which to query is most of the skill:
ga4_sources_rawandga4_ai_landing_raw— two smaller GA4 tables next to the main one: sessions, users, conversions and revenue bysourceandmedium, and the landing pages of visits that came from AI assistants such as ChatGPT, Perplexity, Claude and Gemini. Theai_assistant(source)macro recognizes those sources, so "how much traffic does ChatGPT send us, and to which pages?" is one query.ga4_raw— what GA4 reported:date,property,channel,country,sessions,users,page_views,conversions,revenue. Channel and country breakdowns live here, and so does forensics when a number looks wrong.facts— the cross-channel table where GA4 sits beside Search Console, Google Ads and Meta Ads (beta) under shared dimensions (date,platform,account,campaign) and shared measures (impressions,clicks,cost,conversions,conversion_value). For GA4 rows the account is the property; channel and country are empty here. This is where "was it worth it" gets answered.
Start any session by rehydrating — definitions, connected sources, freshness and the command cheatsheet in one JSON blob the agent holds in context:
tablebi context --json
Four GA4 queries worth stealing
1. Sessions and conversions by channel. The default "where is traffic actually coming from" question, over the last 28 days:
tablebi ask "SELECT channel, SUM(sessions) AS sessions,
SUM(conversions) AS conv, SUM(revenue) AS revenue
FROM ga4_raw
WHERE date >= (SELECT MAX(date) FROM ga4_raw) - 28
GROUP BY channel ORDER BY sessions DESC"
2. Week over week, by channel. Trends beat totals — this is the shape that tells you whether a dip is real or a weekday artifact:
tablebi ask "SELECT channel,
SUM(CASE WHEN date > (SELECT MAX(date) FROM ga4_raw) - 7
THEN sessions ELSE 0 END) AS this_week,
SUM(CASE WHEN date <= (SELECT MAX(date) FROM ga4_raw) - 7
THEN sessions ELSE 0 END) AS prev_week
FROM ga4_raw
WHERE date >= (SELECT MAX(date) FROM ga4_raw) - 14
GROUP BY channel ORDER BY this_week DESC"
3. Conversion rate per channel, without doing the arithmetic in the model. Definition macros (ctr, cpa, cvr, roas, aov…) live in the query engine, so the agent never re-derives a ratio slightly wrong:
tablebi ask "SELECT channel, cvr(SUM(conversions), SUM(sessions)) AS cvr,
SUM(conversions) AS conv
FROM ga4_raw
WHERE date >= (SELECT MAX(date) FROM ga4_raw) - 28
GROUP BY channel ORDER BY conv DESC"
4. The one GA4 can't answer alone — spend against outcome. Same table, both sides of the equation:
tablebi ask "SELECT platform, SUM(cost) AS spend,
SUM(conversions) AS conv, cpa(SUM(cost), SUM(conversions)) AS cpa
FROM facts
WHERE date >= (SELECT MAX(date) FROM facts) - 28
GROUP BY platform ORDER BY spend DESC"
Notice what every window has in common: (SELECT MAX(date) FROM …) - N, never current_date, which the query layer doesn't offer — so a window can't silently include days that haven't synced. The newest day is still provisional: Google's own documentation says GA4 data processing can take 24–48 hours, and reports can change during that time. That is why the trust block lists, for each source, the newest synced day and the newest day every active property has reached — the same discipline we walk through in Claude Code analytics.
Using Codex instead of Claude Code
Both routes that run on your machine work in OpenAI's Codex as well. Codex reads MCP servers from ~/.codex/config.toml (or a project's .codex/config.toml), and OpenAI's MCP page says the Codex CLI, the IDE extension and the ChatGPT desktop app share that configuration. Google's GA4 server goes in as a [mcp_servers] table with the same command and environment variables its README gives for Claude Code:
[mcp_servers.analytics-mcp]
command = "pipx"
args = ["run", "analytics-mcp"]
env = { GOOGLE_APPLICATION_CREDENTIALS = "PATH_TO_CREDENTIALS_JSON", GOOGLE_PROJECT_ID = "YOUR_PROJECT_ID" }
For TableBI, tablebi install --codex plants the same skill in ~/.agents/skills, so Codex drives the CLI the way Claude Code does. If you would rather give Codex tools as well, tablebi install --codex --mcp also appends a [mcp_servers.tablebi] table to ~/.codex/config.toml for TableBI's built-in local MCP server (tablebi mcp: context, query, sql, define and dashboards). There is no hosted TableBI MCP endpoint; both routes run the CLI on your machine with your own sign-in.
Errors you will run into
| Where | What you see | What to do |
|---|---|---|
| Google's MCP server | Permission or "API not enabled" errors on the first call | Enable both the Analytics Admin API and the Analytics Data API in the Cloud project, and make sure the Application Default Credentials include the analytics.readonly scope, as the README requires |
| Google's MCP server | Your property is missing from get_account_summaries | The credentials belong to a Google user who can't see that property; sign in with one who can |
| Google's MCP server | Claude or Codex doesn't list the server | Check the path in GOOGLE_APPLICATION_CREDENTIALS and that pipx run analytics-mcp starts in a terminal on its own |
| TableBI | Google shows "This app isn't verified" before the consent page | TableBI's Google verification is in review: choose Advanced, then continue to console.tablebi.com. The scope is read-only |
| TableBI | connect ga4 returns a list instead of syncing | Your account sees several properties: rerun with --account <property id> |
| TableBI | Yesterday's sessions look low | GA4 keeps processing the last 24–48 hours; the answer's trust block says how far each source runs |
| Any route | GA4 organic sessions don't match Search Console clicks | They count different things; see why Search Console clicks don't match GA4 sessions |
The trap: GA4 sessions are not Search Console clicks
The most common bad analysis in this category is an agent trying to reconcile GA4 sessions with Search Console clicks, finding a gap, and confidently inventing a story about broken tracking. They are different populations counted different ways: a Search Console click is a click on a search result; a GA4 session is on-site behavior with its own deduplication and its own consent-dependent losses. They were never supposed to be equal.
So every answer ships with a trust block — per-source freshness, caveats and lineage. The Search Console-versus-GA4 mismatch is written down as an explicit caveat, and the agent reads it before you do, so it explains the gap instead of raising a false alarm. If Search Console is your main lane, Claude Code for SEO covers that side in depth, and why Search Console clicks don't match GA4 sessions lists every documented cause.
Pin it: GA4 on a live URL
When a GA4 view is worth keeping, don't screenshot it — pin it. A title, a chart kind after two colons (line, bar, hbar, pie, area, scorecard; no suffix renders a table), and the query:
tablebi pin --title "GA4 traffic overview" \ --widget "Daily sessions (28d)::line=SELECT date, SUM(sessions) AS sessions FROM ga4_raw WHERE date >= (SELECT MAX(date) FROM ga4_raw) - 28 GROUP BY date ORDER BY date" \ --widget "Sessions by channel::hbar=SELECT channel, SUM(sessions) AS sessions FROM ga4_raw WHERE date >= (SELECT MAX(date) FROM ga4_raw) - 28 GROUP BY channel ORDER BY sessions DESC" ✓ published → https://dk.tablebi.com/d/dsh_… (public, read-only, self-refreshing)
The output is a URL you hand to a client or a co-founder — no React app, no BI seat. Here is a live dashboard on real data you can open now: Search Console totals across the TableBI founder's own sites. It is a different dashboard from the one described above, published the same way, and it re-runs its queries as the sources sync (what the demo shows, and what it doesn't):