Google Analytics 4 Install / Embed Tag or SDK | OpsBlu Docs

Google Analytics 4 Install / Embed Tag or SDK

Deployment approach for installing Google Analytics 4's tag or SDK across web, mobile, and server environments.

Overview

Google Analytics 4 (GA4) represents a fundamental shift from Universal Analytics, built on an event-based data model designed for cross-platform tracking. GA4 uses machine learning to provide predictive insights and works seamlessly across web and mobile app properties.

Key Architecture:

  • Data Streams: Individual sources (web, iOS, Android)
  • Events: All interactions tracked as events with parameters
  • User Properties: Persistent attributes describing users
  • Measurement Protocol: Server-side event ingestion
  • Firebase Integration: Unified mobile & web analytics

GA4 vs Universal Analytics:

  • Event-based vs session-based model
  • Enhanced measurement (scrolls, outbound clicks, site search) enabled by default
  • Cross-platform identity resolution
  • Predictive metrics and audiences
  • BigQuery export available on free tier

Prerequisites

1. GA4 Property Setup

Before installation:

  • GA4 Property Created: In Google Analytics, create a new GA4 property (not Universal Analytics)
  • Measurement ID: Located under Admin → Data Streams → Your Stream (format: G-XXXXXXXXXX)
  • API Secret: Required for Measurement Protocol (server-side tracking)
  • Data Streams: Separate streams for web, iOS, Android

For maximum flexibility:

  • GTM Container: Create container at tagmanager.google.com
  • Container ID: Format GTM-XXXXXXX
  • Workspace: Development workspace for testing
  • Environment Variables: Configure for staging/production

GA4 requires consent configuration:

  • Consent Mode: Determine if using Google Consent Mode v2
  • Default Consent State: granted or denied for analytics_storage and ad_storage
  • Geolocation Rules: Different consent defaults by region (EU vs non-EU)
  • CMP Integration: OneTrust, Cookiebot, or custom solution

4. Enhanced Measurement Decisions

Configure automatic event tracking:

  • Page Views: Always tracked
  • Scrolls: Track 90% scroll depth
  • Outbound Clicks: Track external link clicks
  • Site Search: Automatically detect search parameters
  • Video Engagement: YouTube video tracking
  • File Downloads: PDF, Excel, etc.

5. Data Layer Architecture

Plan your data structure:

window.dataLayer = window.dataLayer || [];
  • Event Names: Follow GA4 naming conventions (lowercase, underscores)
  • Parameters: Align with GA4 reserved names when possible
  • E-commerce: Use GA4 recommended e-commerce schema
  • User Properties: Maximum 25 custom user properties

Installation Methods

GTM provides the most flexible, maintainable GA4 implementation.

Step 1: Install GTM Container

Add GTM snippet to all pages:

<!DOCTYPE html>
<html>
  <head>
    <!-- Google Tag Manager -->
    <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
    })(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
    <!-- End Google Tag Manager -->
  </head>
  <body>
    <!-- Google Tag Manager (noscript) -->
    <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
    height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
    <!-- End Google Tag Manager (noscript) -->

    <!-- Your content -->
  </body>
</html>

Replace GTM-XXXXXXX with your container ID.

Step 2: Create GA4 Configuration Tag

  1. In GTM, go to Tags → New
  2. Tag Type: Google Analytics: GA4 Configuration
  3. Configuration:
    • Measurement ID: G-XXXXXXXXXX (or use GTM variable)
    • Send a page view event when this configuration loads: Checked (for initial setup)

Add consent defaults BEFORE the GA4 configuration:

  1. Create tag: Tag Type → Custom HTML
  2. Add consent initialization:
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}

  // Default consent to denied
  gtag('consent', 'default', {
    'analytics_storage': 'denied',
    'ad_storage': 'denied',
    'ad_user_data': 'denied',
    'ad_personalization': 'denied',
    'wait_for_update': 500
  });

  // Region-specific defaults (EU requires opt-in)
  gtag('consent', 'default', {
    'analytics_storage': 'granted',
    'ad_storage': 'granted',
    'region': ['US', 'CA', 'AU']
  });
</script>
  1. Trigger: Consent Initialization - All Pages
  2. Fire Priority: 100 (highest priority)

