How to connect Google Analytics to Claude
Hi, this is Vivek, building Contextflo. I share practical notes on getting answers from your data, a couple of times a month.

There are two ways to connect Google Analytics to Claude. Google's own Google Analytics MCP server lets Claude run GA4 reports after a short local setup. The GA4 export to BigQuery takes longer and gives Claude every raw event, so it can answer questions a GA report can't, and join your traffic to revenue and ad spend.
Start with the MCP server if you want quick answers about traffic this week. Set up the export if you've ever lost part of a report to an "(other)" row, or needed GA4 next to your orders table.
The quick route: Google's Analytics MCP server
Google publishes an official Google Analytics MCP server on GitHub.[1] It's read-only and still labeled experimental. It runs on your own machine on top of Google's Analytics Admin API and Data API, and gives the model these tools:
| Tool | What it does |
|---|---|
get_account_summaries | Lists the GA accounts and properties you can see |
get_property_details | Returns details about one property |
get_custom_dimensions_and_metrics | Lists the custom definitions on a property |
run_report | Runs a standard GA4 report through the Data API |
run_funnel_report | Runs a funnel report |
run_realtime_report | Runs a realtime report |
list_google_ads_links | Lists linked Google Ads accounts |
Setup, per the README:
- In a Google Cloud project, enable the Google Analytics Admin API and the Google Analytics Data API.
- Set up Application Default Credentials with the
https://www.googleapis.com/auth/analytics.readonlyscope, using an account that can read the property. - Install
pipxand register the server in your MCP client so it runspipx run analytics-mcp.
The README walks through Gemini CLI, and Claude Code is listed as a client too. Claude Desktop works the same way through its local MCP config. ChatGPT only takes remote MCP connectors, so you'd have to host the server somewhere first.
Once it's connected, "what were our top landing pages by sessions last week?" becomes a run_report call, and Claude reads the rows back to you. For the questions you'd normally answer by clicking around the GA4 UI, it's the fastest path.
Where it stops
Everything goes through the Data API, so you get what a GA4 report can give you, with the same limits:
- Rows come back aggregated. You get sessions by landing page, not the events behind them. Claude can't build its own session logic or check a suspicious number against the raw events.
- High-cardinality reports collapse into "(other)". Once a report passes its row limit, the remaining rows get grouped under "(other)", and dimensions with a lot of unique values a day make that likelier, so check Google's current thresholds rather than assuming your report is safe.[2] Page paths with query strings and campaign parameters hit it fast.
- Quotas are per property. A standard property has its own daily and hourly cap on Data API tokens, so check Google's current API quotas before assuming you have headroom, and demographic and audience dimensions can be thresholded for privacy.[3] Token cost grows with rows, date range and property volume, so an agent pulling long-range reports over and over can hit the hourly limit.
- It can't see anything else. Your orders and ad spend live somewhere else, so "which channel brings customers who actually pay?" has no answer here.
If none of that bothers you, you can stop here. If it does, the export is the fix.
The fuller route: export GA4 to BigQuery
GA4 can write every event it collects into BigQuery, free on standard properties. That gives Claude a table with one row per event, and SQL instead of a report API.
Turn on the export
You need Editor (or higher) on the GA4 property and Owner on the Google Cloud project.[4]
- In the Google Cloud Console, create or pick a project and enable the BigQuery API.
- In GA4, go to Admin > Product links > BigQuery links and click Link.
- Choose the project, then the data location. You can't change the location once the dataset exists, so put it where the rest of your data lives.
- Pick the data streams to include and any events to exclude.
- Choose Daily, Streaming, or both, then submit.
Data should start arriving within 24 hours.[4]
The limits that matter
| Daily export | Streaming export | |
|---|---|---|
| Table | events_YYYYMMDD, one per day | events_intraday_YYYYMMDD, filled continuously |
| Standard property cap | Has a daily event cap, set by Google | No event cap |
| Cost to export | Free | Priced per GB |
| When it lands | Usually mid-afternoon in the property's timezone, sometimes the next day | Continuously through the day |
The specifics come from Google's setup and export docs, and Google revises them from time to time, so check the current numbers there rather than planning around what's in this table.[4][5] A few things catch people out:
- There's no backfill. The export starts from the day you link it. Events collected before that stay in GA4 and never reach BigQuery.[5] So link it now, even if you won't use it for months. If you need history, the BigQuery Data Transfer Service has a GA4 connector that can backfill aggregated report tables like traffic acquisition and landing pages, one day at a time with a gap enforced between days, so check Google's current transfer service limits before planning a big backfill. It won't give you raw events.[6]
- The free sandbox deletes your data. BigQuery's sandbox needs no credit card, but every table in it expires after a fixed retention window, and it doesn't support streaming, so check Google's current sandbox limits before relying on it.[7] A sandbox export quietly becomes a rolling window that's shorter than you'd want. Add billing if you want to keep history, and estimate the storage bill from your own event counts.
- Daily tables keep changing for a few days. Late-arriving events get written into
events_YYYYMMDDfor a few days after the date. If you have streaming on, the intraday table is deleted once that day's daily table is complete.[8] A question about yesterday can give a slightly different answer tomorrow. - Busy properties can hit the daily cap. Once a property passes it, the daily export runs into its limit, so check Google's current cap rather than assuming you're under it. Google's answer is to exclude noisy events from the export or turn on streaming, which has no cap.[5]
What do the tables look like?
Everything lands in one dataset per property, named analytics_<property_id>.[8] Each row is one event, and the part that trips up both people and models is that most of the useful detail sits inside nested, repeated fields.
A single page_view row looks roughly like this:
{
"event_date": "20260915",
"event_timestamp": 1789466400123456,
"event_name": "page_view",
"user_pseudo_id": "1234567890.1789400000",
"event_params": [
{ "key": "page_location", "value": { "string_value": "https://example.com/pricing?utm_source=newsletter" } },
{ "key": "ga_session_id", "value": { "int_value": 1789466300 } },
{ "key": "engagement_time_msec", "value": { "int_value": 5400 } }
],
"device": { "category": "mobile" },
"geo": { "country": "United States" },
"session_traffic_source_last_click": { "...": "..." }
}
event_params is an array of key/value pairs, and each value sits in whichever typed slot applies: string_value, int_value, float_value or double_value. The page URL isn't a column. Neither is the session ID. To get either, you unnest the array and pick the key, which is the pattern Google uses in its own sample queries:[9]
SELECT
event_name,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page_location
FROM `your-project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260901' AND '20260907'
Two details in that WHERE clause matter. The events_* wildcard spans one table per day, and _TABLE_SUFFIX limits how many get scanned, which is most of what you pay for. The wildcard also matches events_intraday_*, whose suffix starts with intraday_, so a date-only range like this one leaves them out. Want today's partial data? Query the intraday table on purpose.
If you'd rather try this before linking your own property, Google publishes an obfuscated GA4 export from its merchandise store at bigquery-public-data.ga4_obfuscated_sample_ecommerce, covering November 2020 to January 2021.[10]
Connect BigQuery to Claude
This part has its own post: How to connect Claude to BigQuery covers Claude's native BigQuery connector, the redirect_uri_mismatch error you'll probably hit, and the cap Claude's connector puts on how many rows come back. You'll create an OAuth client in the Google Cloud Console, and the identity you connect with should only have BigQuery Data Viewer and Job User.
For ChatGPT it's the same idea through a custom MCP connector, which ChatGPT gates by plan, so check OpenAI's current plan requirements for custom connectors.
Before you ask anything, tell the model where the data is and what it looks like. A line in your project instructions saves a lot of trial and error: "GA4 export is in analytics_123456789, one table per day, parameters live in event_params and need UNNEST, always filter _TABLE_SUFFIX."
Questions worth asking
These are the ones the report API can't answer cleanly, and where the raw events pay off:
- Which landing pages bring sessions that end in a purchase, and which ones get traffic and leak it?
- How many days pass between someone's first visit and their first purchase, split by first traffic source?
- Of the users who viewed pricing last month, how many came back within a week?
- Which pages have the longest engagement time for visitors from paid search versus organic?
- Did the checkout funnel drop-off change after the release on the 10th?
For the first one, a model with the context above would write something like this:
WITH events AS (
SELECT
user_pseudo_id,
event_name,
(SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS session_id,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page_location
FROM `your-project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260801' AND '20260831'
),
sessions AS (
SELECT
user_pseudo_id,
session_id,
MAX(IF(event_name = 'session_start', page_location, NULL)) AS landing_page,
COUNTIF(event_name = 'purchase') > 0 AS purchased
FROM events
GROUP BY user_pseudo_id, session_id
)
SELECT
landing_page,
COUNT(*) AS sessions,
COUNTIF(purchased) AS purchasing_sessions,
ROUND(SAFE_DIVIDE(COUNTIF(purchased), COUNT(*)), 3) AS conversion_rate
FROM sessions
GROUP BY landing_page
ORDER BY sessions DESC
LIMIT 20;
A session in the export is user_pseudo_id plus ga_session_id, because the session ID alone isn't unique across users. That's the kind of detail you want to see the model get right, so read the SQL before you trust the number. Also expect landing_page to include query strings, which splits one page into many rows until you strip them.
Put your other data next to it
Once GA4 lives in BigQuery, it's one dataset among others, and the useful questions start crossing between them.
Some sources have native routes into BigQuery. Search Console has a bulk data export, and the BigQuery Data Transfer Service loads Google Ads. For Stripe, HubSpot, Salesforce, Shopify or Meta Ads, tools like Airbyte and Fivetran sync them into the same project on a schedule.
Then a question like "which acquisition channel brings customers with the highest 90-day revenue?" becomes a join between session_traffic_source_last_click in the GA4 export and your payments table. The hard part is the join key. GA4's user_id only exists if your site sets it at login, and user_pseudo_id is a cookie, so matching web sessions to paying customers depends on what your tracking sends. I'd check that before promising anyone a channel-to-revenue number.
When it's a team asking
Everything above works for one person with their own Claude. It gets harder once your marketing lead and a growth hire start asking about the same data.
Each of them sets up their own BigQuery connection, and each model decides for itself what "active user" means and which of GA4's traffic source fields is the real one. Two people ask about last month's active users and walk away with two different numbers.
That's the problem Contextflo handles. Connect BigQuery once through a read-only service account, write down definitions like "an active user is anyone with an engaged session in the period" and which tables they apply to, and everyone's Claude or ChatGPT uses the same ones from then on.
You choose which tables each person can access, and every question is logged with who asked and what it found, so a number that shows up in a deck or a Slack message is traceable back to how it was built.
If your GA4 export is already in BigQuery and your team keeps asking you for numbers from it, you can get started free with one user and one data source.
Either way, link the export today. The data you'll want next year is being collected right now, and GA4 won't hand it over later.
FAQ
How do I connect Google Analytics to Claude? There are two routes. Google publishes an official, read-only Google Analytics MCP server that runs on your machine and lets Claude run GA4 reports through the Data API. For raw event-level data, turn on the GA4 BigQuery export and connect Claude to BigQuery, so it can write SQL against every event.
Is there an official Google Analytics MCP server? Yes. Google publishes one at github.com/googleanalytics/google-analytics-mcp. It is labeled experimental, is read-only, runs locally with pipx, and exposes tools like run_report, run_funnel_report and run_realtime_report on top of the Analytics Admin and Data APIs.
Can ChatGPT analyze my GA4 data? Yes, through the same two routes. ChatGPT only adds remote MCP connectors, so Google's local GA server has to be hosted first. The BigQuery route works through a custom MCP connector pointed at BigQuery, which ChatGPT gates by plan.
Does the GA4 BigQuery export include historical data? No. The export starts from the day you link the property and does not backfill events collected before that. If you need history, the BigQuery Data Transfer Service GA4 connector can backfill aggregated report tables, but not raw events.
Is the GA4 BigQuery export free? The daily export costs nothing on standard properties, up to a daily event cap that Google sets and occasionally revises, so check their current BigQuery Export pricing before you plan around a number. Streaming has its own per-GB cost. BigQuery storage and queries are billed normally, and the free sandbox deletes tables after a fixed retention window, so check Google's current sandbox limits before relying on it for anything long-term.
Find out if Contextflo is the right fit for you.
See how teams use Contextflo
Related posts
Keep reading

Conversational analytics: 5 ways to set it up, compared
7 min read

How to connect Claude to BigQuery (and fix the errors you'll hit)
8 min read

How to connect Claude to Postgres (and keep it read-only)
7 min read

How to build a BI dashboard with Claude that your team can actually trust
8 min read

How to give Claude read-only access to your database (it isn't always the default)
7 min read

You connected your warehouse to Claude, now what?
5 min read

Why is the team missing sprint goals? How to analyze Jira data with AI
8 min read

Are my ads actually profitable? How to analyze Facebook and TikTok ads together with AI
7 min read

Still building the weekly KPI report by hand? How to automate it with Claude
7 min read




