Skip to content
Can Elmas

Attribution · 8 min read

GA4 BigQuery Export: Do You Need It and What Can You Do With It?

TL;DR

Link the GA4 BigQuery export early, even before anyone queries it: it's free to set up, cheap to store and never backfills. It becomes worth building on when thresholds, sampling or 14-month retention block real questions, or when you need to join web behavior with CRM revenue and ad spend. Without someone writing SQL, it's just storage.

· Published · Updated

Yes, link the export now: it’s free to set up, it hands you every raw event, and it only collects data from the day you switch it on. Whether you build anything on top depends on two things: questions the GA4 interface can’t answer, such as revenue by first-touch channel or closed deals by landing page, and someone who will actually write the queries.

What the GA4 interface cannot do

GA4’s reports are built for browsing, not for precise business questions:

LimitIn the GA4 interfaceIn the BigQuery export
Data thresholdsRows are withheld when user counts are low and Google signals or demographics are involvedNo thresholds; every collected event is there
SamplingExplorations over large date ranges can return sampled resultsQueries run on the full dataset
High cardinalityLong-tail values collapse into an “(other)” rowEvery value is kept
RetentionExplorations on standard properties reach back 2 or 14 months, depending on your settingData stays as long as you keep the tables
Custom dimensionsParameters must be registered, within a capped quota, before you can report on themEvery event parameter is exported, registered or not
JoinsData import covers fixed cases like cost data; no free-form joins with CRM deals, margins or order historyJoin anything else you load into BigQuery

The last two rows matter most. The interface shows how many people started checkout, not which first-touch channel produces customers who are still buying a year later, or which landing pages produce opportunities that actually close.

What the BigQuery export includes and what it costs

The export writes one row per event into daily tables named events_YYYYMMDD. Each row carries the event name and timestamp, a nested array of event parameters, user properties, device and geography fields, the pseudonymous browser ID (user_pseudo_id), your own user_id if you set one, traffic source fields, and ecommerce and item details for commerce events. With streaming turned on, the current day also lands in events_intraday_ tables within minutes.

What it does not include:

  • Modeled data. Consent mode modeling and other estimates the interface fills in are not in the export.
  • Google signals and demographic data. Those stay in the interface.
  • Anything before the link date. There is no backfill. Every day you wait is raw data you can never recover.
  • Ad cost. Spend from Google Ads or any other platform has to be loaded separately.

These gaps are part of why BigQuery totals won’t match the interface exactly, much like GA4 and your ad platforms never match. Decide in writing which source each report uses.

Cost

The link and the daily export are free on standard properties. You pay Google Cloud for BigQuery:

  • Storage, billed per GB per month. For most small and mid-sized properties, event data is a minor line item.
  • Queries, billed by data scanned under on-demand pricing. This is the cost that surprises people.
  • Streaming export, if enabled, which adds a per-GB charge.

BigQuery’s free tier covers the first 10 GB of storage and 1 TB of query processing each month, enough for many companies to pay nothing at the start. Standard properties also have a daily export cap of 1 million events. If a property regularly exceeds it, Google can pause the daily export, so exclude noisy events or move to streaming or GA4 360.

Cost blowups follow a pattern: a dashboard selecting every column across all history, refreshed hourly. BigQuery charges for the columns and tables a query touches, so date filters and narrow column lists keep bills small.

Setting up the export correctly from day one

Most problems I find in existing exports were baked in at setup. Get these right when you link it:

  • Create the Google Cloud project under a company-owned account, not an agency’s or a freelancer’s
  • Attach a billing account; sandbox tables expire after 60 days, which quietly deletes your history
  • Choose the dataset location on purpose, because it can’t easily be changed later
  • Turn on the daily export; add streaming only if someone needs same-day data
  • Exclude high-volume events nobody will analyze if you’re approaching the daily cap
  • Set budget alerts in Google Cloud and custom query quotas per user
  • Grant access through groups, and give most people modeled tables rather than the raw export
  • Set user_id on signup or login to an internal ID, never an email address or other personal data
  • Clean up event names and parameters with a tracking plan so you aren’t exporting a mess
  • Make sure every purchase event carries a unique transaction_id so orders can be joined to your backend

Useful queries: user journeys, LTV by source and funnel drop-off

Two things trip up everyone at first. Event parameters are nested, so you pull a value with a subquery over UNNEST(event_params). And there is no session table: a session is user_pseudo_id plus the ga_session_id parameter. Filter dates with _TABLE_SUFFIX so a query scans only the days you need.

Funnel drop-off by session

This counts sessions reaching each step over the last 30 days. It isn’t strictly sequential, which is usually fine for spotting the biggest leak.

