Comprehensive guide to diagnosing and fixing analytics tracking issues in Craft CMS, covering Google Analytics, GTM, Meta Pixel, and common Craft-specific problems.
Common Causes
Environment Configuration Issues
- Scripts only loading in production
- Missing or incorrect environment variables
- Live Preview mode blocking scripts
- Dev mode configuration errors
JavaScript Errors
- Syntax errors in tracking code
- Missing dependencies
- Conflicting scripts
- AdBlockers
Timing Issues
- Scripts loading after events fire
- Async loading problems
- DOM not ready
- Page navigation before tracking completes
Craft-Specific Issues
- Twig template errors
- Conditional logic preventing load
- CSRF token issues
- Caching problems
Diagnostic Process
Step 1: Check Browser Console
Open browser DevTools (F12) and check Console tab:
// Check if tracking libraries are loaded
console.log('GA4 loaded:', typeof gtag !== 'undefined');
console.log('GTM loaded:', typeof google_tag_manager !== 'undefined');
console.log('Meta Pixel loaded:', typeof fbq !== 'undefined');
// Check dataLayer
console.log('dataLayer:', window.dataLayer);
// Check for errors
// Look for red error messages in console
Step 2: Check Network Tab
Look for tracking requests in Network tab:
- Google Analytics: Requests to
google-analytics.com/g/collect - GTM: Requests to
googletagmanager.com/gtm.js - Meta Pixel: Requests to
facebook.com/tr
Filter by:
analyticsgtmfacebook
Step 3: Verify Environment
Add debug output to your template:
{% if craft.app.config.general.devMode %}
<!-- Tracking Debug Info -->
<!-- Environment: {{ craft.app.config.general.environment }} -->
<!-- Dev Mode: {{ craft.app.config.general.devMode ? 'ON' : 'OFF' }} -->
<!-- Live Preview: {{ craft.app.request.isLivePreview ? 'YES' : 'NO' }} -->
<!-- Environment Variables -->
<!-- GA4 ID: {{ getenv('GOOGLE_ANALYTICS_ID') ? 'SET' : 'NOT SET' }} -->
<!-- GTM ID: {{ getenv('GTM_CONTAINER_ID') ? 'SET' : 'NOT SET' }} -->
<!-- Meta Pixel ID: {{ getenv('META_PIXEL_ID') ? 'SET' : 'NOT SET' }} -->
<!-- Conditional Checks -->
{% set shouldLoadTracking = craft.app.config.general.environment == 'production' and not craft.app.request.isLivePreview %}
<!-- Should Load Tracking: {{ shouldLoadTracking ? 'YES' : 'NO' }} -->
{% endif %}
Google Analytics 4 Issues
GA4 Not Loading
Diagnosis:
{# templates/_debug/ga4.twig #}
{% if craft.app.config.general.devMode %}
<script>
console.group('GA4 Debug');
console.log('gtag defined:', typeof gtag !== 'undefined');
console.log('dataLayer:', window.dataLayer);
console.log('dataLayer length:', window.dataLayer ? window.dataLayer.length : 0);
if (typeof gtag !== 'undefined') {
console.log('GA4 loaded successfully');
} else {
console.error('GA4 not loaded');
}
console.groupEnd();
</script>
{% endif %}
Common Fixes:
{# Fix 1: Check environment variable is set #}
{% set analyticsId = getenv('GOOGLE_ANALYTICS_ID') %}
{% if not analyticsId %}
<!-- ERROR: GOOGLE_ANALYTICS_ID not set in .env -->
{% endif %}
{# Fix 2: Ensure production environment #}
{% set environment = craft.app.config.general.environment %}
{% if environment != 'production' %}
<!-- Analytics disabled - not in production (current: {{ environment }}) -->
{% endif %}
{# Fix 3: Check Live Preview #}
{% if craft.app.request.isLivePreview %}
<!-- Analytics disabled - Live Preview active -->
{% endif %}
{# Fix 4: Proper script placement #}
{% if analyticsId and environment == 'production' and not craft.app.request.isLivePreview %}
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ analyticsId }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ analyticsId }}');
</script>
{% endif %}
GA4 Events Not Firing
Debug Events:
{% if craft.app.config.general.devMode %}
<script>
// Intercept gtag calls
var originalGtag = window.gtag || function() {};
window.gtag = function() {
console.log('GA4 Event:', arguments);
return originalGtag.apply(this, arguments);
};
</script>
{% endif %}
Common Event Issues:
{# Issue: Event fires before GA4 loads #}
{# BAD #}
<script>
gtag('event', 'page_view'); // gtag might not be defined yet
</script>
{# GOOD - Wait for gtag to be ready #}
<script>
window.addEventListener('load', function() {
if (typeof gtag !== 'undefined') {
gtag('event', 'page_view', {
'page_title': '{{ entry.title|e('js') }}',
'page_location': '{{ entry.url }}'
});
} else {
console.error('gtag not defined');
}
});
</script>
{# Or use dataLayer directly #}
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'page_view',
'page_title': '{{ entry.title|e('js') }}',
'page_location': '{{ entry.url }}'
});
</script>
Google Tag Manager Issues
GTM Container Not Loading
Diagnosis:
{% if craft.app.config.general.devMode %}
<script>
console.group('GTM Debug');
console.log('google_tag_manager defined:', typeof google_tag_manager !== 'undefined');
console.log('dataLayer:', window.dataLayer);
if (typeof google_tag_manager !== 'undefined') {
console.log('GTM loaded successfully');
console.log('Active containers:', Object.keys(google_tag_manager));
} else {
console.error('GTM not loaded');
}
console.groupEnd();
</script>
{% endif %}
Common Fixes:
{# Fix 1: Verify GTM snippet placement #}
{# GTM must be in <head> #}
<!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','{{ getenv('GTM_CONTAINER_ID') }}');</script>
<!-- End Google Tag Manager -->
</head>
<body>
<!-- GTM noscript immediately after <body> -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id={{ getenv('GTM_CONTAINER_ID') }}"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
{# Content #}
</body>
</html>
GTM Tags Not Firing
Debug dataLayer:
{% if craft.app.config.general.devMode %}
<script>
// Monitor all dataLayer pushes
window.dataLayer = window.dataLayer || [];
var originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
console.log('DataLayer Push:', arguments[0]);
// Check trigger conditions
if (arguments[0].event) {
console.log('Event Name:', arguments[0].event);
console.log('Event Data:', arguments[0]);
}
return originalPush.apply(window.dataLayer, arguments);
};
// Log current dataLayer state
console.log('Current dataLayer:', window.dataLayer);
</script>
{% endif %}
Common Issues:
{# Issue 1: Event name mismatch #}
{# BAD - Inconsistent event names #}
<script>
dataLayer.push({'event': 'form_submit'}); // snake_case
dataLayer.push({'event': 'formSubmit'}); // camelCase
</script>
{# GOOD - Consistent naming #}
<script>
dataLayer.push({'event': 'form_submit'}); // Always use same convention
</script>
{# Issue 2: Missing dataLayer initialization #}
{# BAD #}
<script>
dataLayer.push({'event': 'custom_event'}); // dataLayer might not exist
</script>
{# GOOD #}
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({'event': 'custom_event'});
</script>
{# Issue 3: Timing - pushing before GTM loads #}
{# GOOD - Initialize dataLayer BEFORE GTM script #}
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'page_type': '{{ entry.section.handle }}',
'content_group': '{{ entry.type.handle }}'
});
</script>
<!-- Then load GTM -->
<script>(function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXX');</script>
GTM Preview Mode Not Working
Enable GTM Debug:
{# Add debug parameter to GTM script in staging #}
{% set gtmId = getenv('GTM_CONTAINER_ID') %}
{% set environment = craft.app.config.general.environment %}
{% if environment == 'staging' %}
{% set gtmAuth = getenv('GTM_AUTH_KEY') %}
{% set gtmPreview = getenv('GTM_PREVIEW_KEY') %}
<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+'>m_auth={{ gtmAuth }}>m_preview={{ gtmPreview }}>m_cookies_win=x';f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','{{ gtmId }}');</script>
{% endif %}
Meta Pixel Issues
Meta Pixel Not Loading
Diagnosis:
{% if craft.app.config.general.devMode %}
<script>
console.group('Meta Pixel Debug');
console.log('fbq defined:', typeof fbq !== 'undefined');
console.log('_fbq defined:', typeof _fbq !== 'undefined');
if (typeof fbq !== 'undefined') {
console.log('Meta Pixel loaded successfully');
// Test pixel
fbq('track', 'PageView');
console.log('PageView event sent');
} else {
console.error('Meta Pixel not loaded');
}
console.groupEnd();
</script>
{% endif %}
Common Fixes:
{# Fix 1: Check pixel code placement #}
{% set pixelId = getenv('META_PIXEL_ID') %}
{% if pixelId and craft.app.config.general.environment == 'production' %}
<!-- Meta Pixel Code -->
<script>
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '{{ pixelId }}');
fbq('track', 'PageView');
</script>
<noscript>
<img height="1" width="1" style="display:none"
src="https://www.facebook.com/tr?id={{ pixelId }}&ev=PageView&noscript=1"/>
</noscript>
<!-- End Meta Pixel Code -->
{% endif %}
Meta Pixel Events Not Firing
Debug Events:
{% if craft.app.config.general.devMode %}
<script>
// Mock fbq for development
window.fbq = window.fbq || function() {
console.log('Meta Pixel Event:', arguments);
};
// Or intercept real fbq
var originalFbq = window.fbq || function() {};
window.fbq = function() {
console.log('Meta Pixel:', arguments);
return originalFbq.apply(this, arguments);
};
</script>
{% endif %}
AdBlocker Issues
Detect AdBlockers
<script>
// Detect if analytics scripts are blocked
setTimeout(function() {
if (typeof gtag === 'undefined') {
console.warn('GA4 blocked (likely by AdBlocker)');
}
if (typeof google_tag_manager === 'undefined') {
console.warn('GTM blocked (likely by AdBlocker)');
}
if (typeof fbq === 'undefined') {
console.warn('Meta Pixel blocked (likely by AdBlocker)');
}
}, 3000);
</script>
Test Without AdBlockers
- Disable AdBlocker extensions
- Test in Incognito/Private mode
- Use different browsers
- Check on mobile devices
Content Security Policy (CSP) Blocking
Check for CSP Errors
Look in browser console for CSP violations:
Refused to load the script 'https://www.googletagmanager.com/gtm.js' because it violates the following Content Security Policy directive...
Fix CSP Headers
// config/general.php
return [
'*' => [
'securityHeaders' => [
'Content-Security-Policy' => implode('; ', [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://connect.facebook.net",
"img-src 'self' data: https://www.google-analytics.com https://www.googletagmanager.com https://www.facebook.com",
"connect-src 'self' https://www.google-analytics.com https://analytics.google.com https://www.facebook.com",
"frame-src https://www.googletagmanager.com",
]),
],
],
];
Form Submission Tracking Issues
Problem: Form Submits Before Tracking
{# BAD - Form submits immediately #}
<form method="post" 'form_submit');">
{# ... #}
</form>
{# GOOD - Wait for tracking to complete #}
<form method="post" id="contact-form">
{{ csrfInput() }}
{{ actionInput('contact-form/send') }}
<button type="submit">Submit</button>
</form>
<script>
document.getElementById('contact-form').addEventListener('submit', function(e) {
e.preventDefault();
var form = this;
// Track event
gtag('event', 'form_submit', {
'event_callback': function() {
// Submit form after tracking completes
form.submit();
},
'event_timeout': 2000 // Timeout after 2 seconds
});
// Fallback submit after timeout
setTimeout(function() {
form.submit();
}, 2500);
});
</script>
Cache Issues
Template Caching Problems
{# Don't cache analytics scripts #}
{% cache unless craft.app.request.isLivePreview %}
{# Your cached content #}
{% endcache %}
{# Analytics scripts outside cache tags #}
{{ include('_analytics/google-analytics') }}
Clear Craft Caches
# Clear all caches
./craft clear-caches/all
# Clear template caches
./craft clear-caches/compiled-templates
# Clear data cache
./craft clear-caches/data
Duplicate Events
Problem: Multiple Script Loads
{# BAD - Including analytics multiple times #}
{{ include('_analytics/google-analytics') }}
{# ... later in template ... #}
{{ include('_analytics/google-analytics') }} {# Duplicate! #}
{# GOOD - Include once in layout #}
{# templates/_layouts/base.twig #}
<!DOCTYPE html>
<html>
<head>
{{ include('_analytics/google-analytics') }} {# Only once #}
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Prevent Duplicate Purchase Events
{# Use session flag to prevent duplicate tracking #}
{% set orderNumber = craft.app.request.getParam('number') %}
{% set orderTracked = craft.app.session.get('order_' ~ orderNumber ~ '_tracked') %}
{% if not orderTracked %}
<script>
gtag('event', 'purchase', {
'transaction_id': '{{ orderNumber }}',
'value': {{ order.total }}
});
</script>
{% do craft.app.session.set('order_' ~ orderNumber ~ '_tracked', true) %}
{% else %}
<!-- Order {{ orderNumber }} already tracked -->
{% endif %}
Testing Checklist
- Check browser console for errors
- Verify environment is set to production
- Confirm environment variables are set
- Check scripts are loading (Network tab)
- Test with AdBlocker disabled
- Verify CSP headers allow tracking domains
- Check Live Preview is not active
- Ensure scripts load before events fire
- Verify GTM container is published
- Test in multiple browsers
- Check mobile devices
- Use browser extensions (Tag Assistant, Pixel Helper)
- Verify in GA4 DebugView / GTM Preview / Meta Test Events
Next Steps
- LCP Optimization - Improve page load performance
- CLS Fixes - Fix layout shift issues
- Troubleshooting Overview - General Craft CMS troubleshooting