Skip to content

Schema Reference

The GA4 BigQuery export schema has a fixed outer structure with several nested fields. The key challenge is that event parameters and user properties are stored as arrays of structs rather than flat columns — every parameter lookup requires an UNNEST operation.

Each daily table (events_YYYYMMDD) contains one row per event. The top-level columns are:

events_YYYYMMDD
├── event_date STRING
├── event_timestamp INT64 (microseconds since epoch)
├── event_name STRING
├── event_params ARRAY<STRUCT<key, value>>
├── event_previous_timestamp INT64
├── event_value_in_usd FLOAT64
├── event_bundle_sequence_id INT64
├── event_server_timestamp_offset INT64
├── user_id STRING
├── user_pseudo_id STRING
├── privacy_info STRUCT
├── user_properties ARRAY<STRUCT<key, value>>
├── user_first_touch_timestamp INT64
├── user_ltv STRUCT<revenue, currency>
├── device STRUCT
├── geo STRUCT
├── app_info STRUCT
├── traffic_source STRUCT (first-touch)
├── collected_traffic_source STRUCT (session-level)
├── session_traffic_source_last_click STRUCT (last-click attribution)
├── stream_id STRING
├── platform STRING
├── event_dimensions STRUCT
├── ecommerce STRUCT
├── items ARRAY<STRUCT>
├── is_active_user BOOLEAN
├── batch_event_index INT64
├── batch_page_id INT64
└── batch_ordering_id INT64
FieldTypeDescription
event_dateSTRINGDate in YYYYMMDD format
event_timestampINTEGERMicroseconds since Unix epoch
event_nameSTRINGEvent name (e.g., page_view, purchase)
event_paramsRECORD (REPEATED)Event parameters array
event_previous_timestampINTEGERPrevious event timestamp for this user
event_value_in_usdFLOATEvent value converted to USD
event_bundle_sequence_idINTEGERBundle sequence ID
event_server_timestamp_offsetINTEGERServer timestamp offset in microseconds
user_idSTRINGUser ID (only if set in implementation)
user_pseudo_idSTRINGClient ID / anonymous device identifier
privacy_infoRECORDConsent mode privacy settings
user_propertiesRECORD (REPEATED)User properties array
user_first_touch_timestampINTEGERFirst touch timestamp in microseconds
user_ltvRECORDLifetime value information
deviceRECORDDevice information
geoRECORDGeographic information
app_infoRECORDApp information (mobile streams only)
traffic_sourceRECORDFirst-touch traffic source (user-level)
collected_traffic_sourceRECORDSession-level traffic source with UTM and click IDs
session_traffic_source_last_clickRECORDLast-click attribution data
stream_idSTRINGData stream ID
platformSTRINGWEB, IOS, or ANDROID
event_dimensionsRECORDEvent dimensions
ecommerceRECORDEcommerce transaction data
itemsRECORD (REPEATED)Ecommerce items array
is_active_userBOOLEANWhether the user was active in the reporting period
batch_event_indexINTEGERIndex of this event within the batch
batch_page_idINTEGERPage ID within the batch
batch_ordering_idINTEGEROrdering ID within the batch

Type: STRING Format: YYYYMMDD (e.g., 20240115) Use: Date filtering. Note this is a string, not a DATE type. Convert with PARSE_DATE('%Y%m%d', event_date).

-- Filter by event_date (uses _TABLE_SUFFIX for partition pruning, also filter by event_date for intraday tables)
WHERE _TABLE_SUFFIX = '20240115'
-- Or convert for date arithmetic:
WHERE PARSE_DATE('%Y%m%d', event_date) >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)

Type: INT64 Unit: Microseconds since Unix epoch Use: Time-of-day analysis, session reconstruction, ordering events within a session.

-- Convert to readable timestamp
TIMESTAMP_MICROS(event_timestamp) AS event_time
-- Get hour of day
EXTRACT(HOUR FROM TIMESTAMP_MICROS(event_timestamp) AT TIME ZONE 'America/New_York') AS hour_of_day

Type: STRING Use: Filter to specific events. Always pair with _TABLE_SUFFIX filter.

