The Complete Guide to Connecting GA4 to BigQuery (2026)

Listen to this article
0:00 / 17:36Connecting GA4 to BigQuery takes about ten minutes in the GA4 admin, requires no code, and is free to enable. You link a Google Cloud project, choose an export type (daily, streaming, or both), and GA4 begins writing raw, event-level data into a BigQuery dataset. From there you own every event — unsampled, unaggregated, and queryable with SQL. This guide walks through the exact 2026 setup, the pricing you should actually budget for, the schema you'll be querying, and the mistakes that quietly cost teams money or data.
If you'd rather not touch the Google Cloud console at all, our team handles the link, schema validation, and cost guardrails as part of a standard engagement — see our services. Otherwise, read on.
Why export GA4 to BigQuery at all
The GA4 interface is a reporting layer, not your data. It applies sampling on large or complex explorations, caps cardinality (the dreaded "(other)" row), and retains user- and event-level data for a maximum of 14 months. BigQuery removes all three limits at once:
- No sampling. Every event, every parameter, exactly as collected — including the events that GA4's standard reports quietly fold into "(other)."
- No retention ceiling. BigQuery keeps data as long as you pay to store it — years, not months. Your 2024 cohort is still queryable in 2027.
- Real SQL and joins. Blend GA4 events with CRM, ad-cost, and revenue tables to answer questions the GA4 UI simply cannot — like true customer lifetime value by first-touch channel, or blended CAC across platforms.
This is the single highest-leverage move for any team that has outgrown standard reports. It is also the foundation for custom attribution modeling, LTV analysis, churn prediction, and feeding warehouse-native BI tools (Looker, Metabase, dbt) from a source of truth you control rather than a sampled API.
Website / app
gtag or GTM fires events
GA4 property
collection + processing
BigQuery export
free link, raw events
analytics_<id> dataset
events_ + events_intraday_
SQL / dbt / BI
you own the numbers
Prerequisites
Before you start, make sure you have all four of these. Missing any one is the usual reason a link fails or silently exports nothing:
- Editor access on the GA4 property. You must be at least an Editor at the property level (Viewer and Analyst roles can't create the link).
- A Google Cloud project with billing enabled. The export writes into a project you own, and a billing account must be attached — even though enabling the link itself costs nothing. Free-tier projects without billing will error out.
- The BigQuery API enabled in that Cloud project (
APIs & Services → Enable APIs → BigQuery API). - A decided data location. The export region is set once, at creation, and cannot be changed later without deleting and recreating the link (which loses continuity). If you have EU data-residency requirements, choose
EUdeliberately from the start.
Step-by-step: linking GA4 to BigQuery
- In GA4, open Admin → Product Links → BigQuery Links (under the property, not the account).
- Click Link, then Choose a BigQuery project. Pick the Cloud project that has billing and the BigQuery API enabled.
- Select a data location (e.g.
US,EU, or a specific single region). Choose deliberately — this is permanent for the link. - Choose which data streams to include, and optionally toggle Include advertising identifiers for mobile app streams.
- Pick an export type:
- Daily — a settled batch table (
events_YYYYMMDD) written once per day. - Streaming — continuous export into an intraday table for near-real-time analysis.
- You can enable both, and many teams do.
- Daily — a settled batch table (
- Submit. The first daily export typically appears within roughly 24 hours; streaming data begins flowing within minutes.
Once live, GA4 creates a dataset named analytics_<property_id> in your Cloud project. Daily data lands in events_YYYYMMDD tables; streaming and same-day data land in events_intraday_YYYYMMDD. A word of warning that trips up many teams: the export is never retroactive. It only captures data collected after the link goes live. Enable it as early as possible, even if you won't query it for months.
Export types compared
| Export type | Latency | Table | Best for |
|---|---|---|---|
| Daily (batch) | Up to ~24 hours | events_YYYYMMDD | Stable, complete daily tables for reporting and models |
| Streaming | Minutes | events_intraday_YYYYMMDD | Real-time monitoring, alerting, fresh dashboards |
| Fresh daily | Same-day refresh | events_YYYYMMDD (same-day) | A settled daily table available earlier than the standard batch, where offered |
A practical pattern: enable daily as your source of truth and add streaming only where you have a genuine real-time need. Streaming inserts are billed by BigQuery, so turning it on "just in case" adds cost with no benefit.
One subtlety that catches people out: when both daily and streaming are on, the same-day intraday table and the eventual daily table can briefly overlap in coverage. Always query the settled events_YYYYMMDD table for reporting, and treat events_intraday_* as a live-but-provisional view that will be superseded. Don't UNION them blindly, or you'll double-count the current day.
Understanding the export limits
The export behaves differently depending on whether you run a standard (free) GA4 property or Analytics 360.
- Standard GA4 applies a limit to the daily (batch) export — historically on the order of 1 million events per day per property. If a property exceeds it, the daily export for that day can stop, and you'll see a notice in GA4. The streaming export is not subject to that daily cap, but it does incur BigQuery streaming-insert costs on every event.
- Analytics 360 raises the export ceiling dramatically (into the billions of events per day) and adds an SLA on the daily export. It is the right tier for high-traffic properties.
If you're anywhere near a million events a day on a free property, plan for this before you flip the switch — by enabling streaming (which isn't capped), splitting into multiple properties, or moving to 360. Google has adjusted these thresholds over time, so confirm the current published limit in the official documentation rather than trusting a number in a blog post — including this one.
What BigQuery actually costs in 2026
Enabling the GA4 link is free. You pay Google Cloud for what BigQuery does with the data: storage, queries, and streaming inserts (if you enable streaming). Pricing varies by region and edition and changes over time, so treat the figures below as approximate ranges and confirm against the current Google Cloud pricing page before you budget.
- Storage — roughly $0.02–$0.04 per GB per month for active storage, dropping by about half for long-term storage (partitions untouched for 90 days). The first ~10 GB per month is typically free.
- On-demand queries — on the order of $6–$7 per TiB scanned (US multi-region is around $6.25/TiB), with the first ~1 TiB per month typically free. Critically, you are billed for bytes scanned, not bytes returned — a
SELECT COUNT(*)that scans a year of raw events still bills for the full scan. - Streaming inserts — a small per-GB charge (roughly $0.01–$0.05 per GB) that only applies if you enable the streaming export.
For most small-to-mid sites, monthly BigQuery cost lands in the single-to-low-double-digit dollars — often within or near the free tiers. Cost problems come almost entirely from query patterns, not storage. A single careless dashboard that re-scans your full events_* history every fifteen minutes can turn a $3 bill into a $300 one.
If you'd rather trade per-query billing for a predictable monthly number, BigQuery also offers editions with capacity-based (slot) pricing, where you reserve compute for a flat rate. That only makes sense at meaningful scale; nearly everyone reading this should stay on on-demand and simply query well.
Keeping query cost under control
Because you pay per byte scanned, a handful of habits eliminate most surprise bills. The single biggest lever is partition pruning — restricting each query to the days it actually needs:
| Query habit | Roughly what it scans | Effect on cost |
|---|---|---|
FROM events_* with no date filter | Every event, every day, forever | Grows without bound |
FROM events_* + _TABLE_SUFFIX BETWEEN filter | Only the days in range | Bounded, predictable |
SELECT * | All columns incl. large nested arrays | Multiplies bytes scanned |
| Select only needed columns/fields | A fraction of the row | Often 5-10x cheaper |
Concretely:
- Always filter on the partition. GA4 tables are partitioned by date. Use
_TABLE_SUFFIX BETWEEN '20260101' AND '20260131'when queryingevents_*instead of scanning wholesale. - Never
SELECT *. Select only the columns and nested fields you need — theevent_paramsanduser_propertiesarrays are large and get scanned in full when you touch the whole row. - Materialize common rollups. Turn frequently-run reports into scheduled queries or materialized tables so dashboards don't re-scan raw events on every refresh.
- Set a billing budget and alert in Google Cloud, plus a per-query maximum-bytes-billed cap. It's the cheapest insurance you'll buy.
The schema, and how to actually query it
The thing that surprises most SQL-fluent people on day one: GA4's export is not flat. Each row is one event, but the interesting fields live inside a repeated event_params array of key/value structs. You can't just write WHERE page_location = '...'; you have to UNNEST the array to reach a parameter.
Here's the canonical pattern — pulling page_location and a numeric value out of event_params for a single day's page_view events:
SELECT
event_date,
(SELECT ep.value.string_value
FROM UNNEST(event_params) ep
WHERE ep.key = 'page_location') AS page_location,
(SELECT ep.value.int_value
FROM UNNEST(event_params) ep
WHERE ep.key = 'ga_session_id') AS session_id
FROM `your_project.analytics_XXXXXXXXX.events_20260115`
WHERE event_name = 'page_view';
Each parameter's value lives in exactly one of string_value, int_value, float_value, or double_value depending on its type — the others are NULL. That's the number-one gotcha: read a string param out of int_value and you'll get all NULLs and think the export is broken.
A first genuinely useful rollup — top events for a day, scanning almost nothing because it hits one partition:
SELECT
event_name,
COUNT(*) AS events
FROM `your_project.analytics_XXXXXXXXX.events_20260115`
GROUP BY event_name
ORDER BY events DESC;
Once you're comfortable, swap the single-day table for events_* with a _TABLE_SUFFIX BETWEEN filter to span a date range while keeping the scan bounded:
SELECT
event_date,
COUNTIF(event_name = 'purchase') AS purchases
FROM `your_project.analytics_XXXXXXXXX.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260131'
GROUP BY event_date
ORDER BY event_date;
Consent Mode and server-side tagging still matter
BigQuery receives whatever GA4 collects — so data quality upstream determines data quality downstream. Two 2026 realities to keep in mind:
- Consent Mode v2 is required for advertisers using Google's ad platforms to reach users in the EEA. When users decline consent, Google models conversions inside its own products (Ads, GA4 reporting surfaces), but the event-level rows exported to BigQuery reflect only what was actually collected under the granted consent state — modeled conversions are never injected into your export. If consent is denied and you're relying on cookieless pings, you may see far fewer (or differently shaped) rows than the GA4 UI implies. Plan your BigQuery analysis around real, consented events, and don't expect UI totals to reconcile to the penny.
- Server-side tagging (a server-side GTM container, typically on Cloud Run) doesn't change the BigQuery link — GA4 still exports the same way — but it improves the durability and quality of the data GA4 receives before it ever reaches the warehouse. It helps with ad-blocker resilience, first-party cookie lifetime, and payload control. The rule that follows is unavoidable: BigQuery can't recover events GA4 never recorded. If your client-side collection is lossy, fix it upstream first.
If you're unsure whether your collection is clean enough to trust in the warehouse, a free analytics audit will flag consent, tagging, and data-loss issues before you build models on top of them.
Ready to do this properly
The link takes ten minutes. Building a warehouse you can actually trust — validated schema, cost guardrails, clean collection, and models on top — is the real work, and it's what we do every day. If you want the GA4 → BigQuery foundation set up right the first time, book a call or review transparent pricing to see what an engagement looks like. New clients start with a structured onboarding that gets your export, budgets, and first dashboards live in the first week — and a free audit is always the fastest way to find out whether the data you're about to warehouse is worth warehousing.
FAQ
Is the GA4 to BigQuery export free?
Enabling the link in GA4 is free — Google Analytics charges nothing to turn it on. You pay Google Cloud only for BigQuery storage, queries, and (if enabled) streaming inserts. For most small-to-mid properties, that usage often falls within or near BigQuery's monthly free tiers (roughly the first 10 GB of storage and 1 TiB of query scanning per month), so real-world bills are frequently in the single-digit dollars.
How long does it take for data to appear in BigQuery?
Streaming export data appears within minutes of enabling it. The daily (batch) export typically produces its first events_YYYYMMDD table within about 24 hours. Remember that the export is not retroactive — it only includes data collected after the link is active — so enable it as early as possible even if you won't query it right away.
What's the difference between daily and streaming export?
Daily export writes one settled batch table per day (events_YYYYMMDD) and is ideal as a source of truth for reporting and models. Streaming export continuously writes to an intraday table (events_intraday_YYYYMMDD) for near-real-time analysis but incurs BigQuery streaming-insert costs. Many teams enable daily as the baseline and add streaming only where real-time data is genuinely needed, and query the settled daily table for anything that must be exact.
Will I hit the export limit on a free GA4 property?
Possibly, if you're high-traffic. Standard GA4 has historically limited the daily batch export to around 1 million events per day per property; exceeding it can pause that day's export. The streaming export isn't bound by that daily cap. Very high-volume sites should evaluate Analytics 360, which raises the ceiling into the billions of events per day. Because Google has changed these thresholds over time, confirm the current published limit in the official documentation.
Can I export historical GA4 data to BigQuery?
No. The BigQuery export only captures data from the moment the link is enabled forward — it is never backfilled. This is the single most common regret we hear, so enable the export as early as possible, even on properties you don't plan to query for months. There is no supported way to reach back and fill in the gap after the fact.
Why can't I query GA4 parameters like normal columns?
Because the export is nested, not flat. Fields like page path, session ID, and custom parameters live inside a repeated event_params array of key/value structs rather than as top-level columns. To read one, you UNNEST(event_params) and filter on key, then pull the value from the correct typed field (string_value, int_value, float_value, or double_value) — only one of which is populated per parameter. Reading a string from int_value returns all NULLs, which is the most common "the export is broken" false alarm.
Layer Analytics
Senior GA4, Google Tag Manager, and BigQuery practitioners. We build measurement that marketing teams can actually trust. See what we do →