Skip to main content

Debug & Validation

Comprehensive guide to debugging and validating your analytics implementation.

Debugging Tools

GA4 DebugView

Real-time event stream for debugging:

  1. Enable Debug Mode:
// Via GTM: Set debug_mode parameter
// In GA4 Configuration tag:
// Configuration Settings → debug_mode = true

// Or via URL parameter:
// ?debug_mode=1

// Or via gtag:
gtag('config', 'G-XXXXXXXXXX', {
  debug_mode: true
});
  1. Access DebugView:

    • GA4 → AdminDebugView
    • Select your device from the dropdown
  2. What to Check:

    • Events appearing in correct order
    • Parameter values populated
    • User properties set correctly
    • Errors/warnings in event cards

GTM Preview Mode

Most powerful debugging tool for tag implementation:

  1. Start Preview:

    • Click Preview button in GTM
    • Enter your website URL
    • Opens Tag Assistant Connected
  2. Key Sections:

| Section | Purpose | |---------|---------| | Summary | All tags, fired vs. not fired | | Tags | Individual tag details | | Variables | All variable values | | Data Layer | Data layer contents at each event | | Errors | Any JavaScript errors | | Consent | Consent state (if enabled) |

  1. Event Analysis:
    • Click each event in timeline
    • See which tags fired
    • Check variable values at that moment
    • Verify trigger conditions

Browser DevTools

Network Tab

// Filter for analytics requests:
// - collect? (GA4 Measurement Protocol)
// - google-analytics
// - facebook.com/tr
// - bat.bing.com

Key things to check:

  • Request status (200 = success)
  • Request payload (parameters sent)
  • Request timing

Console Tab

// Enable verbose logging for GA4
gtag('config', 'G-XXXXXXXXXX', {
  'debug_mode': true
});

// Monitor data layer
console.table(dataLayer);

// Watch for changes
(function() {
  var oldPush = dataLayer.push;
  dataLayer.push = function() {
    console.log('dataLayer.push:', arguments[0]);
    return oldPush.apply(this, arguments);
  };
})();

Google Tag Assistant

Browser extension for real-time validation:

  1. Install from Chrome Web Store
  2. Enable on your website
  3. View all Google tags detected
  4. Check for issues and warnings

Validation Workflows

New Implementation Checklist

## Pre-Launch Validation

### Basic Functionality
- [ ] Page view fires on all pages
- [ ] Events fire on expected interactions
- [ ] Conversions track correctly
- [ ] Cross-domain tracking works (if applicable)

### Data Quality
- [ ] No PII in parameters
- [ ] Event names follow conventions
- [ ] Parameter values populated
- [ ] No duplicate events

### Consent & Privacy
- [ ] Consent mode configured
- [ ] Tags blocked without consent
- [ ] Tags fire after consent
- [ ] Opt-out works correctly

### Performance
- [ ] Page load not impacted > 100ms
- [ ] No console errors
- [ ] Tags load asynchronously

Event Validation Matrix

| Event | Trigger | Required Params | Test Case | |-------|---------|-----------------|-----------| | page_view | All pages | page_location, page_title | Navigate to any page | | view_item | Product pages | item_id, item_name, price | View a product | | add_to_cart | Add button click | item_id, quantity, value | Add item to cart | | purchase | Thank you page | transaction_id, value, items | Complete purchase |

Common Issues

Tags Not Firing

| Symptom | Diagnosis | Solution | |---------|-----------|----------| | Tag shows "Not Fired" | Check trigger conditions | Fix trigger logic | | Tag fires but no data | Check variables | Fix variable configuration | | Intermittent firing | Race condition | Adjust trigger timing | | No tags at all | GTM not loading | Check GTM snippet |

Data Layer Issues

// Problem: Data layer undefined
// Solution: Initialize before use
window.dataLayer = window.dataLayer || [];

// Problem: Event not triggering tag
// Check event name matches trigger
dataLayer.push({
  'event': 'purchase'  // Must match GTM trigger exactly
});

// Problem: Variables undefined
// Push data before event
dataLayer.push({
  'transaction_id': 'T123',
  'value': 99.99,
  'event': 'purchase'  // Event comes LAST
});

Duplicate Events

Causes:

  • Multiple tag instances
  • Tag firing on multiple triggers
  • SPA route changes
  • Misconfigured triggers

Diagnosis:

// Count events in data layer
dataLayer.filter(x => x.event === 'purchase').length
// Should equal actual purchases

// Check in GA4 DebugView
// Look for duplicate event_ids

Solutions:

  • Add de-duplication logic
  • Use transaction_id for purchase events
  • Configure "Once per page" triggers
  • Check for multiple GTM containers