WHERE event_name = 'purchase'
WHERE event_name IN ('add_to_cart', 'begin_checkout', 'purchase')
WHERE event_name LIKE 'video_%'

Type: STRING Nullable: Yes — only populated if you set User ID in your implementation Use: Join with your CRM or first-party data. This is the value you set with gtag('config', ..., { user_id: '...' }).

Type: STRING Use: The anonymous user identifier. Equivalent to the client_id on web. Use this as the primary user identifier when User ID is not available.

Note: This value resets when a user clears cookies. It is not a persistent identifier across browsers or devices.

event_params is a repeated record with the following exact structure:

event_params ARRAY<STRUCT<
key STRING,
value STRUCT<
string_value STRING,
int_value INT64,
float_value FLOAT64,
double_value FLOAT64
>
>>

Each parameter is stored as one row in the array, with the value in one of four sub-fields depending on its type. Only one value field is populated per parameter — the others are NULL. To extract a parameter, UNNEST the array and filter by key:

-- Extract string parameter
(SELECT value.string_value
FROM UNNEST(event_params)
WHERE key = 'page_title') AS page_title
-- Extract integer parameter
(SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id') AS session_id
-- Extract float or double (use COALESCE for parameters that might be either)
(SELECT COALESCE(value.float_value, value.double_value)
FROM UNNEST(event_params)
WHERE key = 'value') AS transaction_value
-- Safe extraction (returns NULL if param not present)
(SELECT value.string_value
FROM UNNEST(event_params)
WHERE key = 'search_term') AS search_term
KeyTypeDescription
ga_session_idint_valueSession identifier
ga_session_numberint_valuenth session for this user
session_engagedstring_value’1’ if session was engaged
page_locationstring_valueFull page URL
page_titlestring_valuePage title
page_referrerstring_valueReferring URL
percent_scrolledint_valueScroll depth (90 for Enhanced Measurement scroll)
link_urlstring_valueOutbound click destination URL
search_termstring_valueSite search query
file_namestring_valueDownloaded file name
video_titlestring_valueYouTube video title
video_percentint_valueVideo progress percentage
engagement_time_msecint_valueEngagement time in milliseconds
transaction_idstring_valuePurchase transaction ID
valuefloat/double_valuePurchase or event value
currencystring_valueISO 4217 currency code
couponstring_valueCoupon code applied
affiliationstring_valueStore or affiliate name

Similar to event_params but with an additional set_timestamp_micros field. Contains user properties set with gtag('set', 'user_properties', {...}).

user_properties ARRAY<STRUCT<
key STRING,
value STRUCT<
string_value STRING,
int_value INT64,
float_value FLOAT64,
double_value FLOAT64,
set_timestamp_micros INT64
>
>>

The set_timestamp_micros field records when the user property was last set for this user. This is unique to user_propertiesevent_params does not have it.

-- Extract user property value
(SELECT value.string_value
FROM UNNEST(user_properties)
WHERE key = 'subscription_tier') AS subscription_tier
-- Extract when the property was last set
(SELECT TIMESTAMP_MICROS(value.set_timestamp_micros)
FROM UNNEST(user_properties)
WHERE key = 'subscription_tier') AS tier_set_at
-- Extract with both value and timestamp
SELECT
user_pseudo_id,
up.key AS property_name,
COALESCE(up.value.string_value, CAST(up.value.int_value AS STRING)) AS property_value,
TIMESTAMP_MICROS(up.value.set_timestamp_micros) AS set_time
FROM `project.analytics_PROPERTY_ID.events_*`,
UNNEST(user_properties) AS up
WHERE _TABLE_SUFFIX = '20240115'
  • Maximum 25 custom user properties per GA4 property
  • Property names: up to 24 characters
  • Property values: up to 36 characters
FieldTypeDescription
device.categorySTRINGmobile, desktop, or tablet
device.mobile_brand_nameSTRINGe.g., Apple, Samsung
device.mobile_model_nameSTRINGe.g., iPhone 14, Galaxy S23
device.mobile_marketing_nameSTRINGMarketing name of the device
device.mobile_os_hardware_modelSTRINGOS-reported hardware model
device.operating_systemSTRINGiOS, Android, Windows, Macintosh
device.operating_system_versionSTRINGOS version string
device.vendor_idSTRINGiOS vendor ID (IDFV)
device.advertising_idSTRINGGAID (Android) or IDFA (iOS), if enabled
device.languageSTRINGDevice language (e.g., en-us)
device.is_limited_ad_trackingSTRINGWhether ad tracking is limited
device.time_zone_offset_secondsINTEGEROffset from UTC in seconds
device.browserSTRINGChrome, Safari, Firefox, etc.
device.browser_versionSTRINGBrowser version string
device.web_info.browserSTRINGBrowser (web-specific)
device.web_info.browser_versionSTRINGBrowser version (web-specific)
device.web_info.hostnameSTRINGHostname of the page
SELECT
device.category,
device.operating_system,
device.browser,
device.web_info.hostname,
COUNT(*) AS sessions
FROM `project.analytics_PROPERTY_ID.events_*`
WHERE event_name = 'session_start'
AND _TABLE_SUFFIX BETWEEN '20240101' AND '20240131'
GROUP BY 1, 2, 3, 4
ORDER BY sessions DESC
FieldTypeDescription
geo.continentSTRINGAmericas, Europe, Asia, etc.
geo.countrySTRINGCountry name
geo.regionSTRINGState or province name
geo.citySTRINGCity name
geo.sub_continentSTRINGSub-continent region
geo.metroSTRINGDesignated Market Area (US only)

Contains the original traffic source for the user — the source from their very first session. This is user-level, not session-level.

FieldTypeDescription
traffic_source.nameSTRINGCampaign name
traffic_source.mediumSTRINGMedium (e.g., organic, cpc)
traffic_source.sourceSTRINGSource (e.g., google, facebook)

collected_traffic_source record (session-level)

Section titled “collected_traffic_source record (session-level)”

Contains session-level traffic source information, including UTM parameters and Google click IDs. This is the record to use for session-level attribution.

FieldTypeDescription
collected_traffic_source.manual_campaign_idSTRINGUTM campaign ID (utm_id)
collected_traffic_source.manual_campaign_nameSTRINGUTM campaign name (utm_campaign)
collected_traffic_source.manual_sourceSTRINGUTM source (utm_source)
collected_traffic_source.manual_mediumSTRINGUTM medium (utm_medium)
collected_traffic_source.manual_termSTRINGUTM term (utm_term)
collected_traffic_source.manual_contentSTRINGUTM content (utm_content)
collected_traffic_source.gclidSTRINGGoogle Ads click ID
collected_traffic_source.dclidSTRINGDisplay & Video 360 click ID
collected_traffic_source.srsltidSTRINGGoogle Merchant Center click ID
-- Session-level traffic source attribution
SELECT
collected_traffic_source.manual_source AS source,
collected_traffic_source.manual_medium AS medium,
collected_traffic_source.manual_campaign_name AS campaign,
collected_traffic_source.gclid,
COUNT(DISTINCT
CONCAT(user_pseudo_id, '-',
CAST((SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS STRING)
)
) AS sessions
FROM `project.analytics_PROPERTY_ID.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20240101' AND '20240131'
AND (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'entrances') = 1
GROUP BY 1, 2, 3, 4
ORDER BY sessions DESC

Contains last-click attribution data for the session. This is a newer field that provides attribution information aligned with Google Ads reporting.

Populated for events that use GA4’s ecommerce data model (purchase, add_to_cart, begin_checkout, etc.):

FieldTypeDescription
ecommerce.total_item_quantityINTEGERTotal quantity of all items
ecommerce.purchase_revenue_in_usdFLOATRevenue converted to USD
ecommerce.purchase_revenueFLOATRevenue in property currency
ecommerce.refund_value_in_usdFLOATRefund amount in USD
ecommerce.refund_valueFLOATRefund amount in property currency
ecommerce.shipping_value_in_usdFLOATShipping cost in USD
ecommerce.shipping_valueFLOATShipping cost in property currency
ecommerce.tax_value_in_usdFLOATTax amount in USD
ecommerce.tax_valueFLOATTax amount in property currency
ecommerce.unique_itemsINTEGERNumber of distinct items
ecommerce.transaction_idSTRINGTransaction/order ID

The items array contains one element per product in the event. Each item is a STRUCT with the following fields:

FieldTypeDescription
items.item_idSTRINGProduct SKU or ID
items.item_nameSTRINGProduct name
items.item_brandSTRINGProduct brand
items.item_variantSTRINGProduct variant (e.g., size, color)
items.item_categorySTRINGPrimary category
items.item_category2STRINGCategory level 2
items.item_category3STRINGCategory level 3
items.item_category4STRINGCategory level 4
items.item_category5STRINGCategory level 5
items.price_in_usdFLOATUnit price in USD
items.priceFLOATUnit price in local currency
items.quantityINTEGERQuantity
items.item_revenue_in_usdFLOATItem revenue in USD
items.item_revenueFLOATItem revenue in local currency
items.item_refund_in_usdFLOATItem refund amount in USD
items.item_refundFLOATItem refund amount in local currency
items.couponSTRINGItem-level coupon code
items.affiliationSTRINGStore or affiliate name
items.location_idSTRINGPhysical location ID
items.item_list_idSTRINGID of the list the item was in
items.item_list_nameSTRINGName of the list the item was in
items.item_list_indexSTRINGPosition in the list
items.promotion_idSTRINGPromotion ID
items.promotion_nameSTRINGPromotion name
items.creative_nameSTRINGCreative associated with the promotion
items.creative_slotSTRINGCreative slot name
-- Unnest items for product-level analysis
SELECT
event_date,
item.item_id,
item.item_name,
item.item_brand,
item.item_category,
item.item_category2,
item.price,
item.quantity,
item.item_revenue,
item.coupon,
item.promotion_name
FROM `project.analytics_PROPERTY_ID.events_*`,
UNNEST(items) AS item
WHERE event_name = 'purchase'
AND _TABLE_SUFFIX BETWEEN '20240101' AND '20240131'

Populated for app streams:

app_info.id -- Bundle ID / package name
app_info.version -- App version string
app_info.install_store -- 'google' or 'apple'
app_info.firebase_app_id -- Firebase app ID
app_info.install_source -- Install source
privacy_info.ads_storage -- 'Yes', 'No', 'Unset'
privacy_info.analytics_storage -- 'Yes', 'No', 'Unset'
privacy_info.uses_transient_token -- 'Yes', 'No'

analytics_storage = 'No' means the event was collected with consent mode analytics denied — the data is modeled/estimated, not from a real user session.

user_ltv.revenue -- Cumulative revenue from this user since first visit
user_ltv.currency -- Currency of the revenue

In addition to the daily events_* tables, the BigQuery export includes user-level tables:

  • users_* — Contains data for users who have a user_id set
  • pseudonymous_users_* — Contains data for all users (identified by user_pseudo_id)

These tables contain aggregated user data:

FieldDescription
user_idUser ID (if set)
user_pseudo_idDevice/Client ID
user_infoUser-level aggregates (first touch, last active, first purchase)
audiencesAudience memberships
user_propertiesAll user properties
deviceDevice info
geoLocation info
user_ltvLifetime value (revenue, currency)
predictionsML predictions (purchase probability, churn probability)
-- Query the users table for lifetime value
SELECT
user_pseudo_id,
user_info.last_active_timestamp_micros,
user_info.user_first_touch_timestamp_micros,
user_info.first_purchase_date,
user_ltv.revenue_in_usd AS lifetime_revenue
FROM `project.analytics_PROPERTY_ID.users_*`
WHERE _TABLE_SUFFIX = '20240115'
SELECT *
FROM `project.analytics_PROPERTY_ID.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20240101' AND '20240131'
LIMIT 1000
SELECT
event_name,
COUNT(*) AS count
FROM `project.analytics_PROPERTY_ID.events_20240115`
GROUP BY event_name
ORDER BY count DESC
SELECT
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page_url,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_title') AS page_title,
COUNT(*) AS views
FROM `project.analytics_PROPERTY_ID.events_*`
WHERE event_name = 'page_view'
AND _TABLE_SUFFIX BETWEEN '20240101' AND '20240131'
GROUP BY 1, 2
ORDER BY views DESC
LIMIT 50