How to Fix Craft CMS Tracking Events Not Firing | OpsBlu Docs

How to Fix Craft CMS Tracking Events Not Firing

Fix GA4, GTM, and pixel events not firing on Craft CMS — Twig template head inclusion, environment config toggles, and static page cache debugging

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:

  • analytics
  • gtm
  • facebook

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+'&gtm_auth={{ gtmAuth }}&gtm_preview={{ gtmPreview }}&gtm_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

  1. Disable AdBlocker extensions
  2. Test in Incognito/Private mode
  3. Use different browsers
  4. 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

Resources