Create trigger and tag for consent updates:

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

Trigger: Custom Event → consent_granted

Step 5: Create GTM Variables

Create User-Defined Variables:

Measurement ID Variable:

  • Variable Type: Lookup Table
  • Input Variable: {{Page Hostname}}
  • Output:
    • localhostG-DEV123456
    • staging.yourdomain.comG-STAGING123
    • yourdomain.comG-PROD789

User ID Variable:

  • Variable Type: Data Layer Variable
  • Data Layer Variable Name: userId

Page Type Variable:

  • Variable Type: Data Layer Variable
  • Data Layer Variable Name: pageType

Step 6: Track Custom Events

Create GA4 Event tags:

  1. Tag Type: Google Analytics: GA4 Event
  2. Configuration Tag: Select your GA4 Configuration tag
  3. Event Name: {{Event Name}} (from Data Layer)
  4. Event Parameters:
    • event_category: {{Event Category}}
    • event_label: {{Event Label}}
    • value: {{Event Value}}

Trigger: Custom Event (match your data layer events)

Step 7: E-commerce Tracking

For purchase tracking:

// Data Layer push for purchase
dataLayer.push({
  event: 'purchase',
  ecommerce: {
    transaction_id: 'T12345',
    value: 149.99,
    currency: 'USD',
    tax: 12.00,
    shipping: 5.99,
    items: [
      {
        item_id: 'SKU-001',
        item_name: 'Premium Subscription',
        item_category: 'Subscription',
        item_variant: 'Annual',
        price: 149.99,
        quantity: 1
      }
    ]
  }
});

GA4 Event Tag:

  • Event Name: purchase
  • Send E-commerce data: Checked
  • Source: Data Layer
  • Trigger: Custom Event purchase

Method 2: gtag.js Direct Implementation

For simpler sites without GTM.

Basic Installation

Add global site tag to <head>:

<!DOCTYPE html>
<html>
  <head>
    <!-- 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>
  </head>
  <body>
    <!-- Your content -->
  </body>
</html>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}

  // Set default consent
  gtag('consent', 'default', {
    'analytics_storage': 'denied',
    'ad_storage': 'denied',
    'wait_for_update': 500
  });

  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
</script>

<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>

<!-- After user consents -->
<script>
  gtag('consent', 'update', {
    'analytics_storage': 'granted',
    'ad_storage': 'granted'
  });
</script>

Environment-Specific Configuration

<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  // Determine Measurement ID by environment
  const measurementId = window.location.hostname === 'yourdomain.com'
    ? 'G-PROD789'
    : window.location.hostname.includes('staging')
    ? 'G-STAGING123'
    : 'G-DEV456';

  gtag('config', measurementId, {
    'send_page_view': true,
    'anonymize_ip': true,
    'cookie_flags': 'SameSite=None;Secure'
  });
</script>

Tracking Custom Events

<script>
  // Simple event
  gtag('event', 'signup_button_click');

  // Event with parameters
  gtag('event', 'purchase', {
    transaction_id: 'T12345',
    value: 149.99,
    currency: 'USD',
    tax: 12.00,
    shipping: 5.99,
    items: [
      {
        item_id: 'SKU-001',
        item_name: 'Premium Subscription',
        price: 149.99,
        quantity: 1
      }
    ]
  });

  // Custom event
  gtag('event', 'custom_event_name', {
    event_category: 'engagement',
    event_label: 'button_click',
    value: 1
  });
</script>

User Identification

// Set User ID
gtag('config', 'G-XXXXXXXXXX', {
  'user_id': 'USER-12345'
});

// Set User Properties
gtag('set', 'user_properties', {
  'membership_level': 'premium',
  'signup_date': '2024-01-15'
});

Method 3: NPM Package (@analytics/google-analytics)

For modern JavaScript applications.

Installation

npm install analytics @analytics/google-analytics
# or
yarn add analytics @analytics/google-analytics

Basic Configuration

import Analytics from 'analytics'
import googleAnalytics from '@analytics/google-analytics'

const analytics = Analytics({
  app: 'your-app-name',
  plugins: [
    googleAnalytics({
      measurementIds: ['G-XXXXXXXXXX']
    })
  ]
})

