Google Analytics 4 Setup & Implementation | OpsBlu Docs

Google Analytics 4 Setup & Implementation

How to implement GA4 with gtag.js or Google Tag Manager. Covers Measurement ID configuration, enhanced measurement, custom events, e-commerce tracking.

Google Analytics 4 (GA4) is an event-based analytics platform that replaced Universal Analytics. Unlike its predecessor, GA4 uses a flexible event model where everything is an event -- page views, clicks, scrolls, purchases, and custom actions. This flexibility is powerful but dangerous: without deliberate configuration, you end up with a property full of automatically collected noise and none of the business-specific events that actually matter. Enhanced Measurement captures some useful events automatically, but it also creates conflicts with custom tracking if you are not careful about what to enable and what to disable.

Why Proper Implementation Matters

The Enhanced Measurement Trap

GA4 enables "Enhanced Measurement" by default, which automatically tracks:

  • Page views
  • Scrolls (90% depth)
  • Outbound clicks
  • Site search
  • Video engagement (YouTube embeds)
  • File downloads

The problem: Enhanced Measurement events cannot carry custom parameters. If you need to track scroll depth at 25/50/75/100% (not just 90%), or need to capture the search query with product results count, you must disable the Enhanced Measurement version and implement your own. Running both creates duplicate events with different parameter structures.

Event Naming is Permanent

GA4 event names and parameter names are case-sensitive and cannot be renamed retroactively:

  • page_view and Page_View are different events in GA4
  • Once custom dimensions are created from parameters, renaming the parameter creates a new dimension with zero historical data
  • Google's recommended event names (like purchase, add_to_cart, generate_lead) must match exactly for e-commerce reports to work

Data Retention is Limited

GA4 standard properties retain detailed event data for 2 months (14 months with Google Analytics 360). After that, only aggregated reports remain. This means:

  • If your tracking is broken for the first 2 months, that detailed data is gone permanently
  • BigQuery export is the only way to preserve raw event-level data indefinitely
  • Export must be set up before data is needed -- it is not retroactive

Pre-Implementation Planning

Access and Permissions

Google Analytics Account:

  1. Sign in at analytics.google.com
  2. Verify you have Editor or Admin access to the property
  3. Admin access is required for data stream creation, BigQuery linking, and user management

Google Tag Manager (if using GTM):

  1. Verify Publish access on the GTM container
  2. Confirm the container is installed on all pages
  3. Coordinate with other GTM users to avoid tag conflicts

Required IDs:

ID Where to Find Format
Measurement ID GA4 > Admin > Data Streams > Web G-XXXXXXXXXX
Stream ID GA4 > Admin > Data Streams > Web Numeric
API Secret GA4 > Admin > Data Streams > Measurement Protocol Alphanumeric string
GTM Container ID GTM > Container overview GTM-XXXXXXX

Data Stream Configuration

  1. In GA4, go to Admin > Data Streams
  2. Click Add Stream > Web
  3. Enter your website URL and stream name
  4. Note the Measurement ID (G-XXXXXXXXXX)
  5. Configure Enhanced Measurement:
Feature Enable? When to Disable
Page views Yes (always) Never -- always use this
Scrolls Depends Disable if implementing custom scroll tracking
Outbound clicks Yes Disable if tracking outbound clicks with custom parameters
Site search Yes Disable if implementing custom search tracking with more parameters
Video engagement Yes Only works for YouTube embeds; disable if tracking custom video players
File downloads Yes Disable if you need custom file download parameters

Event Architecture

Design your event structure before implementation:

Automatically Collected Events (cannot disable):

  • first_visit, session_start, user_engagement
  • page_view (if Enhanced Measurement enabled)

Recommended Events (use Google's exact names for built-in reports):

  • E-commerce: view_item, add_to_cart, begin_checkout, purchase
  • Lead gen: generate_lead, sign_up
  • Content: search, share

Custom Events (your business-specific events):

  • Name with snake_case, max 40 characters
  • Up to 500 distinct event names per property
  • Up to 25 custom parameters per event

Implementation Walkthrough

Step 1: Install GA4 Tag

Option A: Google Tag Manager (Recommended)

  1. In GTM, create a new tag:

    • Tag Type: Google Tag
    • Tag ID: Your Measurement ID (G-XXXXXXXXXX)
    • Trigger: All Pages
  2. Configure consent defaults (if using consent management):

// In a Custom HTML tag that fires BEFORE the Google Tag, on All Pages
gtag('consent', 'default', {
  'analytics_storage': 'denied',
  'ad_storage': 'denied',
  'ad_user_data': 'denied',
  'ad_personalization': 'denied',
  'region': ['EU', 'EEA', 'GB']  // Apply restrictions to EU users only
});

Option B: gtag.js Direct Installation

<!-- 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());

  // Consent defaults (if applicable)
  gtag('consent', 'default', {
    'analytics_storage': 'granted',
    'ad_storage': 'denied'
  });

  gtag('config', 'G-XXXXXXXXXX', {
    'send_page_view': true,
    'cookie_domain': 'auto',
    'cookie_flags': 'SameSite=None;Secure'
  });