WITH e AS (
  SELECT
    CONCAT(user_pseudo_id, '-', CAST((SELECT value.int_value
      FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS STRING)) AS session_key,
    event_name
  FROM `your_project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN
      FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY))
      AND FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY))
    AND event_name IN ('session_start', 'view_item', 'add_to_cart', 'begin_checkout', 'purchase')
)
SELECT
  COUNT(DISTINCT IF(event_name = 'session_start', session_key, NULL)) AS sessions,
  COUNT(DISTINCT IF(event_name = 'view_item', session_key, NULL)) AS viewed_item,
  COUNT(DISTINCT IF(event_name = 'add_to_cart', session_key, NULL)) AS added_to_cart,
  COUNT(DISTINCT IF(event_name = 'begin_checkout', session_key, NULL)) AS began_checkout,
  COUNT(DISTINCT IF(event_name = 'purchase', session_key, NULL)) AS purchased
FROM e

Divide each column by the one before it. Start with the largest drop, then split the same query by device or landing page to see where it concentrates.

LTV by first-touch source

The traffic_source fields hold the first source that acquired the user, not the source of each session. That makes them the right fields for lifetime value by acquisition channel:

SELECT
  traffic_source.source,
  traffic_source.medium,
  COUNT(DISTINCT user_pseudo_id) AS purchasers,
  COUNT(DISTINCT ecommerce.transaction_id) AS orders,
  ROUND(SAFE_DIVIDE(SUM(ecommerce.purchase_revenue), COUNT(DISTINCT user_pseudo_id)), 2) AS revenue_per_purchaser
FROM `your_project.analytics_123456789.events_*`
WHERE event_name = 'purchase'
GROUP BY 1, 2
HAVING purchasers >= 50
ORDER BY revenue_per_purchaser DESC

Two caveats: user_pseudo_id is per browser, so a customer who buys again on another device looks like a new user unless you set user_id. And the purchase event records revenue at checkout, before refunds or costs, so join to backend orders by transaction_id when you need net revenue.

User journeys

For paths, take page_view events, pull the page_location parameter, and build an ordered string per session with STRING_AGG(page, ' > ' ORDER BY event_timestamp). Count the most common paths in converting and non-converting sessions separately. Where they diverge tells you more than the interface’s path reports, because you define “converting”: a closed deal, not just a form fill.

Joining GA4 data with CRM and ad spend

This is where the export earns its keep. You need a key shared between systems:

  • user_id: set it to the CRM contact or account ID at signup or login. It then appears on that person’s events in the export.
  • Client ID in forms: read the client ID (the last two number blocks of the _ga cookie) into a hidden form field and store it on the CRM record. It matches user_pseudo_id in the export.
  • transaction_id: joins GA4 purchases to orders in Shopify or your backend, with refunds, margin and repeat orders attached.

For spend, the BigQuery Data Transfer Service loads Google Ads data natively. Meta, LinkedIn, TikTok and most CRMs need a connector tool. Join spend to GA4 sessions on date plus campaign name, which only works if your UTM naming is consistent.

Once the joins work, you can report what the interface never will: cost per qualified opportunity by channel, closed-won revenue by first landing page, or 180-day revenue per customer by acquisition campaign next to what that campaign cost.

Expect match rates below 100%. Visitors who decline consent, block scripts or switch devices won’t link cleanly. Show the match rate next to every joined metric so nobody over-reads the result.

Building these joins and the reports on top of them is a large part of my marketing attribution work.

When BigQuery is overkill

Linking the export is cheap insurance. Building models and dashboards around it is not, and it’s often the wrong use of time.

SituationRecommendation
Low traffic, one or two channels, no revenue in a CRMLink the export, build nothing yet
Your questions fit in explorations and 14 months of historyStay in the interface
Nobody on the team or on retainer writes SQLLink it, but don’t buy dashboards that depend on it
Paid spend across several platforms plus revenue in a CRMWorth building
Long B2B sales cycles where leads and revenue are months apartWorth building
Ecommerce with repeat buyers and cohort LTV questionsWorth building, joined to order data

The honest test: name the first three questions you’d answer and who will write the queries. If you can’t, the export just accumulates tables nobody reads.

Tools that sit on top of the export

A practical stack has three layers:

  1. Modeling. Scheduled queries, Dataform (built into BigQuery) or dbt turn raw events into clean session, user and conversion tables. Open-source packages can model the GA4 export into sessions for you.
  2. Loading other data. The Data Transfer Service for Google Ads, Search Console’s bulk export to BigQuery, and connector tools such as Fivetran, Airbyte or Supermetrics for other ad platforms and CRMs.
  3. Reporting. Looker Studio connects to BigQuery natively, and Looker, Power BI, Tableau and Metabase all work too. Point them at modeled tables, never at the raw events_* tables, which keeps dashboards fast and query costs predictable.

Get it built

If your GA4 data is stuck behind thresholds, or you can’t connect web behavior to revenue, I can set up the export, model the tables and build reports your team will actually open. Start with a Growth Audit, $1,500 fixed and credited if we continue. See pricing or get in touch.

FAQ

Frequently Asked Questions

Is the GA4 BigQuery export free?

Linking the property and running the daily export are free on standard GA4 properties. You pay Google Cloud for BigQuery storage and queries beyond the monthly free tier, plus a per-GB charge if you turn on streaming export.

Can I get historical GA4 data into BigQuery?

No. The export starts on the day you link the property and does not backfill earlier data. Tools that pull from the GA4 Data API can load aggregated reports for past dates, but not raw events.

Why don't my BigQuery numbers match the GA4 interface?

The interface adds modeled data, applies thresholds and estimates some user counts, while the export holds only the raw events that were collected. Expect small gaps in users and sessions, and decide in writing which source each report uses.

Do I need to know SQL to use the GA4 BigQuery export?

Someone on the team or on retainer does. Looker Studio or another BI tool can sit on top, but the clean tables it reads have to be built with SQL first, usually through scheduled queries, Dataform or dbt.

Work with me

Let’s find your biggest growth lever

Tell me about your growth challenge. I’ll tell you honestly if I can help — and if I can’t, who can.

  • ✓ No obligation
  • ✓ No sales script
  • ✓ Honest feedback
  • ✓ Clear next steps