// Track page
analytics.page()

// Track event
analytics.track('button_clicked', {
  category: 'engagement',
  label: 'cta_button'
})

// Identify user
analytics.identify('user-123', {
  email: 'user@example.com',
  membership: 'premium'
})

Advanced Configuration

import Analytics from 'analytics'
import googleAnalytics from '@analytics/google-analytics'

const analytics = Analytics({
  app: 'your-app',
  plugins: [
    googleAnalytics({
      measurementIds: [
        process.env.REACT_APP_GA_MEASUREMENT_ID
      ],
      gtagConfig: {
        'anonymize_ip': true,
        'cookie_flags': 'SameSite=None;Secure',
        'send_page_view': false // Manual page tracking
      },
      customDimensions: {
        'dimension1': 'user_type',
        'dimension2': 'page_type'
      }
    })
  ]
})

export default analytics

Framework Integrations

React

Using Custom Hook

// hooks/useAnalytics.js
import { useEffect } from 'react'

export function usePageTracking() {
  useEffect(() => {
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('event', 'page_view', {
        page_path: window.location.pathname,
        page_title: document.title
      })
    }
  }, [])
}

// Component.jsx
import { usePageTracking } from './hooks/useAnalytics'

function MyPage() {
  usePageTracking()

  const handleClick = () => {
    gtag('event', 'button_click', {
      event_category: 'engagement',
      event_label: 'signup_cta'
    })
  }

  return <button Up</button>
}

React Router Integration

// App.js
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'

function App() {
  const location = useLocation()

  useEffect(() => {
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('event', 'page_view', {
        page_path: location.pathname + location.search,
        page_title: document.title
      })
    }
  }, [location])

  return <YourRoutes />
}

React Context Provider

// context/AnalyticsContext.js
import React, { createContext, useContext } from 'react'

const AnalyticsContext = createContext()

export function AnalyticsProvider({ children, measurementId }) {
  const trackEvent = (eventName, parameters) => {
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('event', eventName, parameters)
    }
  }

  const trackPage = (pagePath, pageTitle) => {
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('event', 'page_view', {
        page_path: pagePath,
        page_title: pageTitle
      })
    }
  }

  const setUserId = (userId) => {
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('config', measurementId, {
        'user_id': userId
      })
    }
  }

  return (
    <AnalyticsContext.Provider value={{ trackEvent, trackPage, setUserId }}>
      {children}
    </AnalyticsContext.Provider>
  )
}

export function useAnalytics() {
  return useContext(AnalyticsContext)
}

// Usage in component
import { useAnalytics } from './context/AnalyticsContext'

function Component() {
  const { trackEvent } = useAnalytics()

  const handlePurchase = () => {
    trackEvent('purchase', {
      transaction_id: 'T123',
      value: 99.99,
      currency: 'USD'
    })
  }

  return <button Now</button>
}

Next.js

App Router (Next.js 13+)

// app/GoogleAnalytics.js
'use client'

import Script from 'next/script'

export default function GoogleAnalytics({ measurementId }) {
  return (
    <>
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
        strategy="afterInteractive"
      />
      <Script id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', '${measurementId}', {
            page_path: window.location.pathname,
          });
        `}
      </Script>
    </>
  )
}

// app/layout.js
import GoogleAnalytics from './GoogleAnalytics'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <GoogleAnalytics measurementId={process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID} />
        {children}
      </body>
    </html>
  )
}

Pages Router

// pages/_app.js
import Script from 'next/script'
import { useRouter } from 'next/router'
import { useEffect } from 'react'

const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID

function MyApp({ Component, pageProps }) {
  const router = useRouter()

  useEffect(() => {
    const handleRouteChange = (url) => {
      if (typeof window !== 'undefined' && window.gtag) {
        window.gtag('config', GA_MEASUREMENT_ID, {
          page_path: url,
        })
      }
    }

    router.events.on('routeChangeComplete', handleRouteChange)
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [router.events])

  return (
    <>
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
        strategy="afterInteractive"
      />
      <Script id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', '${GA_MEASUREMENT_ID}', {
            page_path: window.location.pathname,
          });
        `}
      </Script>
      <Component {...pageProps} />
    </>
  )
}

export default MyApp