Cross-Domain Issues

Symptoms:

  • New sessions when crossing domains
  • Self-referrals in reports
  • Attribution broken across domains

Diagnosis:

// Check linker parameter in URL
// Should see _gl parameter when crossing domains
// https://checkout.example.com?_gl=1*abcdef...

// Check cookie domain
document.cookie // Look for _ga cookie
// Should be on parent domain: .example.com

Solutions:

  • Configure domains in GA4 Admin
  • Check linker configuration in GTM
  • Verify redirect handling
  • Test full user journey

Testing Frameworks

E2E Analytics Testing

Using Playwright or Cypress:

// Playwright example
test('purchase tracking', async ({ page }) => {
  // Intercept analytics requests
  const analyticsRequests = [];
  page.on('request', request => {
    if (request.url().includes('google-analytics')) {
      analyticsRequests.push(request);
    }
  });

  // Complete purchase flow
  await page.goto('/product/123');
  await page.click('[data-add-to-cart]');
  await page.goto('/checkout');
  await page.fill('#email', '[email protected]');
  await page.click('[data-purchase]');

  // Verify purchase event
  const purchaseRequest = analyticsRequests.find(r =>
    r.url().includes('en=purchase')
  );
  expect(purchaseRequest).toBeDefined();

  // Verify parameters
  const params = new URLSearchParams(purchaseRequest.url());
  expect(params.get('ep.transaction_id')).toBeTruthy();
  expect(parseFloat(params.get('ep.value'))).toBeGreaterThan(0);
});

Data Layer Validation

// Validate data layer structure
function validatePurchaseEvent(event) {
  const errors = [];

  // Required fields
  if (!event.transaction_id) errors.push('Missing transaction_id');
  if (!event.value) errors.push('Missing value');
  if (!event.currency) errors.push('Missing currency');
  if (!event.items || !event.items.length) errors.push('Missing items');

  // Validate items
  event.items?.forEach((item, index) => {
    if (!item.item_id) errors.push(`Item ${index}: missing item_id`);
    if (!item.item_name) errors.push(`Item ${index}: missing item_name`);
    if (typeof item.price !== 'number') errors.push(`Item ${index}: invalid price`);
    if (typeof item.quantity !== 'number') errors.push(`Item ${index}: invalid quantity`);
  });

  return errors;
}

// Use in testing
dataLayer.push = (function(originalPush) {
  return function(data) {
    if (data.event === 'purchase') {
      const errors = validatePurchaseEvent(data);
      if (errors.length) {
        console.error('Purchase event validation failed:', errors);
      }
    }
    return originalPush.apply(this, arguments);
  };
})(dataLayer.push);

Monitoring & Alerts

GA4 Anomaly Detection

GA4 automatically detects:

  • Unusual traffic spikes
  • Conversion drops
  • Engagement changes

Set up custom alerts:

  1. AdminCustom Alerts (coming soon)
  2. Use BigQuery for custom monitoring

BigQuery Monitoring

-- Daily conversion tracking alert
DECLARE expected_min INT64 DEFAULT 50;

WITH daily_conversions AS (
  SELECT
    DATE(TIMESTAMP_MICROS(event_timestamp)) as date,
    COUNT(*) as conversions
  FROM `project.analytics_XXXXXX.events_*`
  WHERE event_name = 'purchase'
    AND _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY))
  GROUP BY date
)
SELECT
  date,
  conversions,
  CASE
    WHEN conversions < expected_min THEN 'ALERT: Low conversions'
    ELSE 'OK'
  END as status
FROM daily_conversions;

Uptime Monitoring

Monitor critical endpoints:

| Endpoint | Check | Alert Threshold | |----------|-------|-----------------| | GTM container | Response 200 | 5 seconds | | Server-side endpoint | Health check | 2 seconds | | GA4 collection | Sample request | 5 seconds |

Debugging Cheat Sheet

Quick Diagnostics

// 1. Is GTM loaded?
google_tag_manager  // Should be defined

// 2. What's in data layer?
console.table(dataLayer.filter(x => typeof x === 'object'))

// 3. Is GA4 active?
window.google_tag_data  // Should contain gtag data

// 4. Check consent state
dataLayer.filter(x => x[0] === 'consent')

// 5. Are cookies set?
document.cookie.split(';').filter(c => c.includes('_ga'))

URL Debug Parameters

| Parameter | Purpose | |-----------|---------| | ?debug_mode=1 | Enable GA4 DebugView | | ?gtm_debug=x | Force GTM preview | | ?_gl=... | Cross-domain linker |


Previous: Attribution Modeling Next: SaaS Analytics