Google Analytics 4: Event-Based Tracking with gtag.js | OpsBlu Docs

Google Analytics 4: Event-Based Tracking with gtag.js

Implement GA4 event-based analytics with gtag.js configuration, Measurement Protocol, Enhanced Measurement, BigQuery export, consent mode, and debug...

How GA4 Works

Google Analytics 4 uses an event-based data model. Every interaction is an event: a pageview, a scroll, a click, a purchase. There are no sessions or pageviews as distinct hit types. Sessions are calculated after data collection by grouping events that occur within a configurable timeout window (default 30 minutes of inactivity).

Each event consists of an event name and up to 25 key-value parameters. For example, a page_view event carries parameters like page_location, page_referrer, and page_title. A purchase event carries transaction_id, value, currency, and an items array. This flat event+parameter model replaces the Universal Analytics hierarchy of hits, sessions, and users.

GA4 collects data through data streams. A data stream is a flow of events from a single platform: a website, an iOS app, or an Android app. Each stream gets a unique Measurement ID (for web, formatted as G-XXXXXXXXXX) or a Firebase App ID (for mobile). A single GA4 property can have multiple data streams, which is how cross-platform measurement works. Events from all streams land in the same property, and GA4 uses device IDs, Google Signals, or User-ID to stitch users across platforms.

On the web, data collection works through the gtag.js library. When gtag.js loads, it sets a first-party cookie (_ga) containing a randomly generated client ID. Every event sent to GA4 includes this client ID. The library batches events and sends them as HTTP POST requests to https://www.google-analytics.com/g/collect. The payload is a URL-encoded string of event parameters, not JSON.

GA4 processes incoming events through a pipeline that applies filters, modifications, and channel groupings before writing to storage. Processed data appears in the GA4 interface with a latency of 24-48 hours for standard reports. Real-time reports show unprocessed data within seconds but with limited dimensions.

For deeper analysis, GA4 offers a free BigQuery export that writes raw event-level data daily (or streaming, with GA4 360). Each row in BigQuery represents a single event with all its parameters, user properties, and device/geo metadata. This is the only way to access raw, unaggregated GA4 data.

Installing the Tracking Script

Add the Global Site Tag (gtag.js) in the <head> of every page. Replace G-XXXXXXXXXX with your Measurement ID from the GA4 Admin panel under Data Streams.

<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
</script>

The gtag('config', ...) call initializes the measurement ID and sends an automatic page_view event. You can pass configuration parameters to modify default behavior:

gtag('config', 'G-XXXXXXXXXX', {
  send_page_view: false,           // Disable automatic page_view events
  cookie_domain: 'auto',           // Cookie domain (auto-detects)
  cookie_expires: 63072000,        // Cookie expiration in seconds (2 years default)
  cookie_prefix: 'mysite',         // Prefix for cookie names
  cookie_flags: 'SameSite=None;Secure',  // Cookie flags for cross-site
  page_location: window.location.href,
  page_title: document.title
});

Installing via Google Tag Manager

In GTM, create a Google Analytics: GA4 Configuration tag. Set the Measurement ID and configure it to fire on All Pages. GA4 event tags then reference this configuration tag. No gtag.js snippet is needed when using GTM because the GTM container script handles loading the GA4 library.

Verifying Installation

Open Chrome DevTools, go to Network, and filter for collect. You should see POST requests to google-analytics.com/g/collect on each page load. The request payload contains the encoded event data. Alternatively, enable GA4 DebugView by installing the Google Analytics Debugger Chrome extension, which sets debug_mode: true on all events. Events will then appear in real time under Admin > DebugView in the GA4 interface.

Event Tracking and Data Collection

Enhanced Measurement

GA4 automatically tracks certain events when Enhanced Measurement is enabled (on by default in the data stream settings):

  • page_view -- fired on every page load and on history.pushState for SPAs
  • scroll -- fired when the user scrolls past 90% of the page height
  • click -- fired on outbound link clicks (links to other domains)
  • view_search_results -- fired when a URL contains a search query parameter (q, s, search, query, or keyword)
  • video_start, video_progress, video_complete -- fired for embedded YouTube videos
  • file_download -- fired when a user clicks a link to a file (pdf, xlsx, docx, etc.)
  • form_start, form_submit -- fired on form interaction

Custom Events

Send custom events with gtag('event', ...):

// Track a button click with custom parameters
gtag('event', 'cta_click', {
  button_text: 'Start Free Trial',
  button_location: 'hero_section',
  page_type: 'landing_page'
});

// Track a purchase (recommended e-commerce event)
gtag('event', 'purchase', {
  transaction_id: 'T12345',
  value: 49.99,
  currency: 'USD',
  items: [
    {
      item_id: 'SKU_123',
      item_name: 'Pro Plan',
      price: 49.99,
      quantity: 1
    }
  ]
});

GA4 has a set of recommended event names (login, sign_up, purchase, add_to_cart, begin_checkout, etc.) with predefined parameter schemas. Using recommended events enables built-in e-commerce and engagement reports. Any event name not in the recommended list is treated as a custom event.

User Properties

Set user-scoped properties that persist across events:

gtag('set', 'user_properties', {
  account_type: 'premium',
  signup_date: '2026-01-15',
  lifetime_value: 299.99
});

User properties must be registered in the GA4 Admin under Custom Definitions before they appear in reports.

Identity and User Tracking

GA4 supports three identity resolution methods, applied in a cascade:

  1. User-ID -- You assign a persistent identifier when the user is authenticated. Set it with gtag('config', 'G-XXXXXXXXXX', { user_id: 'USER_123' }). This is the most accurate method because it works across devices and browsers.

  2. Google Signals -- When enabled, GA4 uses Google account data from signed-in users who have opted into ad personalization. This provides cross-device tracking without requiring your own User-ID implementation. Enable under Admin > Data Settings > Data Collection.

  3. Device ID -- The _ga cookie (web) or Firebase Instance ID (app). This is the fallback when neither User-ID nor Google Signals is available. It only identifies a single browser or app instance.