Utility Functions

// utils/gtag.js
export const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID

export const pageview = (url) => {
  if (typeof window !== 'undefined' && window.gtag) {
    window.gtag('config', GA_MEASUREMENT_ID, {
      page_path: url,
    })
  }
}

export const event = ({ action, category, label, value }) => {
  if (typeof window !== 'undefined' && window.gtag) {
    window.gtag('event', action, {
      event_category: category,
      event_label: label,
      value: value,
    })
  }
}

Vue.js

Vue 3 Plugin

// plugins/analytics.js
export default {
  install: (app, options) => {
    app.config.globalProperties.$gtag = (...args) => {
      if (typeof window !== 'undefined' && window.gtag) {
        window.gtag(...args)
      }
    }

    app.provide('gtag', app.config.globalProperties.$gtag)
  }
}

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import analyticsPlugin from './plugins/analytics'

const app = createApp(App)
app.use(analyticsPlugin)
app.mount('#app')

Vue Router Integration

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [/* your routes */]
})

router.afterEach((to, from) => {
  if (typeof window !== 'undefined' && window.gtag) {
    window.gtag('event', 'page_view', {
      page_path: to.fullPath,
      page_title: to.meta.title || document.title
    })
  }
})

export default router

Angular

Service Implementation

// services/analytics.service.ts
import { Injectable } from '@angular/core'

declare global {
  interface Window {
    gtag?: (...args: any[]) => void
    dataLayer?: any[]
  }
}

@Injectable({
  providedIn: 'root'
})
export class AnalyticsService {
  private measurementId: string

  constructor() {
    this.measurementId = environment.gaMeasurementId
  }

  init() {
    const script1 = document.createElement('script')
    script1.async = true
    script1.src = `https://www.googletagmanager.com/gtag/js?id=${this.measurementId}`
    document.head.appendChild(script1)

    const script2 = document.createElement('script')
    script2.innerHTML = `
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
      gtag('config', '${this.measurementId}');
    `
    document.head.appendChild(script2)
  }

  trackEvent(eventName: string, parameters?: any) {
    if (window.gtag) {
      window.gtag('event', eventName, parameters)
    }
  }

  trackPage(pagePath: string, pageTitle: string) {
    if (window.gtag) {
      window.gtag('event', 'page_view', {
        page_path: pagePath,
        page_title: pageTitle
      })
    }
  }

  setUserId(userId: string) {
    if (window.gtag) {
      window.gtag('config', this.measurementId, {
        'user_id': userId
      })
    }
  }
}

Router Integration

// app.component.ts
import { Component, OnInit } from '@angular/core'
import { Router, NavigationEnd } from '@angular/router'
import { filter } from 'rxjs/operators'
import { AnalyticsService } from './services/analytics.service'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  constructor(
    private router: Router,
    private analytics: AnalyticsService
  ) {}

  ngOnInit() {
    this.analytics.init()

    this.router.events
      .pipe(filter(event => event instanceof NavigationEnd))
      .subscribe((event: NavigationEnd) => {
        this.analytics.trackPage(event.urlAfterRedirects, document.title)
      })
  }
}

Mobile SDK Installation

iOS (Firebase SDK)

Installation via CocoaPods

# Podfile
platform :ios, '13.0'

target 'YourApp' do
  use_frameworks!

  pod 'Firebase/Analytics'
  pod 'Firebase/Core'
end
pod install

Swift Package Manager

  1. In Xcode: File → Add Packages
  2. Enter: https://github.com/firebase/firebase-ios-sdk
  3. Select FirebaseAnalytics

Configuration

// AppDelegate.swift
import UIKit
import Firebase

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication,
                    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // Configure Firebase
        FirebaseApp.configure()

        // Enable debug mode
        #if DEBUG
        Analytics.setAnalyticsCollectionEnabled(true)
        #endif

        return true
    }
}

Tracking Events

import Firebase

// Log custom event
Analytics.logEvent("button_tap", parameters: [
    "button_name": "signup_cta",
    "screen_name": "home"
])

// Log screen view
Analytics.logEvent(AnalyticsEventScreenView, parameters: [
    AnalyticsParameterScreenName: "Home",
    AnalyticsParameterScreenClass: "HomeViewController"
])

