Debugging GTM | Blue Frog Docs

Debugging GTM

Complete guide to troubleshooting Google Tag Manager issues, from tags not firing to data layer problems and container conflicts.

Debugging Google Tag Manager

This guide covers systematic approaches to diagnose and fix Google Tag Manager problems. Whether tags aren't firing, triggers aren't matching, or data isn't flowing correctly, follow these steps to identify and resolve the issue.

Essential Debugging Tools

1. GTM Preview Mode

The most powerful debugging tool for GTM:

  1. Enter Preview Mode:

    • Open GTM workspace
    • Click "Preview" button (top right)
    • A new tab opens with Tag Assistant
  2. Connect your site:

    • Enter your website URL
    • Click "Connect"
    • Your site opens with Tag Assistant overlay
  3. What to examine:

    • Tags Fired: Which tags executed
    • Tags Not Fired: Which tags didn't and why
    • Data Layer: All dataLayer pushes
    • Variables: Current variable values
    • Errors: JavaScript errors and warnings

2. Browser Developer Tools

Console Tab:

// Check if GTM is loaded
console.log('GTM loaded:', typeof google_tag_manager !== 'undefined');

// Inspect dataLayer
console.log(window.dataLayer);

// Monitor dataLayer pushes in real-time
const originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
  console.log('dataLayer.push:', arguments[0]);
  return originalPush.apply(this, arguments);
};

Network Tab:

  • Filter by googletagmanager.com
  • Verify GTM container loads (gtm.js)
  • Check for blocked requests

3. Google Tag Assistant

Chrome extension for visual debugging:

  1. Install Tag Assistant
  2. Navigate to your site
  3. Review all tags detected
  4. Check for errors and warnings

4. GTM Debug Panel

In Preview mode, the debug panel shows:

  • Summary: Overview of page load
  • Tags: All tags and their status
  • Variables: All variable values
  • Data Layer: Complete dataLayer history
  • Errors: Any errors that occurred

Common Problems and Solutions

Problem: Tags Not Firing

Symptoms:

  • Tag shows "Not Fired" in Preview
  • No network request for the tag
  • Analytics not receiving data

Diagnostic Steps:

  1. Check trigger conditions:

    • Open the tag in Preview mode
    • Click "Tags Not Fired"
    • Review "Firing Triggers" requirements
    • Identify which condition failed
  2. Verify trigger configuration:

// Example: Click trigger not matching
// Check if the element matches your CSS selector
document.querySelectorAll('.your-css-selector').length
// If 0, your selector is wrong
  1. Check for blocking triggers:

    • Review exception triggers
    • Verify no blocking triggers are applied
  2. Verify container is published:

    • Check container version in Preview
    • Ensure latest changes are published

Solutions:

Fix CSS selector triggers:

// Test your selector in console
document.querySelectorAll('button.cta-btn')

// If it returns elements, selector is correct
// If empty, adjust your selector

// Use Click Classes instead of Click Element for reliability:
// Trigger Type: Click - All Elements
// Condition: Click Classes contains "cta-btn"

Fix Page View triggers:

// Ensure Page View trigger type matches:
// - "Page View" fires on gtm.js
// - "DOM Ready" fires on gtm.dom
// - "Window Loaded" fires on gtm.load

// Check in dataLayer:
window.dataLayer.filter(e => e.event?.startsWith('gtm.'))

Problem: Tag Fires Multiple Times

Symptoms:

  • Same tag fires 2+ times per action
  • Duplicate events in analytics
  • Performance issues

Diagnostic Steps:

  1. Check Preview mode:

    • Look at Tags section
    • Count how many times tag appears
    • Note which triggers fired it
  2. Identify duplicate triggers:

    • Multiple triggers attached to same tag
    • Trigger fires multiple times (bubbling)
  3. Check for SPA issues:

    • History Change + Page View triggers both firing
    • Multiple route changes triggering same tag

Solutions:

// Prevent duplicate firing with one-per-page variable
// Create Custom JavaScript variable: "js - hasEventFired"

function() {
  return function(eventName) {
    var key = 'gtm_fired_' + eventName;
    if (window[key]) return true;
    window[key] = true;
    return false;
  };
}

// Use in trigger:
// Condition: {{js - hasEventFired}}('purchase') equals false
// For click events, prevent bubbling
// In tag, add to Custom HTML:
<script>
  (function() {
    if ({{Click Element}}.getAttribute('data-tracked')) return;
    {{Click Element}}.setAttribute('data-tracked', 'true');
    // Your tracking code here
  })();
</script>

Problem: Variables Return Undefined

