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:
Enable debug mode in your browser:
- Install Google Analytics Debugger extension
- Or add
?debug_mode=trueto any URL - Or set debug mode in GTM
Access DebugView:
- Go to GA4 Admin → Property → DebugView
- Events appear in real-time with 30+ minute history
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
collectorgoogle-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:
- Install Tag Assistant
- Click the extension icon and enable
- Navigate your site
- Review tag firing status and errors
4. Real-Time Reports
For quick validation (not debugging details):
- GA4 → Reports → Real-time
- Verify page views, events, and users
- 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:
- 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');
}
- Verify Measurement ID:
// Check if GA4 config exists in dataLayer
window.dataLayer.filter(e =>
e[0] === 'config' && e[1]?.startsWith('G-')
);
Check for JavaScript errors:
- Open Console tab
- Look for red error messages
- Errors before gtag script can prevent loading
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:
- Check DebugView for repeated events
- Search code for multiple gtag calls:
# Search your codebase
grep -r "gtag\('event'" .
- Check GTM for duplicate tags
- 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:
- Check DebugView parameter values
- 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
}]
});
- 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:
Diagnostic Steps:
- Check linker configuration:
gtag('config', 'G-XXXXXXXXXX', {
'linker': {
'domains': ['example.com', 'checkout.example.com']
}
});
Verify links include linker parameter:
- Click a cross-domain link
- Check URL includes
_gl=parameter
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
}
});
Problem: Consent Mode Blocking Data
Symptoms:
- No data after implementing consent banner
- Events only fire after consent
- Missing data from EU users
Diagnostic Steps:
- Check consent state:
// View current consent state
gtag('get', 'G-XXXXXXXXXX', 'consent', (value) => {
console.log('Consent state:', value);
});
- 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:
Processing delay: GA4 reports have 24-48 hour delay
- Use Real-time reports for recent data
- Wait before checking standard reports
Filters applied: Check report filters
- Remove date filters
- Check comparison settings
- Verify no segments are applied
Custom dimensions not registered:
- Admin → Custom definitions
- Register parameters as custom dimensions
- Takes 24-48 hours to populate
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 IDen: Event nameep.*: Event parametersup.*: 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
- GA4 Measurement Protocol - Server-side debugging
- GA4 Developer Documentation
- Google Analytics Help Community
- Debugging GTM - If using Google Tag Manager
Related Resources
- Events Not Firing - General tracking issues
- Data Layer Setup - Proper data layer structure
- Cross-Domain Tracking - Multi-domain setup