</script>

Step 2: Configure Custom Events

E-commerce Events (must use exact Google names):

// View item (product page)
gtag('event', 'view_item', {
  currency: 'USD',
  value: 49.99,
  items: [{
    item_id: 'SKU-12345',
    item_name: 'Premium Widget',
    item_brand: 'Acme',
    item_category: 'Widgets',
    item_category2: 'Premium',
    price: 49.99,
    quantity: 1
  }]
});

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

// Begin checkout
gtag('event', 'begin_checkout', {
  currency: 'USD',
  value: 129.98,
  coupon: 'SAVE10',
  items: [{
    item_id: 'SKU-12345',
    item_name: 'Premium Widget',
    price: 49.99,
    quantity: 1
  }, {
    item_id: 'SKU-67890',
    item_name: 'Deluxe Gadget',
    price: 79.99,
    quantity: 1
  }]
});

// Purchase (critical event)
gtag('event', 'purchase', {
  transaction_id: 'ORD-2024-12345',
  value: 129.98,
  tax: 10.40,
  shipping: 5.99,
  currency: 'USD',
  coupon: 'SAVE10',
  items: [{
    item_id: 'SKU-12345',
    item_name: 'Premium Widget',
    affiliation: 'Online Store',
    item_brand: 'Acme',
    item_category: 'Widgets',
    price: 49.99,
    quantity: 1
  }, {
    item_id: 'SKU-67890',
    item_name: 'Deluxe Gadget',
    affiliation: 'Online Store',
    item_brand: 'Acme',
    item_category: 'Gadgets',
    price: 79.99,
    quantity: 1
  }]
});

Lead Generation Events:

// Form submission
gtag('event', 'generate_lead', {
  value: 50.00,
  currency: 'USD',
  form_name: 'Contact Us',
  form_id: 'contact-main'
});

// Sign up
gtag('event', 'sign_up', {
  method: 'Google'  // or 'Email', 'Apple', etc.
});

Custom Business Events:

// Feature usage (SaaS)
gtag('event', 'feature_used', {
  feature_name: 'export_report',
  feature_category: 'analytics',
  export_format: 'csv'
});

// Content engagement
gtag('event', 'article_read', {
  article_id: 'ART-123',
  article_category: 'tutorials',
  read_percentage: 100,
  word_count: 2500
});

Step 3: GTM Data Layer Implementation

For GTM-based deployments, push structured data to the data layer:

// On all pages
window.dataLayer = window.dataLayer || [];
dataLayer.push({
  'event': 'page_data_ready',
  'page_type': 'product',           // home, category, product, cart, checkout, confirmation
  'user_logged_in': true,
  'user_id': 'USR-12345',
  'user_type': 'returning'
});

// On product pages
dataLayer.push({
  'event': 'view_item',
  'ecommerce': {
    'currency': 'USD',
    'value': 49.99,
    'items': [{
      'item_id': 'SKU-12345',
      'item_name': 'Premium Widget',
      'item_brand': 'Acme',
      'item_category': 'Widgets',
      'price': 49.99,
      'quantity': 1
    }]
  }
});

// On purchase confirmation
dataLayer.push({
  'event': 'purchase',
  'ecommerce': {
    'transaction_id': 'ORD-2024-12345',
    'value': 129.98,
    'tax': 10.40,
    'shipping': 5.99,
    'currency': 'USD',
    'items': [/* item array */]
  }
});

GTM Tag Configuration:

  • Create a GA4 Event tag for each custom event
  • Use the data layer variable for event parameters
  • Set triggers based on data layer event values

Step 4: Configure Custom Dimensions and Metrics

In GA4 Admin, register custom parameters as dimensions or metrics:

  1. Go to Admin > Custom Definitions
  2. Click Create Custom Dimension:
Parameter Scope Description
page_type Event Type of page (product, category, home)
user_type User New vs. returning customer
form_name Event Name of submitted form
feature_name Event Product feature used
article_category Event Content category
  1. Click Create Custom Metric:
Parameter Scope Unit Description
word_count Event Standard Article word count
read_percentage Event Standard Article read depth