GA4 applies these in order: User-ID first, then Google Signals, then Device ID. The reporting identity setting (under Admin > Reporting Identity) controls which methods are used for reports. "Blended" uses all three; "Observed" uses User-ID and Google Signals; "Device-based" uses only Device ID.

Measurement Protocol

The Measurement Protocol lets you send events to GA4 from server-side code, IoT devices, or any system that can make HTTP requests:

# Send a server-side event to GA4
curl -X POST "https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "client_id_from_cookie",
    "events": [{
      "name": "server_purchase",
      "params": {
        "transaction_id": "T67890",
        "value": 120.00,
        "currency": "USD"
      }
    }]
  }'

The API secret is generated in Admin > Data Streams > Measurement Protocol API secrets. The client_id must match the _ga cookie value (minus the GA1.1. prefix) to associate server-side events with the correct user.

For authenticated server-side events, include user_id in the payload:

{
  "client_id": "123456789.1234567890",
  "user_id": "USER_123",
  "events": [{
    "name": "subscription_renewed",
    "params": {
      "plan": "annual",
      "value": 599.00
    }
  }]
}

The Measurement Protocol does not return data in the response. There is a validation endpoint at /mp/collect?...&validation=1 that returns error details without recording the event.

BigQuery Export

GA4 can export raw event data to BigQuery daily (free) or in streaming mode (GA4 360 only). Enable under Admin > BigQuery Links.

Once linked, GA4 creates daily tables in BigQuery named events_YYYYMMDD. Each row is a single event with nested fields:

-- Count events by name for the last 7 days
SELECT
  event_name,
  COUNT(*) AS event_count
FROM `project.analytics_PROPERTY_ID.events_*`
WHERE _TABLE_SUFFIX BETWEEN
  FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY))
  AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
GROUP BY event_name
ORDER BY event_count DESC;

-- Extract a specific event parameter value
SELECT
  event_timestamp,
  (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page,
  (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'engagement_time_msec') AS engaged_ms
FROM `project.analytics_PROPERTY_ID.events_*`
WHERE event_name = 'page_view'
  AND _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE());

The BigQuery schema uses nested RECORD fields for event_params, user_properties, and items. Each parameter is stored as a key-value pair inside a REPEATED RECORD, which requires UNNEST to query.

GA4 integrates with Consent Mode v2 to adjust data collection behavior based on user consent choices. Set consent defaults before gtag('config', ...):

gtag('consent', 'default', {
  analytics_storage: 'denied',      // Blocks _ga cookie until consent
  ad_storage: 'denied',             // Blocks advertising cookies
  ad_user_data: 'denied',           // Controls sending user data for ads
  ad_personalization: 'denied',     // Controls ad personalization signals
  wait_for_update: 500              // Wait 500ms for consent tool to load
});

When the user grants consent, update the consent state:

gtag('consent', 'update', {
  analytics_storage: 'granted',
  ad_storage: 'granted',
  ad_user_data: 'granted',
  ad_personalization: 'granted'
});

When analytics_storage is denied, GA4 does not set the _ga cookie and does not send full event payloads. Instead, it sends cookieless pings that GA4 uses for behavioral modeling to fill gaps in reports. This modeled data is clearly marked in the GA4 interface.

Common Issues

Issue Cause Fix
Events appear in DebugView but not in standard reports Standard reports have 24-48 hour processing latency Wait for processing; use Realtime report or DebugView for immediate verification
(not set) values in reports Event parameters or user properties not registered as custom dimensions in Admin > Custom Definitions Register the parameter as a custom dimension before data appears in reports (not retroactive)
Duplicate page_view events on SPA Enhanced Measurement detects history.pushState and fires page_view, plus your code manually sends one Disable page_view in Enhanced Measurement settings or remove the manual gtag('event', 'page_view') call
Measurement Protocol events not appearing Wrong client_id format, missing API secret, or using the validation endpoint Ensure client_id matches the _ga cookie value; generate an API secret in the data stream settings; remove validation=1 from the URL
Cross-domain tracking not working Domains not configured in the Admin > Data Streams > Configure tag settings > Configure your domains Add all domains to the cross-domain configuration; GA4 will automatically append the _gl linker parameter to outbound links
BigQuery export tables missing BigQuery link not active, or export delay (tables appear 24h after link creation) Verify the BigQuery link is active under Admin > BigQuery Links; daily tables are created for the previous day
Consent mode blocking all data analytics_storage set to denied without an update mechanism Verify your CMP calls gtag('consent', 'update', ...) after the user accepts cookies
Event parameter values truncated GA4 limits parameter values to 100 characters (event name: 40 chars) Shorten parameter values; use a different parameter for long strings
Thresholding hides data in reports When Google Signals is enabled and the user count is low, GA4 applies thresholding to protect privacy Switch reporting identity to "Device-based" or increase the date range to raise the user count above the threshold

Data Retention and Limits

GA4 has specific limits that affect implementation:

  • Event parameters: 25 parameters per event, 50 custom dimensions, 50 custom metrics per property
  • User properties: 25 per property
  • Event name length: 40 characters max
  • Parameter key length: 40 characters max
  • Parameter value length: 100 characters max
  • Data retention: 2 months or 14 months for exploration reports (configurable under Admin > Data Settings > Data Retention). Standard reports are unaffected by retention settings because they use aggregated data.
  • BigQuery export: Daily export is free; streaming export requires GA4 360. Daily tables contain all events regardless of the UI retention setting.