// Log purchase
Analytics.logEvent(AnalyticsEventPurchase, parameters: [
    AnalyticsParameterTransactionID: "T12345",
    AnalyticsParameterValue: 99.99,
    AnalyticsParameterCurrency: "USD",
    AnalyticsParameterItems: [
        [
            AnalyticsParameterItemID: "SKU-001",
            AnalyticsParameterItemName: "Premium Subscription",
            AnalyticsParameterPrice: 99.99,
            AnalyticsParameterQuantity: 1
        ]
    ]
])

// Set User ID
Analytics.setUserID("user-123")

// Set User Property
Analytics.setUserProperty("premium", forName: "membership_level")

Android (Firebase SDK)

Installation

// build.gradle (project level)
buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.3.15'
    }
}

// build.gradle (app level)
plugins {
    id 'com.google.gms.google-services'
}

dependencies {
    implementation platform('com.google.firebase:firebase-bom:32.0.0')
    implementation 'com.google.firebase:firebase-analytics'
}

Configuration

Add google-services.json to app/ directory.

// Application.kt
import android.app.Application
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase

class MyApplication : Application() {
    private lateinit var firebaseAnalytics: FirebaseAnalytics

    override fun onCreate() {
        super.onCreate()

        // Initialize Firebase Analytics
        firebaseAnalytics = Firebase.analytics

        // Enable debug mode
        firebaseAnalytics.setAnalyticsCollectionEnabled(true)
    }
}

Tracking Events

import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.analytics.ktx.logEvent
import com.google.firebase.ktx.Firebase

val firebaseAnalytics = Firebase.analytics

// Log custom event
firebaseAnalytics.logEvent("button_tap") {
    param("button_name", "signup_cta")
    param("screen_name", "home")
}

// Log screen view
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) {
    param(FirebaseAnalytics.Param.SCREEN_NAME, "Home")
    param(FirebaseAnalytics.Param.SCREEN_CLASS, "HomeActivity")
}

// Log purchase
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.PURCHASE) {
    param(FirebaseAnalytics.Param.TRANSACTION_ID, "T12345")
    param(FirebaseAnalytics.Param.VALUE, 99.99)
    param(FirebaseAnalytics.Param.CURRENCY, "USD")
    param(FirebaseAnalytics.Param.ITEMS, arrayOf(
        Bundle().apply {
            putString(FirebaseAnalytics.Param.ITEM_ID, "SKU-001")
            putString(FirebaseAnalytics.Param.ITEM_NAME, "Premium Subscription")
            putDouble(FirebaseAnalytics.Param.PRICE, 99.99)
            putLong(FirebaseAnalytics.Param.QUANTITY, 1)
        }
    ))
}

// Set User ID
firebaseAnalytics.setUserId("user-123")

// Set User Property
firebaseAnalytics.setUserProperty("membership_level", "premium")

Server-Side Tracking (Measurement Protocol)

Node.js Implementation

const axios = require('axios')

const GA_MEASUREMENT_ID = 'G-XXXXXXXXXX'
const GA_API_SECRET = 'your-api-secret'

