Debugging GA4 | Blue Frog Docs

Debugging GA4

Step-by-step guide to diagnosing and fixing Google Analytics 4 tracking issues, from missing data to incorrect event parameters.

Debugging GA4

This guide walks you through systematic approaches to identify and resolve common GA4 tracking problems. Whether you're missing data, seeing incorrect values, or experiencing tracking failures, follow these steps to diagnose and fix the issue.

Essential Debugging Tools

1. GA4 DebugView

The most powerful tool for real-time event debugging:

  1. Enable debug mode in your browser:

  2. Access DebugView:

    • Go to GA4 Admin → Property → DebugView
    • Events appear in real-time with 30+ minute history
  3. What to look for:

    • Events appearing in correct sequence
    • Event parameters with expected values
    • User properties populating correctly
    • No duplicate events firing
// Force debug mode via dataLayer
gtag('config', 'G-XXXXXXXXXX', {
  'debug_mode': true
});

2. Browser Developer Tools

Console Tab:

// Check if gtag is loaded
console.log(typeof gtag); // Should return "function"

// Check dataLayer contents
console.log(window.dataLayer);

// Monitor gtag calls
const originalGtag = window.gtag;
window.gtag = function() {
  console.log('gtag called:', arguments);
  originalGtag.apply(this, arguments);
};

Network Tab:

  • Filter by collect or google-analytics.com
  • Check for requests to google-analytics.com/g/collect
  • Verify status codes are 200/204
  • Inspect payload parameters

3. Google Tag Assistant

Chrome extension for visual debugging:

  1. Install Tag Assistant
  2. Click the extension icon and enable
  3. Navigate your site
  4. Review tag firing status and errors

4. Real-Time Reports

For quick validation (not debugging details):

  1. GA4 → Reports → Real-time
  2. Verify page views, events, and users
  3. Note: 30-second delay; use DebugView for instant feedback

Common Problems and Solutions

Problem: Events Not Appearing

Symptoms:

  • No data in DebugView
  • Missing events in reports
  • Real-time shows no activity

Diagnostic Steps:

  1. Check gtag is loaded:
// In browser console
if (typeof gtag === 'undefined') {
  console.error('gtag not loaded - check script installation');
} else {
  console.log('gtag is loaded');
}
  1. Verify Measurement ID:
// Check if GA4 config exists in dataLayer
window.dataLayer.filter(e =>
  e[0] === 'config' && e[1]?.startsWith('G-')
);
  1. Check for JavaScript errors:

    • Open Console tab
    • Look for red error messages
    • Errors before gtag script can prevent loading
  2. Verify network requests:

    • Open Network tab
    • Filter by collect
    • If no requests, gtag isn't firing

Solutions:

<!-- Ensure gtag script is in <head> -->
<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>

Problem: Duplicate Events

Symptoms:

  • Events fire 2x or more per action
  • Inflated event counts
  • Sessions seem too short

Diagnostic Steps:

  1. Check DebugView for repeated events
  2. Search code for multiple gtag calls:
# Search your codebase
grep -r "gtag\('event'" .
  1. Check GTM for duplicate tags
  2. Verify both gtag and GTM aren't firing same event

Solutions:

// Debounce rapid event firing
let lastEventTime = 0;
function trackEvent(eventName, params) {
  const now = Date.now();
  if (now - lastEventTime < 500) return; // Skip if < 500ms
  lastEventTime = now;
  gtag('event', eventName, params);
}

// Track clicks with debounce
document.querySelector('.cta-button').addEventListener('click', () => {
  trackEvent('cta_click', { button_text: 'Sign Up' });
});

Problem: Missing Event Parameters

Symptoms:

  • Event appears but parameters are (not set)
  • Values showing as undefined
  • Numbers appearing as strings

Diagnostic Steps:

  1. Check DebugView parameter values
  2. Inspect dataLayer for correct structure:
// Correct structure
gtag('event', 'purchase', {
  transaction_id: 'T12345',
  value: 99.99,  // Number, not string
  currency: 'USD',
  items: [{
    item_id: 'SKU123',
    item_name: 'Product Name',
    price: 99.99
  }]
});
  1. Verify data types:
// Common mistake: string instead of number
value: '99.99'  // Wrong
value: 99.99    // Correct

// Common mistake: missing items array
items: { item_id: 'SKU' }  // Wrong
items: [{ item_id: 'SKU' }]  // Correct

Solutions:

// Validate parameters before sending
function trackPurchase(order) {
  const params = {
    transaction_id: String(order.id),
    value: parseFloat(order.total) || 0,
    currency: order.currency || 'USD',
    items: order.items.map(item => ({
      item_id: String(item.sku),
      item_name: String(item.name),
      price: parseFloat(item.price) || 0,
      quantity: parseInt(item.quantity) || 1
    }))
  };

  console.log('Sending purchase:', params);
  gtag('event', 'purchase', params);
}

Problem: Cross-Domain Tracking Not Working

