BigQuery is the escape hatch from every GA4 limit and the way to join analytics with your own data.
Admin > Product links > BigQuery links connects the property to a Google Cloud project. Pick a dataset location, the data streams to include, and the export types:
- Daily: a table
events_YYYYMMDDper day, finalised a few hours after midnight in the property's time zone (and occasionally rewritten within 72 hours). Free properties get up to 1 million events per day. - Streaming:
events_intraday_YYYYMMDD, updated within minutes, deleted once the daily table lands. No event cap, but Cloud charges apply. - User data export:
pseudonymous_users_andusers_tables with per-user attributes, audiences and lifetime metrics.
The export starts from link creation; nothing earlier is backfilled. BigQuery's own costs are modest for most sites: storage is cheap and queries are billed per byte scanned, so always filter on the table suffix (_TABLE_SUFFIX BETWEEN '20260901' AND '20260930').
The schema is one row per event with nested, repeated fields: event_date, event_timestamp (microseconds), event_name, event_params (an array of {key, value: {string_value, int_value, float_value, double_value}}), user_pseudo_id, user_id, user_properties (same shape), device, geo, traffic_source (first-touch), session_traffic_source_last_click, ecommerce and items (array). You read a parameter with UNNEST and a subquery, as in the example.
Why it matters: BigQuery data is unsampled, unthresholded, unaffected by retention (you keep it as long as you pay for storage), carries every parameter whether or not a custom dimension exists, and can be joined to CRM, cost or product tables. Sessions, engagement rate and attribution are not precomputed; you rebuild them from ga_session_id and engagement_time_msec, and numbers will differ slightly from the UI, which applies identity stitching and modelling you do not get here.
Connect Looker Studio or any BI tool to the resulting tables for dashboards that never sample.
Example
-- Sessions and page views per landing page, September 2026
SELECT
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page_location,
COUNT(DISTINCT CONCAT(user_pseudo_id, '-',
(SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id'))) AS sessions,
COUNTIF(event_name = 'page_view') AS page_views,
COUNT(DISTINCT user_pseudo_id) AS users
FROM `my-project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260901' AND '20260930'
AND event_name IN ('page_view', 'session_start')
GROUP BY page_location
ORDER BY sessions DESC
LIMIT 20;Remember
- Daily and streaming event tables, plus user tables; starts at link time
- Nested schema: UNNEST event_params to read a parameter
- Unsampled, unthresholded, unlimited retention; sessions must be rebuilt by hand