Symptoms:

  • Variable shows "(undefined)" in Preview
  • Parameters missing in tags
  • Data layer variables empty

Diagnostic Steps:

  1. Check variable timing:

    • Is the data available when the tag fires?
    • Does the variable need DOM Ready?
  2. Verify data layer structure:

// Check exact structure in console
console.log(JSON.stringify(window.dataLayer, null, 2));

// Look for your expected data
window.dataLayer.find(e => e.productName)
  1. Test variable in Preview:
    • Click on the variable
    • Check "Current Value"
    • Compare expected vs actual

Solutions:

Fix Data Layer Variable:

// Incorrect: Nested path not matching
// GTM Variable: ecommerce.purchase.products
// Actual dataLayer: ecommerce.items

// Correct: Match exact structure
dataLayer.push({
  event: 'purchase',
  ecommerce: {
    transaction_id: 'T12345',
    items: [{...}]  // Use 'items', not 'products'
  }
});

// GTM Variable: ecommerce.items

Fix timing issues:

// Push data BEFORE the event
dataLayer.push({
  productName: 'Widget',
  productPrice: 29.99
});

// Then push the event
dataLayer.push({
  event: 'productView'
});

// Or combine (data persists):
dataLayer.push({
  event: 'productView',
  productName: 'Widget',
  productPrice: 29.99
});

Problem: Click Triggers Not Working

Symptoms:

  • Click events don't fire
  • "Clicks - All Elements" not matching
  • Link clicks not tracked

Diagnostic Steps:

  1. Verify click is reaching GTM:
// Check for event.preventDefault() or event.stopPropagation()
document.addEventListener('click', function(e) {
  console.log('Click detected:', e.target);
}, true);
  1. Check element attributes:

    • Click ID, Click Classes, Click URL in Preview
    • Verify your trigger conditions match
  2. Test in Preview:

    • Click the element
    • Check if gtm.click event appears in Data Layer

Solutions:

For dynamically loaded elements:

// Use Click - All Elements, not Click - Just Links
// Condition based on Click Element matches CSS selector

// Or use event delegation in Custom HTML tag:
<script>
document.body.addEventListener('click', function(e) {
  if (e.target.matches('.dynamic-button')) {
    dataLayer.push({
      event: 'dynamicButtonClick',
      buttonText: e.target.textContent
    });
  }
});
</script>

For links with event.preventDefault():

// Check "Wait for Tags" in trigger
// Set maximum wait time (e.g., 2000ms)

// Or fire before navigation:
// Tag Sequencing → Fire before this tag

Problem: Container Not Loading

Symptoms:

  • GTM snippet not visible in source
  • No gtm.js request in Network tab
  • All tags show as not fired

Diagnostic Steps:

  1. Check page source:
// Look for GTM snippet
document.querySelector('script[src*="googletagmanager"]')

// Or search source for container ID
document.documentElement.innerHTML.indexOf('GTM-XXXXXXX')
  1. Check for JavaScript errors:

    • Errors before GTM can block execution
    • Check Console for red errors
  2. Verify snippet placement:

    • Should be in <head> (script)
    • And after <body> opens (noscript)

Solutions:

<!-- Correct GTM installation -->
<!-- In <head>, as high as possible -->
<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','GTM-XXXXXXX');
</script>

<!-- Immediately after <body> opens -->
<noscript>
<iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>

Problem: Data Layer Events Not Triggering Tags

Symptoms:

  • Custom events pushed but tags don't fire
  • Event visible in dataLayer but not in Preview
  • Trigger conditions seem correct

Diagnostic Steps:

  1. Verify event name exactly matches:
// Check actual event name in dataLayer
window.dataLayer.filter(e => e.event).map(e => e.event)

// Common mistake: case sensitivity
'formSubmit' !== 'formsubmit'
  1. Check event timing:

    • Event pushed before GTM loads?
    • Event pushed in wrong order?
  2. Verify trigger type:

    • Custom Event trigger, not Page View
    • Event name matches exactly

Solutions:

// Ensure GTM is loaded before pushing events
window.dataLayer = window.dataLayer || [];

// For events before GTM loads, they'll be processed when GTM initializes
dataLayer.push({
  event: 'userRegistration',
  userId: '12345'
});

// If GTM might not be loaded yet:
function pushEvent(eventData) {
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push(eventData);
}

For SPA frameworks:

// React/Vue/Angular might push events before GTM
// Use this pattern:

