← Back to Documentation
    DocumentationintegrationsFeb 11, 2025

    GA4 Event Implementation Reference

    Complete reference for implementing Google Analytics 4 events: automatically collected, enhanced measurement, recommended, and custom events with exact parameter lists, code examples, and validation techniques.

    Event Model Overview

    Google Analytics 4 uses a fundamentally different data model from Universal Analytics (UA). Where UA tracked discrete hit types (pageview, event, transaction, social, timing), GA4 treats everything as an event. There are no hit types — a page view is an event, a purchase is an event, and a scroll is an event.

    Key differences from Universal Analytics

    ConceptUniversal AnalyticsGA4
    Data modelHit-based (pageview, event, transaction)Event-based (everything is an event)
    Event structureCategory / Action / Label / ValueEvent name + parameters (key-value pairs)
    SessionsServer-defined, 30-min timeoutDerived from session_start event, configurable timeout
    PageviewsDedicated hit typepage_view event with page_location parameter
    E-commerceEnhanced Ecommerce pluginBuilt-in recommended events with items array
    Custom dataCustom dimensions/metrics (index-based)Event parameters + custom dimensions/metrics (name-based)
    User identityClient ID + User IDClient ID (user_pseudo_id) + User ID (user_id) + Google Signals

    Event anatomy

    Every GA4 event consists of:

    • Event name: A string identifier (e.g., page_view, purchase, sign_up)
    • Event parameters: Key-value pairs attached to the event (e.g., page_location, transaction_id)
    • User properties: Attributes of the user that persist across events (e.g., membership_tier)
    // Conceptual structure of a GA4 event
    {
      event_name: "purchase",
      event_params: {
        transaction_id: "T12345",
        value: 99.99,
        currency: "USD",
        items: [{ item_id: "SKU123", item_name: "Widget", price: 99.99 }]
      },
      user_properties: {
        membership_tier: "gold"
      },
      // Automatically attached:
      event_timestamp: 1707612345000000,
      user_pseudo_id: "abc123.def456",
      device: { category: "desktop", browser: "Chrome" },
      geo: { country: "United States", city: "San Francisco" }
    }
    

    Automatically Collected Events

    These events are collected automatically by the GA4 tag with no additional configuration. You cannot disable them.

    Web (gtag.js / GTM)

    EventTriggerKey Parameters
    first_visitFirst time a user visits the site (new user_pseudo_id cookie created)None (user-level event)
    session_startNew session begins (30-minute inactivity timeout or midnight boundary)ga_session_id, ga_session_number
    page_viewEvery page load (or history.pushState in SPAs when enhanced measurement is on)page_location, page_title, page_referrer
    user_engagementWhen the page is in focus for at least 1 second and user has interactedengagement_time_msec

    Mobile (Firebase SDK)

    EventTriggerKey Parameters
    first_openFirst time a user opens the app after install or reinstallprevious_gmp_app_id, updated_with_analytics
    session_startNew session beginsga_session_id, ga_session_number
    screen_viewScreen transition or logEvent call with screen_viewfirebase_screen, firebase_screen_class, firebase_screen_id, firebase_previous_screen
    user_engagementApp is in the foreground for at least 1 secondengagement_time_msec
    app_updateApp is updated to a new version and launchedprevious_app_version
    app_removeApp package is removed (Android only)None
    os_updateDevice OS is updatedprevious_os_version
    app_clear_dataUser resets/clears app dataNone
    app_exceptionApp crashes or throws an uncaught exceptionfatal, timestamp

    Automatically attached parameters

    Every event (automatic or otherwise) includes these parameters by default:

    • language — User's language setting
    • page_location — Full URL (web) or screen name (app)
    • page_referrer — Previous page URL (web)
    • page_title — HTML document title (web)
    • screen_resolution — Display resolution
    • ga_session_id — Session identifier
    • ga_session_number — Sequential session count for the user
    • engagement_time_msec — Engagement time in milliseconds

    Enhanced Measurement Events

    Enhanced measurement events are collected automatically when enabled in the GA4 data stream settings (Admin > Data Streams > Web > Enhanced Measurement). Each event type can be toggled on or off individually.

    EventTriggerKey ParametersToggle
    scrollUser scrolls past 90% of page heightpercent_scrolled (always 90)Scrolls
    clickUser clicks a link that navigates away from the current domainlink_url, link_domain, link_classes, link_id, outbound (true)Outbound clicks
    view_search_resultsPage loads with a URL query parameter matching a search term pattern (default: q, s, search, query, keyword)search_termSite search
    video_startEmbedded YouTube video starts playingvideo_url, video_title, video_provider, video_current_time, visibleVideo engagement
    video_progressYouTube video reaches 10%, 25%, 50%, or 75%video_url, video_title, video_provider, video_percent, video_current_time, visibleVideo engagement
    video_completeYouTube video reaches endvideo_url, video_title, video_provider, video_current_time, visibleVideo engagement
    file_downloadUser clicks a link to a file (extensions: pdf, xls, xlsx, doc, docx, txt, rtf, csv, exe, key, pps, ppt, pptx, 7z, pkg, rar, gz, zip, avi, mov, mp4, mpeg, wmv, midi, mp3, wav, wma)file_name, file_extension, link_url, link_text, link_domainFile downloads
    form_startUser first interacts with a form (focus on an input field)form_id, form_name, form_destinationForm interactions
    form_submitUser submits a formform_id, form_name, form_destination, form_submit_textForm interactions

    Configuring enhanced measurement

    Enhanced measurement is enabled by default for new web data streams. To configure:

    1. Navigate to Admin > Data Streams > select your web stream
    2. Click Enhanced measurement toggle section
    3. Enable or disable individual event types
    4. For site search, click the gear icon to configure custom query parameters
    // You can also configure enhanced measurement via gtag.js
    gtag('config', 'G-XXXXXXXXXX', {
      // Disable specific enhanced measurement events
      send_page_view: false  // disable automatic page_view on config
    });
    

    Note: Video tracking only works for embedded YouTube videos using the JS API (enablejsapi=1). Self-hosted or third-party video players require custom event implementation.

    Recommended Events

    Recommended events have predefined names and parameter lists that unlock built-in GA4 reports and features (e.g., e-commerce reports, monetization reports). You must implement these manually, but using the exact event names and parameter names enables GA4 to populate standard reports.

    All Properties

    These recommended events apply to any type of website or app.

    login

    Sent when a user logs in.

    gtag('event', 'login', {
      method: 'Google'  // STRING — login method (e.g., 'Google', 'Email', 'Facebook')
    });
    

    sign_up

    Sent when a user registers an account.

    gtag('event', 'sign_up', {
      method: 'Email'  // STRING — registration method
    });
    

    share

    Sent when a user shares content.

    gtag('event', 'share', {
      method: 'Twitter',         // STRING — sharing method
      content_type: 'article',   // STRING — type of shared content
      item_id: 'article_12345'   // STRING — identifier for the shared content
    });
    

    search

    Sent when a user performs a search.

    gtag('event', 'search', {
      search_term: 'running shoes'  // STRING — the search query
    });
    

    select_content

    Sent when a user selects content.

    gtag('event', 'select_content', {
      content_type: 'product',    // STRING — type of content selected
      content_id: 'P12345'        // STRING — identifier for the content
    });
    

    E-commerce

    These events power GA4's built-in e-commerce reports. Implement them in sequence to see full funnel data.

    view_item_list

    Sent when a user views a list of items (e.g., category page, search results).

    gtag('event', 'view_item_list', {
      item_list_id: 'category_123',       // STRING — list identifier
      item_list_name: 'Running Shoes',    // STRING — list display name
      items: [
        {
          item_id: 'SKU_123',
          item_name: 'Trail Runner Pro',
          item_brand: 'RunCo',
          item_category: 'Shoes',
          item_category2: 'Running',
          item_variant: 'Blue',
          price: 129.99,
          currency: 'USD',
          index: 0,
          item_list_id: 'category_123',
          item_list_name: 'Running Shoes'
        },
        {
          item_id: 'SKU_456',
          item_name: 'Road Runner Elite',
          item_brand: 'RunCo',
          item_category: 'Shoes',
          item_category2: 'Running',
          item_variant: 'Red',
          price: 149.99,
          currency: 'USD',
          index: 1,
          item_list_id: 'category_123',
          item_list_name: 'Running Shoes'
        }
      ]
    });
    

    select_item

    Sent when a user selects an item from a list.

    gtag('event', 'select_item', {
      item_list_id: 'category_123',
      item_list_name: 'Running Shoes',
      items: [{
        item_id: 'SKU_123',
        item_name: 'Trail Runner Pro',
        item_brand: 'RunCo',
        item_category: 'Shoes',
        price: 129.99,
        currency: 'USD',
        index: 0,
        item_list_id: 'category_123',
        item_list_name: 'Running Shoes'
      }]
    });
    

    view_item

    Sent when a user views a product detail page.

    gtag('event', 'view_item', {
      currency: 'USD',
      value: 129.99,
      items: [{
        item_id: 'SKU_123',
        item_name: 'Trail Runner Pro',
        item_brand: 'RunCo',
        item_category: 'Shoes',
        item_category2: 'Running',
        item_variant: 'Blue',
        price: 129.99,
        currency: 'USD',
        quantity: 1
      }]
    });
    

    add_to_cart

    Sent when a user adds an item to the cart.

    gtag('event', 'add_to_cart', {
      currency: 'USD',
      value: 129.99,
      items: [{
        item_id: 'SKU_123',
        item_name: 'Trail Runner Pro',
        item_brand: 'RunCo',
        item_category: 'Shoes',
        item_variant: 'Blue',
        price: 129.99,
        currency: 'USD',
        quantity: 1
      }]
    });
    

    remove_from_cart

    Sent when a user removes an item from the cart.

    gtag('event', 'remove_from_cart', {
      currency: 'USD',
      value: 129.99,
      items: [{
        item_id: 'SKU_123',
        item_name: 'Trail Runner Pro',
        price: 129.99,
        currency: 'USD',
        quantity: 1
      }]
    });
    

    view_cart

    Sent when a user views their cart.

    gtag('event', 'view_cart', {
      currency: 'USD',
      value: 259.98,
      items: [
        {
          item_id: 'SKU_123',
          item_name: 'Trail Runner Pro',
          price: 129.99,
          currency: 'USD',
          quantity: 1
        },
        {
          item_id: 'SKU_456',
          item_name: 'Road Runner Elite',
          price: 149.99,
          currency: 'USD',
          quantity: 1
        }
      ]
    });
    

    begin_checkout

    Sent when a user begins checkout.

    gtag('event', 'begin_checkout', {
      currency: 'USD',
      value: 259.98,
      coupon: 'SUMMER20',        // STRING — coupon code applied at checkout level
      items: [
        {
          item_id: 'SKU_123',
          item_name: 'Trail Runner Pro',
          price: 129.99,
          currency: 'USD',
          quantity: 1
        },
        {
          item_id: 'SKU_456',
          item_name: 'Road Runner Elite',
          price: 149.99,
          currency: 'USD',
          quantity: 1
        }
      ]
    });
    

    add_shipping_info

    Sent when a user submits shipping information during checkout.

    gtag('event', 'add_shipping_info', {
      currency: 'USD',
      value: 259.98,
      coupon: 'SUMMER20',
      shipping_tier: 'Express',  // STRING — shipping tier selected (e.g., 'Ground', 'Express', 'Next Day')
      items: [
        {
          item_id: 'SKU_123',
          item_name: 'Trail Runner Pro',
          price: 129.99,
          currency: 'USD',
          quantity: 1
        }
      ]
    });
    

    add_payment_info

    Sent when a user submits payment information during checkout.

    gtag('event', 'add_payment_info', {
      currency: 'USD',
      value: 259.98,
      coupon: 'SUMMER20',
      payment_type: 'Credit Card',  // STRING — payment method (e.g., 'Credit Card', 'PayPal', 'Apple Pay')
      items: [
        {
          item_id: 'SKU_123',
          item_name: 'Trail Runner Pro',
          price: 129.99,
          currency: 'USD',
          quantity: 1
        }
      ]
    });
    

    purchase

    Sent when a user completes a purchase. This is the most critical e-commerce event.

    gtag('event', 'purchase', {
      transaction_id: 'T12345',    // STRING (REQUIRED) — unique transaction identifier
      value: 259.98,               // NUMBER (REQUIRED) — total transaction value
      currency: 'USD',             // STRING (REQUIRED) — ISO 4217 currency code
      tax: 20.80,                  // NUMBER — tax amount
      shipping: 9.99,              // NUMBER — shipping cost
      coupon: 'SUMMER20',          // STRING — coupon code
      items: [
        {
          item_id: 'SKU_123',
          item_name: 'Trail Runner Pro',
          affiliation: 'Online Store',
          coupon: 'ITEM10OFF',
          discount: 13.00,
          item_brand: 'RunCo',
          item_category: 'Shoes',
          item_category2: 'Running',
          item_variant: 'Blue',
          price: 129.99,
          currency: 'USD',
          quantity: 1
        },
        {
          item_id: 'SKU_456',
          item_name: 'Road Runner Elite',
          affiliation: 'Online Store',
          item_brand: 'RunCo',
          item_category: 'Shoes',
          item_category2: 'Running',
          item_variant: 'Red',
          price: 149.99,
          currency: 'USD',
          quantity: 1
        }
      ]
    });
    

    Critical: The purchase event requires transaction_id, value, and currency. Without these, the event will not populate e-commerce reports correctly. GA4 deduplicates purchases by transaction_id within a 72-hour window.

    refund

    Sent when a refund is issued.

    // Full refund
    gtag('event', 'refund', {
      transaction_id: 'T12345',   // STRING (REQUIRED) — original transaction ID
      value: 259.98,
      currency: 'USD'
    });
    
    // Partial refund (include items being refunded)
    gtag('event', 'refund', {
      transaction_id: 'T12345',
      value: 129.99,
      currency: 'USD',
      items: [{
        item_id: 'SKU_123',
        item_name: 'Trail Runner Pro',
        price: 129.99,
        currency: 'USD',
        quantity: 1
      }]
    });
    

    Lead Generation

    generate_lead

    Sent when a user submits a lead form or completes a lead generation action.

    gtag('event', 'generate_lead', {
      currency: 'USD',
      value: 50.00   // NUMBER — estimated value of the lead
    });
    

    Gaming

    earn_virtual_currency

    gtag('event', 'earn_virtual_currency', {
      virtual_currency_name: 'Coins',  // STRING — name of the virtual currency
      value: 100                        // NUMBER — amount earned
    });
    

    spend_virtual_currency

    gtag('event', 'spend_virtual_currency', {
      virtual_currency_name: 'Coins',  // STRING — name of the virtual currency
      value: 50,                        // NUMBER — amount spent
      item_name: 'Power Boost'         // STRING — item purchased
    });
    

    level_up

    gtag('event', 'level_up', {
      level: 5,             // NUMBER — new level reached
      character: 'Warrior'  // STRING — character that leveled up
    });
    

    post_score

    gtag('event', 'post_score', {
      score: 15000,         // NUMBER (REQUIRED) — the score
      level: 5,             // NUMBER — level where score was achieved
      character: 'Warrior'  // STRING — character used
    });
    

    tutorial_begin

    gtag('event', 'tutorial_begin');
    // No required parameters
    

    tutorial_complete

    gtag('event', 'tutorial_complete');
    // No required parameters
    

    unlock_achievement

    gtag('event', 'unlock_achievement', {
      achievement_id: 'first_blood'  // STRING (REQUIRED) — achievement identifier
    });
    

    Item Parameter Reference

    The items array used across e-commerce events supports these parameters per item:

    ParameterTypeDescription
    item_idSTRINGItem SKU/ID (recommended)
    item_nameSTRINGItem display name (recommended)
    affiliationSTRINGStore or affiliation
    couponSTRINGItem-level coupon code
    discountNUMBERDiscount amount
    indexNUMBERPosition in list
    item_brandSTRINGBrand name
    item_categorySTRINGPrimary category
    item_category2STRINGCategory level 2
    item_category3STRINGCategory level 3
    item_category4STRINGCategory level 4
    item_category5STRINGCategory level 5
    item_list_idSTRINGList ID where item was shown
    item_list_nameSTRINGList name where item was shown
    item_variantSTRINGVariant (e.g., color, size)
    location_idSTRINGPhysical location associated with the item
    priceNUMBERItem price
    currencySTRINGISO 4217 currency code
    quantityNUMBERItem quantity
    promotion_idSTRINGPromotion ID
    promotion_nameSTRINGPromotion name
    creative_nameSTRINGCreative name associated with a promotion
    creative_slotSTRINGCreative slot associated with a promotion

    Note: At least one of item_id or item_name is required for each item. GA4 supports up to 200 items per event.

    Custom Events

    Use custom events when no automatically collected, enhanced measurement, or recommended event fits your use case.

    Naming Rules

    • Maximum 40 characters for event names
    • Must start with an alphabetic character
    • Only alphanumeric characters and underscores allowed ([a-zA-Z][a-zA-Z0-9_]*)
    • Case sensitive: Add_To_Cart and add_to_cart are different events
    • Cannot use reserved prefixes: firebase_, google_, ga_
    • Cannot use reserved event names: ad_activeview, ad_click, ad_exposure, ad_impression, ad_query, adunit_exposure, app_clear_data, app_install, app_update, app_remove, error, first_open, first_visit, in_app_purchase, notification_dismiss, notification_foreground, notification_open, notification_receive, os_update, session_start, screen_view, user_engagement

    Parameter Limits

    LimitValue
    Unique event names per property500 (auto + enhanced + recommended + custom)
    Parameters per event25
    Parameter name length40 characters
    Parameter value length (string)100 characters
    User property name length24 characters
    User property value length (string)36 characters
    User properties per project25

    Custom Event Examples

    // Newsletter signup
    gtag('event', 'newsletter_signup', {
      newsletter_type: 'weekly_digest',
      signup_location: 'footer',
      user_segment: 'returning_visitor'
    });
    
    // Feature usage tracking (SaaS)
    gtag('event', 'feature_used', {
      feature_name: 'export_csv',
      feature_category: 'data_tools',
      plan_tier: 'pro'
    });
    
    // Content engagement
    gtag('event', 'article_read', {
      article_id: 'post_12345',
      article_category: 'technology',
      read_time_seconds: 245,
      scroll_depth: 85
    });
    
    // Error tracking
    gtag('event', 'error_occurred', {
      error_type: 'form_validation',
      error_message: 'invalid_email',
      page_section: 'checkout'
    });
    

    GTM dataLayer Implementation

    When using Google Tag Manager, push events to the dataLayer:

    // Standard custom event
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
      event: 'newsletter_signup',
      newsletter_type: 'weekly_digest',
      signup_location: 'footer'
    });
    
    // E-commerce purchase via dataLayer
    window.dataLayer.push({ ecommerce: null });  // Clear previous ecommerce data
    window.dataLayer.push({
      event: 'purchase',
      ecommerce: {
        transaction_id: 'T12345',
        value: 259.98,
        currency: 'USD',
        tax: 20.80,
        shipping: 9.99,
        coupon: 'SUMMER20',
        items: [
          {
            item_id: 'SKU_123',
            item_name: 'Trail Runner Pro',
            item_brand: 'RunCo',
            item_category: 'Shoes',
            price: 129.99,
            quantity: 1
          }
        ]
      }
    });
    

    Important: Always push { ecommerce: null } before e-commerce events to clear stale data from the dataLayer. This prevents data leakage between events.

    Custom Dimensions and Metrics

    Custom dimensions and metrics let you extend GA4's data model by registering event parameters or user properties for reporting.

    Registration

    Register custom dimensions and metrics in Admin > Custom definitions.

    • Custom dimension (event-scoped): Maps an event parameter to a reportable dimension
    • Custom dimension (user-scoped): Maps a user property to a reportable dimension
    • Custom metric: Maps a numeric event parameter to a reportable metric

    Scoping

    ScopeSourceUse Case
    Event-scoped dimensionEvent parameterPage-level or action-level attributes (e.g., content_type, form_name)
    User-scoped dimensionUser propertyUser-level attributes that persist (e.g., membership_tier, signup_source)
    Custom metricEvent parameter (numeric)Numeric values for aggregation (e.g., read_time_seconds, score)

    Quota Limits

    ResourceStandard PropertiesAnalytics 360
    Event-scoped custom dimensions50125
    User-scoped custom dimensions25100
    Custom metrics50125

    Processing Time

    After registration, custom dimensions and metrics take 24-48 hours to begin populating in standard reports. They appear in Realtime and DebugView immediately.

    Setting User Properties

    // Set user properties (persist across sessions)
    gtag('set', 'user_properties', {
      membership_tier: 'gold',
      signup_date: '2025-01-15',
      preferred_language: 'en'
    });
    
    // Alternative: set individual user property
    gtag('set', { user_properties: { membership_tier: 'gold' } });
    

    Implementation Patterns

    gtag.js (Global Site Tag)

    The simplest implementation method — add the gtag.js snippet directly to your HTML.

    <!-- Google tag (gtag.js) -->
    <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>
    

    Then send events anywhere in your code:

    // Send a custom event
    gtag('event', 'newsletter_signup', {
      newsletter_type: 'weekly_digest'
    });
    
    // Send a recommended event
    gtag('event', 'purchase', {
      transaction_id: 'T12345',
      value: 99.99,
      currency: 'USD',
      items: [{ item_id: 'SKU_123', item_name: 'Widget', price: 99.99, quantity: 1 }]
    });
    

    Google Tag Manager (GTM)

    For GTM implementations, push events to the dataLayer and create corresponding GA4 Event tags in GTM.

    // Push event to dataLayer
    window.dataLayer.push({
      event: 'cta_click',
      cta_text: 'Start Free Trial',
      cta_location: 'hero_section',
      page_section: 'homepage'
    });
    

    In GTM, create:

    1. Trigger: Custom Event trigger matching cta_click
    2. Tag: GA4 Event tag with event name cta_click
    3. Parameters: Map dataLayer variables to event parameters

    Measurement Protocol (Server-Side)

    The GA4 Measurement Protocol sends events server-side via HTTPS POST. Useful for offline conversions, CRM events, and backend-triggered events.

    # Endpoint
    POST https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=YOUR_API_SECRET
    
    # Payload
    {
      "client_id": "abc123.def456",
      "events": [{
        "name": "purchase",
        "params": {
          "transaction_id": "T12345",
          "value": 99.99,
          "currency": "USD",
          "items": [{
            "item_id": "SKU_123",
            "item_name": "Widget",
            "price": 99.99,
            "quantity": 1
          }]
        }
      }]
    }
    
    # Python example
    import requests
    import json
    
    MEASUREMENT_ID = 'G-XXXXXXXXXX'
    API_SECRET = 'YOUR_API_SECRET'
    
    url = f'https://www.google-analytics.com/mp/collect?measurement_id={MEASUREMENT_ID}&api_secret={API_SECRET}'
    
    payload = {
        'client_id': 'abc123.def456',  # Must match an existing GA4 client_id
        'events': [{
            'name': 'offline_purchase',
            'params': {
                'transaction_id': 'OFFLINE_T789',
                'value': 250.00,
                'currency': 'USD',
                'payment_type': 'invoice'
            }
        }]
    }
    
    response = requests.post(url, data=json.dumps(payload))
    # Returns 204 No Content on success (does NOT validate payload)
    

    Measurement Protocol limitations:

    • No response body — events are not validated server-side (use Validation Server for debugging)
    • client_id must match an existing GA4 cookie value to attribute events to users
    • Events are not visible in Realtime reports (30-minute processing delay)
    • Cannot trigger first_visit or session_start events

    Validation Server (Debug Endpoint)

    Use the validation endpoint during development to catch payload errors before sending to production:

    POST https://www.google-analytics.com/debug/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=YOUR_API_SECRET
    
    # Returns validation messages instead of silently accepting
    {
      "validationMessages": [
        {
          "fieldPath": "events[0].params.currency",
          "description": "currency is required when value is set",
          "validationCode": "VALUE_REQUIRED"
        }
      ]
    }
    

    Validation and Debugging

    DebugView

    GA4 DebugView (Admin > DebugView) shows events in near real-time from debug-enabled devices/browsers.

    Enable debug mode:

    // gtag.js — enable debug mode
    gtag('config', 'G-XXXXXXXXXX', { debug_mode: true });
    
    // Or per-event
    gtag('event', 'purchase', {
      debug_mode: true,
      transaction_id: 'T12345',
      value: 99.99,
      currency: 'USD'
    });
    

    For GTM, install the Google Analytics Debugger Chrome extension, or add a debug_mode = true field to your GA4 Configuration tag.

    DebugView shows:

    • Event timeline with event names and timestamps
    • Event parameters for each event (click to expand)
    • User properties at the time of each event
    • Conversion flag indicators

    Realtime Report

    The Realtime report (Reports > Realtime) shows aggregate data from the last 30 minutes. Use it to verify:

    • Events are arriving with correct names
    • User counts are reasonable
    • Event parameters are populated
    • Conversions are firing

    BigQuery Validation Queries

    Once events flow to BigQuery, use these queries to validate implementation quality.

    Check event volume by name

    SELECT
      event_name,
      COUNT(*) as event_count,
      COUNT(DISTINCT user_pseudo_id) as unique_users,
      MIN(TIMESTAMP_MICROS(event_timestamp)) as first_seen,
      MAX(TIMESTAMP_MICROS(event_timestamp)) as last_seen
    FROM `project.analytics_123456789.events_*`
    WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE() - 1)
    GROUP BY event_name
    ORDER BY event_count DESC
    

    Validate e-commerce funnel completeness

    WITH funnel AS (
      SELECT
        event_name,
        COUNT(*) as event_count,
        COUNT(DISTINCT user_pseudo_id) as users
      FROM `project.analytics_123456789.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() - 1)
        AND event_name IN (
          'view_item_list', 'select_item', 'view_item',
          'add_to_cart', 'remove_from_cart', 'view_cart',
          'begin_checkout', 'add_shipping_info', 'add_payment_info',
          'purchase', 'refund'
        )
      GROUP BY event_name
    )
    
    SELECT
      event_name,
      event_count,
      users,
      ROUND(SAFE_DIVIDE(users, MAX(users) OVER ()), 4) as pct_of_top
    FROM funnel
    ORDER BY event_count DESC
    

    Detect missing required parameters

    -- Check for purchase events missing transaction_id or value
    SELECT
      'purchase_missing_transaction_id' as issue,
      COUNT(*) as count
    FROM `project.analytics_123456789.events_*`
    WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE() - 1)
      AND event_name = 'purchase'
      AND (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id') IS NULL
    
    UNION ALL
    
    SELECT
      'purchase_missing_value' as issue,
      COUNT(*) as count
    FROM `project.analytics_123456789.events_*`
    WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE() - 1)
      AND event_name = 'purchase'
      AND (SELECT value.double_value FROM UNNEST(event_params) WHERE key = 'value') IS NULL
      AND (SELECT value.float_value FROM UNNEST(event_params) WHERE key = 'value') IS NULL
      AND (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'value') IS NULL
    
    UNION ALL
    
    SELECT
      'purchase_missing_currency' as issue,
      COUNT(*) as count
    FROM `project.analytics_123456789.events_*`
    WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE() - 1)
      AND event_name = 'purchase'
      AND (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'currency') IS NULL
    

    Find duplicate transactions

    SELECT
      transaction_id,
      COUNT(*) as duplicate_count,
      COUNT(DISTINCT user_pseudo_id) as user_count,
      MIN(TIMESTAMP_MICROS(event_timestamp)) as first_occurrence,
      MAX(TIMESTAMP_MICROS(event_timestamp)) as last_occurrence
    FROM (
      SELECT
        (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id') as transaction_id,
        user_pseudo_id,
        event_timestamp
      FROM `project.analytics_123456789.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() - 1)
        AND event_name = 'purchase'
    )
    WHERE transaction_id IS NOT NULL
    GROUP BY transaction_id
    HAVING COUNT(*) > 1
    ORDER BY duplicate_count DESC
    

    Audit custom event naming

    -- Find event names that violate naming conventions
    SELECT
      event_name,
      COUNT(*) as event_count,
      CASE
        WHEN LENGTH(event_name) > 40 THEN 'exceeds_40_chars'
        WHEN REGEXP_CONTAINS(event_name, r'^(firebase_|google_|ga_)') THEN 'reserved_prefix'
        WHEN NOT REGEXP_CONTAINS(event_name, r'^[a-zA-Z][a-zA-Z0-9_]*$') THEN 'invalid_characters'
        WHEN event_name != LOWER(event_name) AND event_name NOT IN (
          'first_visit', 'session_start', 'page_view', 'user_engagement'
        ) THEN 'mixed_case_warning'
        ELSE 'valid'
      END as naming_issue
    FROM `project.analytics_123456789.events_*`
    WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE() - 1)
    GROUP BY event_name
    HAVING naming_issue != 'valid'
    ORDER BY event_count DESC
    

    Check parameter value truncation

    -- Find parameters that might be truncated at the 100-character limit
    SELECT
      event_name,
      ep.key as parameter_name,
      MAX(LENGTH(ep.value.string_value)) as max_length,
      COUNTIF(LENGTH(ep.value.string_value) >= 100) as at_limit_count,
      COUNT(*) as total_count
    FROM `project.analytics_123456789.events_*`,
      UNNEST(event_params) as ep
    WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE() - 1)
      AND ep.value.string_value IS NOT NULL
    GROUP BY event_name, ep.key
    HAVING max_length >= 100
    ORDER BY at_limit_count DESC
    

    Common Pitfalls

    1. Event name case sensitivity

    GA4 event names are case sensitive. Purchase, purchase, and PURCHASE are three different events. Always use snake_case for consistency and to match recommended event names.

    // WRONG — creates a separate event from the recommended 'purchase'
    gtag('event', 'Purchase', { ... });
    
    // CORRECT
    gtag('event', 'purchase', { ... });
    

    2. Parameter value truncation

    String parameter values are truncated at 100 characters. URLs, long product names, or error messages may be silently cut off.

    // WRONG — URL will be truncated
    gtag('event', 'page_error', {
      full_url: window.location.href  // Could exceed 100 chars
    });
    
    // BETTER — hash or shorten the value
    gtag('event', 'page_error', {
      page_path: window.location.pathname,  // Usually shorter
      url_hash: hashFunction(window.location.href)  // Fixed length
    });
    

    3. Exceeding custom dimension quotas

    Standard GA4 properties allow 50 event-scoped and 25 user-scoped custom dimensions. Once you hit the limit, you cannot register new parameters for reporting without archiving existing ones. Plan your parameter taxonomy carefully.

    4. Duplicate purchase events

    Without proper deduplication, purchase events can fire multiple times (e.g., page refresh on thank-you page). GA4 deduplicates by transaction_id within 72 hours, but only if transaction_id is provided.

    // WRONG — no dedup protection without transaction_id
    gtag('event', 'purchase', { value: 99.99, currency: 'USD' });
    
    // CORRECT — always include transaction_id
    gtag('event', 'purchase', {
      transaction_id: 'T12345',
      value: 99.99,
      currency: 'USD',
      items: [...]
    });
    

    5. Missing currency with value

    When you send a value parameter, you must also send currency. Without currency, the value is ignored in monetization reports.

    6. Not clearing dataLayer ecommerce object

    In GTM implementations, stale ecommerce data in the dataLayer can leak into subsequent events.

    // ALWAYS clear before pushing ecommerce events
    window.dataLayer.push({ ecommerce: null });
    window.dataLayer.push({
      event: 'purchase',
      ecommerce: { ... }
    });
    

    7. Sending PII in event parameters

    GA4 Terms of Service prohibit sending personally identifiable information (PII) such as email addresses, phone numbers, or names in event parameters or user properties. Violations can result in data deletion or property suspension.

    // WRONG — PII in parameters
    gtag('event', 'sign_up', { email: 'user@example.com' });
    
    // CORRECT — use a hashed identifier
    gtag('event', 'sign_up', { method: 'email', user_type: 'new' });
    

    8. Exceeding the 500-event-name limit

    Each GA4 property supports a maximum of 500 unique event names (including automatically collected and enhanced measurement events). Dynamically generated event names (e.g., click_button_123) quickly exhaust this limit.

    // WRONG — dynamic event names burn through quota
    gtag('event', `click_${buttonId}`, {});
    
    // CORRECT — single event name with parameter
    gtag('event', 'button_click', { button_id: buttonId });
    

    Resources

    Claude Code Skill

    This reference is available as a Claude Code skill for instant access during development:

    ga4-events Skill on GitHub

    Install by adding to your .claude/settings.json:

    {
      "permissions": {
        "allow": [
          "skill:ga4-events"
        ]
      }
    }
    
    Ready for similar results?
    Start with Solo at $9/mo or talk to us about Cloud.
    ❯ get startedcompare plans →