← All posts

Tutorials

Debug GTM without developers — log dataLayer.push in 30 seconds

The marketing analyst asks: "did this event fire?". The developer says: "probably, check GTM Preview". GTM Preview, however, shows different data than production. Four JS snippets that surface raw dataLayer.push straight into the console — without touching production code.

Why standard tooling falls short

Google Tag Manager Preview works great, but has three limits:

Your solution: inject JS that wraps dataLayer.push and logs everything. Without site code changes, no deploy, no review.

Method 1 — basic logger

Simplest version. Wraps dataLayer.push and dumps every call into the console:

// Wait for GTM init
window.dataLayer = window.dataLayer || [];

const origPush = window.dataLayer.push;
window.dataLayer.push = function(...args) {
  console.log(
    '%c[GTM]', 'color:#16a34a;font-weight:bold;font-size:13px',
    args
  );
  return origPush.apply(window.dataLayer, args);
};

console.log('[GTM] dataLayer logger active');

You open DevTools, click anything on the page (an "Add to cart" button, login, scroll), and the console shows:

[GTM] [{event: 'add_to_cart', ecommerce: {items: [...]}}]
[GTM] [{event: 'gtm.click', gtm.element: button, ...}]

Every push in full form. Exactly what GTM sees.

Method 2 — filter only events you care about

The method 1 logger produces a lot of noise (GTM itself pushes gtm.dom, gtm.load, gtm.click etc). If you're debugging a specific flow — say e-commerce purchase — filter:

const TARGET_EVENTS = [
  'purchase', 'add_to_cart', 'begin_checkout',
  'view_item', 'select_item', 'add_payment_info'
];

window.dataLayer = window.dataLayer || [];
const origPush = window.dataLayer.push;
window.dataLayer.push = function(...args) {
  const eventName = args[0]?.event;
  if (TARGET_EVENTS.includes(eventName)) {
    console.group(
      `%c[GTM] ${eventName}`,
      'color:#16a34a;font-weight:bold;font-size:14px'
    );
    console.log('Payload:', args[0]);
    if (args[0].ecommerce) {
      console.table(args[0].ecommerce.items);
    }
    console.groupEnd();
  }
  return origPush.apply(window.dataLayer, args);
};

console.group creates a collapsible section, console.table renders items as a table with columns — nicer than raw JSON. Perfect for e-commerce events with many products.

Method 3 — intercept gtag()

Some integrations (GA4 setup) use the gtag() function instead of direct dataLayer pushes. Under the hood it's the same, but you have to catch it differently:

// Wait for gtag to become available
function wrapGtag() {
  if (typeof window.gtag !== 'function') {
    setTimeout(wrapGtag, 100);
    return;
  }
  const origGtag = window.gtag;
  window.gtag = function(...args) {
    console.log(
      '%c[gtag]', 'color:#2563eb;font-weight:bold',
      args[0] /* 'event' | 'config' | 'set' */,
      args.slice(1)
    );
    return origGtag.apply(this, args);
  };
  console.log('[gtag] logger active');
}
wrapGtag();

Polls every 100ms until gtag is defined (GTM loads it async). Once found — wraps. Logs every gtag('event', '...', {...}) and gtag('config', '...').

Method 4 — visual toast feedback

The console is fine, but sometimes you want to see events without opening DevTools — say, demonstrating to a client that tracking works. Floating toast in the top-right corner:

// Toast container
const container = document.createElement('div');
container.style.cssText = `
  position: fixed; top: 16px; right: 16px;
  z-index: 999999; display: flex; flex-direction: column;
  gap: 6px; max-width: 320px;
`;
document.body.appendChild(container);

function showToast(text) {
  const t = document.createElement('div');
  t.style.cssText = `
    background: #16a34a; color: white;
    padding: 8px 12px; border-radius: 6px;
    font: 12px ui-monospace, monospace;
    box-shadow: 0 4px 12px rgba(0,0,0,.2);
  `;
  t.textContent = text;
  container.appendChild(t);
  setTimeout(() => t.remove(), 3000);
}

const origPush = window.dataLayer.push;
window.dataLayer.push = function(...args) {
  const name = args[0]?.event || 'unknown';
  showToast('GTM: ' + name);
  return origPush.apply(window.dataLayer, args);
};

Each event → green toast top-right, disappears in 3 seconds. The client watches the flow, sees events "firing" — credibility without DevTools.

Pitfalls worth avoiding

How to plug into JustZix

  1. Install JustZix.
  2. Create a "GTM debug" folder.
  3. Per-environment rule: URL pattern https://mystore.com/* and https://staging.mystore.com/*, JavaScript = Method 1 or 2.
  4. Floating button → toggle the rule when you need it. Disable after the debug session.
  5. Sync: paste the sync key on a second device — your marketing colleague also has the logger one click away.

What's next

The same pattern (wrap an existing global function + log) lets you log fetch, XMLHttpRequest, console.error, framework callbacks. See Examples → JavaScript and Use cases → analytics debugging.

Install JustZix and have developer tools loaded on every site, without changes to production code.

Rate this post

No ratings yet — be the first.

Try it yourself

Install JustZix and paste any snippet from this article. Two minutes from zero to a working rule across all your devices.

Get JustZix

Features · How it works · Examples · Use cases