// In your component
useEffect(() => {
  // Wait for GTM
  const checkGTM = setInterval(() => {
    if (window.google_tag_manager) {
      clearInterval(checkGTM);
      dataLayer.push({ event: 'componentLoaded' });
    }
  }, 100);

  return () => clearInterval(checkGTM);
}, []);

Data Layer Debugging

Inspecting the Data Layer

// View entire dataLayer
console.table(window.dataLayer);

// Find specific events
window.dataLayer.filter(e => e.event === 'purchase');

// Get latest data layer state (merged)
function getDataLayerState() {
  return window.dataLayer.reduce((acc, item) => {
    if (typeof item === 'object' && !Array.isArray(item)) {
      return {...acc, ...item};
    }
    return acc;
  }, {});
}
console.log(getDataLayerState());

Common Data Layer Mistakes

// WRONG: Nested event property
dataLayer.push({
  event: {
    name: 'purchase'  // Event won't fire!
  }
});

// CORRECT: Event at top level
dataLayer.push({
  event: 'purchase',
  transactionId: '12345'
});

// WRONG: Missing event key
dataLayer.push({
  productName: 'Widget'  // No trigger will fire
});

// CORRECT: Include event
dataLayer.push({
  event: 'productView',
  productName: 'Widget'
});

Monitoring Data Layer Changes

// Real-time monitoring
(function() {
  var originalPush = window.dataLayer.push;
  window.dataLayer.push = function() {
    var arg = arguments[0];
    console.group('dataLayer.push');
    console.log('Data:', JSON.stringify(arg, null, 2));
    console.log('Time:', new Date().toISOString());
    console.trace('Stack trace');
    console.groupEnd();
    return originalPush.apply(this, arguments);
  };
})();

Trigger Debugging

Understanding Trigger Priority

  1. Page View triggers fire in this order:

    • gtm.js (Initialization - All Pages)
    • gtm.dom (DOM Ready)
    • gtm.load (Window Loaded)
  2. Custom Event triggers fire when event is pushed

  3. Click triggers fire on user interaction

Debugging Trigger Conditions

// In Preview mode, click on a trigger
// Check "Filters" section to see:
// - Expected value
// - Actual value
// - Match result

// Common issues:
// "Page Path" equals "/products"
// Actual: "/products/" (trailing slash!)

// Fix: Use "contains" or "matches RegEx"
// Page Path matches RegEx ^/products/?$

Tag Debugging

Verify Tag Output

// For Custom HTML tags, add logging:
<script>
  console.log('Tag fired: My Custom Tag');
  console.log('Variables:', {
    productId: '{{Product ID}}',
    price: '{{Product Price}}'
  });
  // Your actual tag code
</script>

Check Tag Sequencing

If tags depend on each other:

  1. Open tag settings
  2. Click "Tag Sequencing"
  3. Set "Setup Tag" (fires before)
  4. Set "Cleanup Tag" (fires after)

Validate Marketing Tags

// Check if marketing pixels fired
// GA4:
console.log('GA4 loaded:', typeof gtag !== 'undefined');

// Meta Pixel:
console.log('Meta Pixel:', typeof fbq !== 'undefined');

// LinkedIn:
console.log('LinkedIn:', typeof _linkedin_data_partner_ids !== 'undefined');

Debugging Checklist

Use this checklist for any GTM issue:

  • GTM container loads (check Network tab)
  • Container ID is correct
  • Preview mode connected
  • Latest container version published
  • No JavaScript errors before GTM
  • dataLayer initialized before GTM
  • Event names match exactly (case-sensitive)
  • Trigger conditions are met
  • Variables resolve to expected values
  • No blocking triggers preventing fire
  • Tag sequencing is correct
  • Consent mode not blocking tags

Common Error Messages

"Tag didn't fire due to error"

Check the error in Preview mode's Errors tab. Common causes:

  • JavaScript syntax error in Custom HTML
  • Missing required field
  • Invalid URL format

"Variable evaluation failed"

The variable couldn't be resolved:

  • Check if element exists on page
  • Verify data layer key spelling
  • Ensure data is available when evaluated

"Trigger conditions not met"

One or more conditions didn't match:

  • Check actual vs expected values in Preview
  • Verify timing (data might not be available yet)
  • Check for typos in condition values

Performance Debugging

Slow Container Loading

// Check container load time
performance.getEntriesByName('https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX')

// If slow, consider:
// - Fewer tags
// - Efficient triggers (avoid All Pages for specific tags)
// - Tag pausing for unused tags

Tag Execution Time

In Preview mode:

  1. Click on a tag
  2. Check "Execution Time"
  3. Optimize slow tags (>100ms)

Getting More Help

// SYS.FOOTER