Symptoms:

  • Users appear as new on each domain
  • Session breaks between domains
  • Referral traffic from own domains

Diagnostic Steps:

  1. Check linker configuration:
gtag('config', 'G-XXXXXXXXXX', {
  'linker': {
    'domains': ['example.com', 'checkout.example.com']
  }
});
  1. Verify links include linker parameter:

    • Click a cross-domain link
    • Check URL includes _gl= parameter
  2. Test with DebugView:

    • Enable debug mode
    • Navigate between domains
    • Verify same client_id

Solutions:

// Configure cross-domain tracking
gtag('config', 'G-XXXXXXXXXX', {
  'linker': {
    'domains': ['domain1.com', 'domain2.com', 'checkout.domain.com'],
    'accept_incoming': true
  }
});

// For form submissions
gtag('config', 'G-XXXXXXXXXX', {
  'linker': {
    'domains': ['example.com'],
    'decorate_forms': true
  }
});

Symptoms:

  • No data after implementing consent banner
  • Events only fire after consent
  • Missing data from EU users

Diagnostic Steps:

  1. Check consent state:
// View current consent state
gtag('get', 'G-XXXXXXXXXX', 'consent', (value) => {
  console.log('Consent state:', value);
});
  1. Verify consent initialization:
// Should appear BEFORE gtag config
gtag('consent', 'default', {
  'ad_storage': 'denied',
  'analytics_storage': 'denied'
});

Solutions:

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

// After user grants consent
gtag('consent', 'update', {
  'ad_storage': 'granted',
  'analytics_storage': 'granted'
});

Problem: Data Not in Reports (but in DebugView)

Symptoms:

  • Events appear in DebugView
  • No data in standard reports
  • Long delay before data appears

Causes and Solutions:

  1. Processing delay: GA4 reports have 24-48 hour delay

    • Use Real-time reports for recent data
    • Wait before checking standard reports
  2. Filters applied: Check report filters

    • Remove date filters
    • Check comparison settings
    • Verify no segments are applied
  3. Custom dimensions not registered:

    • Admin → Custom definitions
    • Register parameters as custom dimensions
    • Takes 24-48 hours to populate
  4. Threshold applied: Small datasets hidden

    • Increase date range
    • Reduce granularity
    • Check for "thresholding applied" notice

Debugging Specific Event Types

Page Views

// Verify page_view is firing
gtag('config', 'G-XXXXXXXXXX', {
  'send_page_view': true,
  'page_title': document.title,
  'page_location': window.location.href
});

// For SPAs, manually track page views
gtag('event', 'page_view', {
  page_title: 'New Page Title',
  page_location: '/new-page-url'
});

E-commerce Events

// Debug e-commerce tracking
console.log('Checking e-commerce setup...');

// View item
gtag('event', 'view_item', {
  currency: 'USD',
  value: 29.99,
  items: [{
    item_id: 'SKU123',
    item_name: 'Test Product',
    price: 29.99
  }]
});

// Add to cart
gtag('event', 'add_to_cart', {
  currency: 'USD',
  value: 29.99,
  items: [{
    item_id: 'SKU123',
    item_name: 'Test Product',
    price: 29.99,
    quantity: 1
  }]
});

// Purchase
gtag('event', 'purchase', {
  transaction_id: 'TEST_' + Date.now(),
  value: 29.99,
  currency: 'USD',
  items: [{
    item_id: 'SKU123',
    item_name: 'Test Product',
    price: 29.99,
    quantity: 1
  }]
});

Form Submissions

// Track form submission
document.querySelector('form').addEventListener('submit', (e) => {
  // Log for debugging
  console.log('Form submitted, tracking event...');

  gtag('event', 'generate_lead', {
    currency: 'USD',
    value: 50.00  // Estimated lead value
  });
});

Network Request Analysis

Understanding the Collect Request

When gtag fires, it sends data to:

https://www.google-analytics.com/g/collect?v=2&tid=G-XXXXXXXXXX&...

Key Parameters:

  • tid: Measurement ID
  • en: Event name
  • ep.*: Event parameters
  • up.*: User properties
  • _dbg: Debug mode enabled

Decoding the request:

// In Network tab, find a collect request
// Copy the URL and decode it:
const url = new URL('https://www.google-analytics.com/g/collect?...');
url.searchParams.forEach((value, key) => {
  console.log(`${key}: ${decodeURIComponent(value)}`);
});

Debugging Checklist

Use this checklist when troubleshooting:

  • gtag script is in <head> section
  • Measurement ID is correct (G-XXXXXXXXXX format)
  • No JavaScript errors before gtag loads
  • Events appear in DebugView
  • Network requests succeed (200/204 status)
  • Parameters have correct data types
  • No duplicate events firing
  • Cross-domain linking configured (if applicable)
  • Consent mode properly implemented (if applicable)
  • Custom dimensions registered in Admin

Getting More Help

// SYS.FOOTER