async function sendGA4Event(clientId, events) {
  const url = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`

  const payload = {
    client_id: clientId,
    events: events
  }

  try {
    const response = await axios.post(url, payload)
    return response.status === 204
  } catch (error) {
    console.error('GA4 tracking error:', error)
    return false
  }
}

// Track purchase
await sendGA4Event('client-123', [
  {
    name: 'purchase',
    params: {
      transaction_id: 'T12345',
      value: 149.99,
      currency: 'USD',
      tax: 12.00,
      shipping: 5.99,
      items: [
        {
          item_id: 'SKU-001',
          item_name: 'Premium Subscription',
          price: 149.99,
          quantity: 1
        }
      ]
    }
  }
])

// Track custom event
await sendGA4Event('client-123', [
  {
    name: 'server_action',
    params: {
      action_type: 'email_sent',
      email_template: 'welcome',
      user_id: 'user-123'
    }
  }
])

Python Implementation

import requests
import json

GA_MEASUREMENT_ID = 'G-XXXXXXXXXX'
GA_API_SECRET = 'your-api-secret'

def send_ga4_event(client_id, events):
    url = f'https://www.google-analytics.com/mp/collect?measurement_id={GA_MEASUREMENT_ID}&api_secret={GA_API_SECRET}'

    payload = {
        'client_id': client_id,
        'events': events
    }

    try:
        response = requests.post(url, json=payload)
        return response.status_code == 204
    except Exception as e:
        print(f'GA4 tracking error: {e}')
        return False

# Track purchase
send_ga4_event('client-123', [
    {
        'name': 'purchase',
        'params': {
            'transaction_id': 'T12345',
            'value': 149.99,
            'currency': 'USD',
            'items': [
                {
                    'item_id': 'SKU-001',
                    'item_name': 'Premium Subscription',
                    'price': 99.99,
                    'quantity': 1
                }
            ]
        }
    }
])

Validation Endpoint

Use the validation server to test events:

// Validation URL (doesn't send to GA4)
const validationUrl = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`

const response = await axios.post(validationUrl, payload)
console.log('Validation response:', response.data)

E-commerce Implementation

GA4 E-commerce Events

// View item list
gtag('event', 'view_item_list', {
  item_list_id: 'category_page',
  item_list_name: 'Premium Products',
  items: [
    {
      item_id: 'SKU-001',
      item_name: 'Premium Subscription',
      item_category: 'Subscriptions',
      price: 99.99,
      quantity: 1,
      index: 0
    }
  ]
})

// Select item
gtag('event', 'select_item', {
  item_list_id: 'category_page',
  items: [
    {
      item_id: 'SKU-001',
      item_name: 'Premium Subscription',
      price: 99.99
    }
  ]
})

// View item details
gtag('event', 'view_item', {
  currency: 'USD',
  value: 99.99,
  items: [
    {
      item_id: 'SKU-001',
      item_name: 'Premium Subscription',
      item_category: 'Subscriptions',
      price: 99.99,
      quantity: 1
    }
  ]
})

// Add to cart
gtag('event', 'add_to_cart', {
  currency: 'USD',
  value: 99.99,
  items: [
    {
      item_id: 'SKU-001',
      item_name: 'Premium Subscription',
      price: 99.99,
      quantity: 1
    }
  ]
})

// Begin checkout
gtag('event', 'begin_checkout', {
  currency: 'USD',
  value: 99.99,
  items: [
    {
      item_id: 'SKU-001',
      item_name: 'Premium Subscription',
      price: 99.99,
      quantity: 1
    }
  ]
})

// Add payment info
gtag('event', 'add_payment_info', {
  currency: 'USD',
  value: 99.99,
  payment_type: 'Credit Card',
  items: [
    {
      item_id: 'SKU-001',
      item_name: 'Premium Subscription',
      price: 99.99,
      quantity: 1
    }
  ]
})

// Purchase
gtag('event', 'purchase', {
  transaction_id: 'T12345',
  value: 149.99,
  currency: 'USD',
  tax: 12.00,
  shipping: 5.99,
  items: [
    {
      item_id: 'SKU-001',
      item_name: 'Premium Subscription',
      item_category: 'Subscriptions',
      price: 99.99,
      quantity: 1
    }
  ]
})

// Refund
gtag('event', 'refund', {
  transaction_id: 'T12345',
  value: 149.99,
  currency: 'USD',
  items: [
    {
      item_id: 'SKU-001',
      item_name: 'Premium Subscription',
      price: 99.99,
      quantity: 1
    }
  ]
})

Verification & Debugging

1. DebugView (Real-Time)

Enable DebugView:

Web:

gtag('config', 'G-XXXXXXXXXX', {
  'debug_mode': true
})

iOS:

# Enable debug mode
-FIRDebugEnabled

Android:

# Enable debug logging
adb shell setprop debug.firebase.analytics.app com.your.package

Access DebugView: GA4 Property → Admin → DebugView

2. Google Tag Assistant

  1. Install Tag Assistant Chrome Extension
  2. Navigate to your site
  3. Click extension icon
  4. Enable recording
  5. Interact with your site
  6. Review tag firing and data

3. Network Inspector

Check network requests:

gtag.js:

  • Script: https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX
  • Events: https://www.google-analytics.com/g/collect

GTM:

  • Container: https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX
  • Events: https://www.google-analytics.com/g/collect

4. Realtime Reports

View live data:

  1. Navigate to Reports → Realtime
  2. Trigger events on your site
  3. Verify events appear within 30 seconds
  4. Check event parameters are correct

Troubleshooting

Events Not Showing

Problem: Events not appearing in GA4

Solutions:

  1. Check Measurement ID is correct
  2. Verify data stream is active
  3. Ensure events sent with valid parameters
  4. Check browser console for errors
  5. Use DebugView to see event validation errors
  6. Verify ad blockers aren't blocking GA4

Problem: Consent not updating properly

Solutions:

  1. Ensure consent set BEFORE gtag loads
  2. Verify consent update fires on user action
  3. Check consent parameters match GA4 requirements
  4. Test in incognito to verify cookie behavior

User ID Not Persisting

Problem: User ID lost across sessions

Solutions:

  1. Call gtag('set', {'user_id': 'USER_ID'}) on every page
  2. Ensure User-ID set before pageview event
  3. Check cookie settings allow persistence
  4. Verify User-ID feature enabled in GA4

Duplicate Events

Problem: Same event tracked multiple times

Solutions:

  1. Check GTM tags not firing multiple times
  2. Verify only one GA4 configuration tag active
  3. Don't mix gtag.js and GTM on same page
  4. Check for duplicate script tags

Security Best Practices

1. Measurement ID Protection

// Good - Use environment variables
const MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID

// Acceptable - Client-side exposure is expected
// (Measurement IDs are designed to be public)
gtag('config', 'G-XXXXXXXXXX')

2. API Secret Protection

// Good - Server-side only
const GA_API_SECRET = process.env.GA_API_SECRET

// Bad - NEVER expose API secret client-side
// API secrets must remain server-side only

3. PII Protection

// Bad - Sending PII
gtag('event', 'signup', {
  email: 'user@example.com',
  name: 'John Doe'
})

// Good - Hash or omit PII
gtag('event', 'signup', {
  user_type: 'premium',
  signup_method: 'google'
})

4. Content Security Policy

<meta http-equiv="Content-Security-Policy"
  content="script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com 'unsafe-inline';
           connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com;
           img-src 'self' https://www.google-analytics.com https://www.googletagmanager.com data:;">

Validation Checklist

Before production:

  • Correct Measurement ID per environment
  • Consent Mode configured and tested
  • Enhanced Measurement settings verified
  • Custom events appear in DebugView
  • E-commerce events structured correctly
  • User-ID implementation tested
  • Cross-domain tracking works if needed
  • Server-side events validated
  • No PII sent in event parameters
  • Page load performance acceptable
  • GTM container published to production
  • All team members have GA4 access
  • Data retention settings configured
  • Filters configured (internal traffic, etc.)

Configuration Recommendations

These are the key decisions to lock down before going to production:

Consent Management: Use Google's Consent Mode v2 with your CMP (OneTrust, Cookiebot, or CookieYes). Set the default consent state to denied for analytics_storage and ad_storage in regions requiring consent (EU/EEA, UK). Consent mode still collects cookieless pings for behavioral modeling when consent is denied.

Enhanced Measurement: Enable all Enhanced Measurement events in GA4 Admin → Data Streams unless you have a specific reason not to. File download tracking captures .pdf, .docx, .xlsx, .csv, .zip by default — add custom extensions in the stream settings if needed. Disable scroll tracking only if you implement custom scroll depth (25/50/75/100%) via GTM.

E-commerce: Implement the full funnel (view_itemadd_to_cartbegin_checkoutpurchase) for meaningful funnel analysis. Track view_item_list and select_item only if product list click-through analysis matters for your merchandising team.

User Identification: Use User-ID when you have authenticated users — set it server-side at login with gtag('config', 'G-XXX', { user_id: 'USER_ID' }). Client-ID handles anonymous users automatically. Cross-device tracking requires User-ID; Client-ID is device-scoped.

Data Quality: Exclude internal traffic using GA4's built-in IP filter (Admin → Data Streams → Configure Tag Settings → Define Internal Traffic). Create a data filter in Admin → Data Settings → Data Filters set to "active" for the internal traffic definition.