Limits:

  • 50 custom dimensions (event scope)
  • 25 custom dimensions (user scope)
  • 50 custom metrics
  • Plan allocations carefully; deleting a custom definition loses historical data

Step 5: Set Up Measurement Protocol (Server-Side)

For backend events (subscription renewals, CRM actions, offline conversions):

const MEASUREMENT_ID = 'G-XXXXXXXXXX';
const API_SECRET = 'your_api_secret';

async function sendServerEvent(clientId, userId, eventName, params) {
  const payload = {
    client_id: clientId,       // Required: GA4 client ID (_ga cookie value)
    user_id: userId,           // Optional: your internal user ID
    events: [{
      name: eventName,
      params: {
        ...params,
        engagement_time_msec: 1  // Required for events to show in reports
      }
    }]
  };

  const url = `https://www.google-analytics.com/mp/collect?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`;

  const response = await fetch(url, {
    method: 'POST',
    body: JSON.stringify(payload)
  });

  // Measurement Protocol returns 204 No Content on success
  return response.status === 204;
}

// Example: Track subscription renewal
await sendServerEvent(
  'GA1.1.123456789.1234567890',  // client_id from _ga cookie
  'USR-12345',
  'subscription_renewed',
  {
    value: 99.00,
    currency: 'USD',
    plan_type: 'pro',
    billing_period: 'annual'
  }
);

Validation endpoint (use during testing):

https://www.google-analytics.com/debug/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=YOUR_SECRET

This returns validation messages instead of actually recording events.

Step 6: Enable BigQuery Export

For raw event-level data preservation (highly recommended):

  1. In GA4, go to Admin > BigQuery Links
  2. Click Link
  3. Select your Google Cloud project
  4. Choose export frequency:
    • Daily: Batch export of previous day's data
    • Streaming: Near-real-time export (additional cost)
  5. Select data to export: Events, Users, or both
  6. BigQuery dataset is created automatically in format: analytics_PROPERTY_ID

Cost consideration: BigQuery storage is cheap (~$0.02/GB/month), but query costs can add up. Partition tables by date and use WHERE _TABLE_SUFFIX filters.

Verification and QA

DebugView

  1. In GA4, go to Admin > DebugView
  2. Enable debug mode on your browser:
    • GTM: Enable Preview mode
    • gtag.js: Add 'debug_mode': true to config
    • Chrome extension: Install "Google Analytics Debugger"
  3. Navigate through your site and verify:
    • Events appear in real-time
    • Event parameters are populated
    • User properties are set
    • E-commerce items array is structured correctly

Real-Time Report

  1. Go to Reports > Realtime
  2. Verify:
    • Active users count increases when you visit
    • Events appear with correct names
    • Conversions fire on appropriate pages

GTM Preview Mode

  1. Enable Preview in GTM
  2. Navigate through conversion funnel
  3. For each page/action, check:
    • Google Tag fires on All Pages
    • Event tags fire on correct triggers
    • Variable values are populated correctly
    • No duplicate tags

Common Issues

Issue Cause Fix
Events not in reports Wrong Measurement ID Check G-XXXXXXXXXX in tag configuration
Duplicate page_views Enhanced Measurement + custom tracking Disable Enhanced Measurement page views if using custom
E-commerce reports empty Wrong event names or missing items array Use exact Google event names: purchase, add_to_cart
Custom dimensions show "(not set)" Parameter name mismatch Verify parameter name in tag matches custom dimension registration
Cross-domain identity breaks Missing linker configuration Set up cross-domain measurement in data stream settings
Consent blocking all tracking Default consent too restrictive Verify consent defaults only apply to regulated regions
Measurement Protocol events missing Missing engagement_time_msec Add engagement_time_msec: 1 to all server-side events

Deployment Artifacts

  • Measurement ID and API Secret: Central reference for all implementations
  • Tracking plan: Events, parameters, and expected values
  • GTM container documentation: Tags, triggers, variables, and version history
  • Custom dimensions/metrics registry: Allocated dimensions with descriptions and owners
  • Data layer specification: Expected data layer structure per page type
  • BigQuery export configuration: Project, dataset, export frequency
  • Consent mode configuration: Default settings, CMP integration, regional rules
  • Environment matrix: Measurement IDs, GTM containers, and debug settings per environment

Linked Runbooks

Change Log and Owners

  • Track who publishes GTM containers and who manages GA4 Admin settings
  • Document custom dimension/metric allocations with owners
  • Record Enhanced Measurement configuration changes
  • Maintain BigQuery export status and query cost monitoring
  • Review monthly: event volume, data quality, custom dimension utilization