I’ve always been proud to avoid the typical headline clickbait of “Ultimate guide to pigeon care”, “All you need to know about the Great Vowel Shift”, “Did you know that you’ve been smoking peyote wrong your whole life?”. I’m ready to make an exception now by adding a BIG WHOPPING NUMBER to the title. You see, the amount of knowledge one can accumulate about anything they do on a daily basis is mind-blowing. It helps if you write a blog about the topic, since creative output is a great way to organize your thoughts. It also helps to be active in community support, since problem-solving is an excellent way to accumulate new skills and to hone the edge of your existing talent.

Now, I already have 50+ GTM Tips written, so it’s not like this is a novel idea, even on this blog. But this time I just wanted to write short, byte-sized things I’ve learned along the way, and I want to share them with you.

As you can read from the outrageously baiting title, there should be 100+ tips, but I only enumerated an even 100. That’s because I want YOU to add your ideas to the end of this post, and let’s see if we can keep it going. Yes, it’s my shameful attempt to delegate content creation to the community. I am guilty of that, too, now.

Table of Contents

Container JavaScript Snippet

1. Initializes the dataLayer

The JavaScript snippet part of the GTM container has one very important function (among others). It initializes the window.dataLayer array. Thus, if you haven’t initialized a dataLayer object yourself, the container snippet will do this for you. This ensures that dataLayer.push() works within GTM.

2. Creates the script loader for the GTM library

Perhaps even more importantly, the JavaScript container snippet creates a <script> element, which loads the Google Tag Manager container library for your GTM container ID from Google’s servers.

3. JavaScript snippet should be in <head> but can be (almost) anywhere

The latest (and best) recommendation for placing the JavaScript snippet is to put it in the <head> of the document. This helps GTM load as early as possible, resulting in greater tracking accuracy. However, you can execute the JavaScript snippet pretty much any time during the page load and anywhere in your site code where execution of JavaScript is possible. The sooner the library loads, though, the more accurate your data collection will be.

4. Pushes the initial event: 'gtm.js'

The JavaScript snippet also pushes the initial event: 'gtm.js' into dataLayer. This is an important GTM event. It is used by the All Pages and Page View triggers. Any Data Layer variables you want to use with these triggers must be added to dataLayer before the JavaScript container snippet is executed.

5. Multiple container snippets on a page are supported

You can add multiple JavaScript container snippets on a page. This is officially supported. The caveat is that they all need to use the same dataLayer name.


Container <noscript> snippet

6. The <noscript> block should be at the very beginning of <body>

At the time of writing, the <noscript> block should be added to the very beginning of <body>. This is the only way that Search Console Verification using the Google Tag Manager method will work. Naturally, if you don’t care about verifying the site using the GTM method, nor do you have any use for tracking non-JavaScript visits, you can leave the <noscript> block out altogether. Just don’t place it in <head> as that would result in HTML validation issues.

7. Only executed by browsers with JavaScript disabled

The <noscript> snippet is only executed by browsers with JavaScript disabled. If you want to test it, you can disable JavaScript using your browser’s developer tools (e.g. Chrome).

8. Loads an HTML page in an <iframe>

The block loads an <iframe> element, which fetches its data as an HTML file from Google Tag Manager’s servers. In essence, this HTML file is your container. The HTML will contain the image elements you have configured to fire for JavaScript-disabled visitors.

9. Only the Page View trigger works

Because the JavaScript-less GTM can’t run JavaScript (d’oh), only the Page View trigger is at your disposal. Thus, there’s no dynamic triggers, and no way to wait for the page to load or anything like that. The Page View trigger is fired when the <iframe> contents are fetched.

10. Use a function() { return true; } Custom JavaScript variable in the trigger

A very handy way to fire tags only when executed in the <iframe> is to create a Custom JavaScript Variable with the following content:

function() {
  return true;
}

This variable will only return true if the browser executes it, i.e. executes JavaScript. By adding {{Variable}} does not equal true as a trigger condition fires the trigger only in browsers where JavaScript is disabled.

11. Only the Custom Image tag is useful

Since the JavaScript-less container can’t execute JavaScript, you are left with just the Custom Image tag. In other words, you can create image elements that are added directly into the container HTML. These image elements will then be rendered by the browser. In fact, you can even do some basic Google Analytics tracking using an image tag, since GA requests are basically image pixels. See this LunaMetric guide for inspiration.

12. Can utilize “Data Layer” parameters via query parameters

You can feed “Data Layer” values to the container HTML using query parameters in the <iframe> src attribute value. The query parameters need to be added as key-value pairs, and the keys that you add can then be used in Data Layer variables. For further details, see the Bounteous guide linked to in the previous paragraph, or check the guide I’ve written.


The dataLayer structure

13. Global JavaScript array

The dataLayer structure is a global JavaScript array, and can thus be accessed in any site code that can also access the window object. It’s a good idea to always prefix the dataLayer name with window. to avoid conflicts with any locally scoped structures that use the same name.

14. You can use a different name than dataLayer

You can change the name of this global structure in the JavaScript container snippet. Just remember to always use this new name when adding messages to dataLayer!

15. Only the .push() method works with GTM

Google Tag Manager only reacts to the .push() method. You can .splice(), .slice(), .shift() and .pop() all you like. GTM only listens for .push() commands.

16. Typically only plain objects work with GTM

The most common way to feed data to Google Tag Manager is using plain objects. Each object contains one or more key-value pairs. These key-value pairs are then translated into Data Layer variables, which you can create in Google Tag Manager to fetch values from the Data Layer.

var plainObject = {
  someKey: 'someValue',
  someOtherKey: 'someOtherValue'
};
window.dataLayer.push(plainObject);

17. You can use any JavaScript type as a value of a key

All JavaScript types are supported as values when you push your dataLayer messages. When you create a Data Layer variable in GTM, it will contain a reference to whatever the value of the key is, regardless of type.

window.dataLayer.push({
  type_number: 5,
  type_string: 'hello',
  type_object: { someKey: 'someValue' },
  type_array: [1,2,3,4],
  type_function: function() { return 'hello'!; },
  type_boolean: true
});

18. Only event key can trigger tags

Only a message with an event: 'someValue' key-value pair can trigger tags. Any object without an 'event' key is treated as just a “message”, and has no triggering power of its own.

19. You can also .push() a command array

There’s a special command array you can .push() into dataLayer if you want to execute methods for values already in the data model. So technically it’s not just plain objects that dataLayer digests. There’s more about this in tip #26.

20. Never overwrite, always .push()

I usually hate to dole out best practices, so consider this a fact of life instead. Never, ever, ever, ever use this syntax:

var dataLayer = [{...}];

It’s destructive. If this command is executed after the GTM container snippet or after you’ve already established a dataLayer object, you will end up overwriting the existing object with this newly initialized structure. Worst-case scenario (surprisingly common) is that you’ll end up breaking GTM, since you also overwrite the custom .push() listener added by the container library.

Prefer this syntax instead:

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({...});

21. The array is capped at 300

This is perhaps more obscure, but in addition to adding a .push() listener, the GTM container library also caps the length of the dataLayer structure at 300. This means that when you .push() the 301st item into dataLayer, the first/oldest item in dataLayer is removed. As you will learn in the next section, this has no impact on GTM’s data model. It just caps the dataLayer structure itself.


GTM’s data model

22. Copies messages queued via dataLayer.push()

When you use the Data Layer Variable, Google Tag Manager doesn’t fetch the value from the dataLayer array. Instead, it polls its own internal data model to see if any value has been pushed to the given key (or variable name). If a value is found, GTM returns it. This means that GTM’s internal data model has access to the most recently pushed value for any given key.

23. GTM freezes variable values when a trigger fires

Triggers only fire when the key 'event' is pushed into dataLayer. When this happens, GTM “freezes” the state of the container, and any tags that fire on this trigger will only have access to the current state of the internal data model. Thus, if you want to push values into dataLayer so that they become available to a tag that triggers on the site, these values need to be pushed before or in the same object as the 'event' that triggers the tag.

24. Objects are recursively merged

Recursive merge is one of the more complex concepts to understand. When you work with primitive values (strings, numbers, booleans, for example), the internal data model of GTM only has access to whatever was most recently pushed into a key whose value is one of these primitive types. However, when you work with structured objects and arrays, it’s more complicated.

When you push an object into dataLayer, GTM goes through each key in this object, and only overwrites those that have shared keys and primitive values (or where the type changes). New keys are simply added to the existing object value.

When pushing an object to a key that already contains an object with the same keys, only the keys that have primitive values or a different type are overwritten. All others are simply updated.

25. Arrays are recursively merged

In JavaScript, arrays are structured objects, too, where the keys are index numbers that start from 0. So when you push an array into a key that already had an array, these two arrays are recursively merged starting from index 0, and any indices that are not modified remain the same.

26. You can run JavaScript methods on existing Data Layer values with a command array

What if you already have an array in a key, but instead of merging or overwriting you want to add values to it, i.e. push items to the end of the array? You can use a special command array that you push into dataLayer. The first element in the array is a string that contains the key name and the command you want to execute, and all the other items are passed as arguments to the command.

27. Version 1 vs. Version 2 of the Data Layer Variable

You’ve probably noticed that you can choose a version when using the Data Layer Variable. There are some very important differences between the two.

Version 2 supports deep structures with dot notation. If you want to access array indices, you need to use dot notation too (products.0.name rather than products[0].name). Only Version 2 supports recursive merge.

Version 1 does not support dot notation, and it only fetches the most recently pushed value whether it’s an object or not. Thus there’s no recursive merge - what you push is what you get.

28. google_tag_manager['GTM-XXXX'].dataLayer methods

If you want to access values stored in Google Tag Manager’s data model from outside GTM or without using a Data Layer Variable, you can use the google_tag_manager interface.

google_tag_manager['GTM-XXXX'].dataLayer.get('keyName') fetches the value stored in GTM’s data model for variable name keyName.

google_tag_manager['GTM-XXXX'].dataLayer.set('keyName', 'someValue') sets the value of keyName to someValue in GTM’s data model. This is the equivalent to using dataLayer.push({keyName: 'someValue'});

google_tag_manager['GTM-XXXX'].dataLayer.reset() purges GTM’s data model, removing all stored keys.


Preview mode

When you enter GTM’s Preview Mode, you are transported through the domain www.googletagmanager.com (the same domain that serves the gtm.js library), during which a cookie is written in your browser for that domain.

When you then visit your website, the request for the gtm.js library identifies that you have the Preview mode cookie written on www.googletagmanager.com, and the preview container library is returned instead. So what you’re basically dealing with is a third-party cookie, even though the cookie isn’t set while browsing the site itself.

30. Shows the state of tags, triggers, variables, and Data Layer at each Data Layer message

Preview mode is a great way to understand how Google Tag Manager works. The navigation in the left column is a chronological (oldest at the bottom) list of messages that have been pushed into dataLayer. By selecting a message, you can see if any tags fired during that message, and you can see the state of tags, variables, and triggers at the time of the message.

What you see is what you get. If the variable does not have a value at the time of the message, it means that any tags that fire for that message will not have access to any value if using that variable. This is why it’s important to understand that if you want to use a variable, it must have a value when the message that triggers the tag is pushed into dataLayer.

31. Summary shows the state at the latest message

Summary is not a message itself. It’s a recap of what the state of the container is after the latest message has fired. Note that if you have Summary (or any other message, for that matter) selected, and you select a tag that fired in an earlier message, the tag might have different values than what you’d expect. That’s because when you select a message (or Summary), the variables reflect what their values are at the time of the selected message. This way tags can show different values from those that were actually used.

That’s why it’s really important to start debugging by selecting the message that fired the tag. Any other message and you might see confusing data.

32. Variables are resolved multiple times - at least once per message

If you’ve ever created a variable with side effects and then gone to preview mode, you might have been surprised at what happens. For example, create a Custom JavaScript Variable with this:

function() {
  window._testCount = window._testCount || 1;
  return window.alert(window._testCount++);
}

Now when you go to Preview mode, you’ll see a bunch of alerts with a number that increments with each alert. Depending on how many messages are pushed into dataLayer and how many tags use this variable, you might see a huge number in the last alert box.

This is because GTM resolves variables in Preview mode multiple times. Preview mode needs to resolve the variables at least once per message pushed into dataLayer. Why? Because Preview mode must be able to tell you the value of each variable in each tag, trigger, variable, and message.

In a live container, variables won’t be resolve this many times. Most likely they are only resolved when they are directly invoked, e.g. in triggers and tags upon injection.

33. Preview can be minimized

The Preview mode panel can be visually obstructive, so it’s a good thing the developers added a minimize button some time ago:

After clicking it, you can bring the panel back up by clicking the small DEBUG ^ icon in the lower right corner of the window.

34. To quit preview, you need to exit preview mode via the GTM UI

The easiest way to quit Preview mode is to go to the Google Tag Manager user interface and click the “Leave preview mode” link:

You can also go to your browser’s content settings, and delete all cookies written on the www.googletagmanager.com domain. This works with Shared Preview, too.

Wouldn’t it be handy if you could just quit Preview mode from the panel itself on the site? Yes, I think so too.

If you want to quit Preview mode that has been shared with you, you should follow the original Share Preview link and click “Exit preview and debug mode”.

Note that you can also delete the cookies as described in the previous tip.

36. Problems with the preview mode not showing correctly are most typically due to CSS conflicts

Sometimes you might not see a Preview mode on a website at all. Other times the panel might be buggy, such as being partly transparent or completely white.

In these cases, it’s most often a CSS conflict with the site code. GTM loads the panel on the website itself, so style conflicts can arise if they share the same namespace.

If this happens, your best bet is to contact the developer team via the Send Feedback link in the UI, or by posting the issue in the Product Forums.

37. You can also preview without the debug panel

Note that you can also preview a container on the site without the benefit of the debug panel. Why you’d want to do this when you can minimize the debug panel escapes me, but to do so you need to click the Share Preview link in the GTM UI, uncheck “Turn on debugging when previewing”, and then follow the link in your browser. This sets your browser into Preview mode without showing the debug panel.

38. Preview must be refreshed after every change

GTM doesn’t auto-refresh the Preview mode when you save changes in your container. You need to click the “Refresh” link to update the preview mode for yourself and anyone with the preview link.


Universal Analytics

39. GTM creates a new tracker with every tag instance

Unlike on-page Universal Analytics (analytics.js), Google Tag Manager creates a new, unique tracker object with every single tag that fires, even if they use the same template.

This might not be the most elegant technical design ever, but it’s necessary in how GTM structures Universal Analytics tags. Basically each tag is its own sandbox, and settings are not shared from tag to tag.

40. Settings are not shared across tags

Because each tag has a unique tracker, no settings are shared from tag to tag. This is very much unlike on-page Universal Analytics, where you create a single tracker and then invoke that tracker in commands like ga('trackerName.send', 'pageview');.

If you want to share settings of a single tag with other tags, currently you need to set the Tracker Name field in the tag settings. But before you do, read the next tip.

41. You can set a tracker name, but most often this is risky and unnecessary

If you do set the Tracker Name, you are likely to run into a host of problems. First of all - ALL settings are shared across the two tags. This is because GTM sets all fields and Custom Dimension / Metrics on the tracker object itself rather than just the hit. So you’ll need to take great care to reset any fields that you don’t want values to leak into.

Until GTM introduces some type of shared tag settings feature, I suggest avoiding the tracker name setting and working with GTM variables instead. If you want a setting to apply across two or more tags, just replicate the setting in each tag and use a variable to populate the same value in all the tags.

42. Use Fields to Set for setting any analytics.js fields

You can use Fields to Set to set any analytics.js fields. These fields are set on the tracker object itself (see previous tip), but will work as if set on the hit itself.

You can also add Measurement Protocol parameters to Fields to Set, but this is, in most cases, unnecessary.

43. If a field has the variable icon, you can use variables in it

Fields in Google Tag Manager support adding a variable if the field has the variable icon next to it.

By clicking the icon, a list of all available variables pops up. You can also use the auto-complete feature by typing {{ into the field, after which an auto-complete menu shows, and you can continue typing to find the variable you’re looking for.


Enhanced Ecommerce

44. Use Data Layer option uses Version 1 of the Data Layer Variable

When you select the Use Data Layer option in your Enhanced Ecommerce enabled Universal Analytics tags, GTM uses the Version 1 of the Data Layer Variable to locate the most recently pushed ecommerce key from the Data Layer.

Read that again. GTM only has access to the most recently pushed Enhanced Ecommerce payload in dataLayer. This means that if you first push impressions, for example, but don’t fire a tag, and then you push a Product Detail View which does fire a tag, that tag will only access the Product Detail View data. The impressions data is lost in cyberspace, due to no tag firing when it was pushed to dataLayer. To avert this, either make sure you always add an event to all your Enhanced Ecommerce pushes, and always use a Custom Event Trigger to fire an Enhanced Ecommerce enable tag.

Alternatively, you can use the far more flexible Custom JavaScript variable method.

45. Requires Data Layer object to be syntactically flawless

Enhanced Ecommerce is a bit different from how Data Layer typically works. Generally, you can push any key-value pairs into Data Layer, because you can always transform and mutate them in the GTM UI later on. However, when working with Enhanced Ecommerce, either via “Use Data Layer” or the Custom JavaScript Variable method, the payload must be syntactically accurate. It must have all the required keys, it must be structured correctly, and it must obey certain limitations to the structure (more details about structure).

You should always make sure you’re following the official developer guide to the letter.

46. The Currency type is just a string with a monetary value

If you read the official Enhanced Ecommerce developer guide, you might have noticed references to a type called “currency”.

Well, there’s no such data type in JavaScript. What the guide means is a string that has a monetary value (without currency symbol). Don’t use a thousand separator (e.g. “1 045.99”), and use the period as the decimal character.

A valid “currency” type would be "1045.99". Invalid types would be "1 045.99" and "1045,99". Due to loose typing in JavaScript, you could just as well pass it as a number 1045.99, but that will definitely lead to problems if the number is incorrectly formatted.

47. Product-scoped Custom Dimensions and Metrics need to be formatted correctly

Product-scoped Custom Dimensions and Metrics need to be formatted in a certain way to work. With regular Custom Dimensions and Metrics, all you need to do is add them to the tags under the respective tag settings.

However, in Enhanced Ecommerce, all the information must be in the payload. With Product-scoped Custom Dimensions and Metrics, this data must be in the products array, under each individual product you want to add the dimensions and metrics to. The dimensions and metrics must be named dimensionX and metricX where X is the index number for the given custom variable.

{
  ecommerce: {
    purchase: {
      actionField: {
        ...
      },
      products: [{
        id: '1',
        name: 'Shirt',
        dimension1: 'Red',
        metric1: 132,
        quantity: 1,
        price: '10.99'
      },{
        id: '2',
        name: 'Pants',
        dimension1: 'Black',
        dimension2: 'Adidas',
        metric1: 133,
        quantity: 1,
        price: '13.99'
      }]
    }
  }
}

48. Custom JavaScript variable method is more flexible than Use Data Layer

I always implement Enhanced Ecommerce using the Custom JavaScript variable method. It gives me so much more flexibility, as I can simply create the original dataLayer object as semantically unambiguous as possible, and then use the Custom JavaScript variable to mutate the object into the state the Enhanced Ecommerce requires. Why? Because I have plenty of other platforms that need the ecommerce data, too, and they might not be happy with the way that Google Tag Manager enforces a specific structure.

function() {
  var order = {{DLV - Order}};
  return {
    ecommerce: {
      purchase: {
        actionField: {
          id: order.orderId,
          revenue: order.price.totalWithTax,
          tax: order.price.taxValue,
          affiliation: order.store.name
        },
        products: [order.productsForGTM]
      }
    }
  };
}

The Custom JavaScript variable itself is simple. All you need to do is make sure it returns a valid Enhanced Ecommerce object as required by Google Tag Manager.


Triggers

49. Variables can only be used to check against

This is perhaps slightly oddly worded, but what I mean is that you can only use a variable as the thing in the trigger whose value you are checking. You can’t use a variable as the condition value itself.

50. Use a Custom JavaScript variable to check for dynamic values

If you DO want to check against dynamic values in your triggers, you can always use a Custom JavaScript variable. Let’s say you want to check if the clicked URL contains the current page hostname. Why? Because you want a trigger that fires only for clicks on links that take the user away from the website. This is what the Custom JavaScript variable might look like:

function() {
  return {{Click URL}}.indexOf({{Page Hostname}}) > -1;
}

This variable returns true if the clicked URL contains the current page hostname, and false otherwise. Now you can use a trigger like this:

51. 'event' is implicit in all but the Custom Event trigger

All triggers require an event key in dataLayer to fire. Thus, when you create a trigger, they check for the value of event, and if there’s a match the trigger fires. Only the Custom Event trigger requires you to explicitly state the value of event you want to fire against. Here are the basic trigger types and their implicit event values:

  • DOM Ready - gtm.dom

  • Page View - gtm.js

  • Window Loaded - gtm.load

  • Click / All Elements - gtm.click

  • Click / Just Links - gtm.linkClick

  • Form submission - gtm.formSubmit

  • History Change - gtm.historyChange

  • JavaScript Error - gtm.pageError

  • Timer - gtm.timer

  • Scroll Depth - gtm.scrollDepth

  • YouTube Video - gtm.video

52. Multiple trigger conditions are AND, multiple triggers are OR

Multiple conditions in a single trigger must ALL match for the trigger to fire. Thus a trigger like this should never work:

Why won’t it work? Because the hostname of the current page can’t be two things at once.

If you add multiple triggers to a tag, then any one of these will fire the tag. So, if you want your tag to fire when the page hostname is either www.domain.com or www.other-domain.com, you can create two triggers, one for each hostname, and add both to the tag.

53. Use regular expressions or Custom JavaScript variables to add optionality in a single trigger

There’s an easier way to introduce optionality, though. First, if it’s a simple string check, you can always use regular expressions.

If you have more complex logic, a Custom JavaScript variable is your best friend, again.

function() {
  var hn = {{Page Hostname}},
      ut = {{DLV - userType}};
  if (hn === 'www.mydomain.com' && ut === 'visitor') {
    return 'visitor';
  }
  if (hn === 'www.mydomain.com' && ut === 'member') {
    return 'member';
  }
  if (hn === 'www.other-domain.com' && ut === 'loyal') {
    return 'loyal';
  }
  return 'other';
}

Auto-event trigger

When you create a Just Links trigger, it listens to clicks on <a> elements and their descendants. When a click is registered, Google Tag Manager checks if there is a link node wrapping the clicked element, and if there is, GTM stores a reference to the link in the dataLayer.

For example, say the page HTML looks like this:

<div id="content">
  <a href="https://www.google.com/">
    <span>Google</span>
  </a>
</div>

If someone clicks on the link, the click actually falls on the <span> element, but the Just Links trigger propagates the click to the <a> element, and returns that for you to leverage with Auto-event variables.

55. All Elements listens to all clicks

All Elements, on the other hand, listens to all clicks and returns the element that was actually clicked. In the HTML example above, the All Elements trigger would return the <span> element because that’s the element that was actually clicked.

56. Form listens to a submit event dispatched by a <form> element

The Form trigger only works with an actual <form> element submitted with default HTML form functionality. This means that there actually needs to be a submit event dispatched by the form, and it must be allowed to bubble up.

Any custom server-side validation, suppressing of the submit event, or customized form handling will result in the Form trigger not working. Thus, the Form trigger is typically the trigger you’ll have the most difficulties with due to the ridiculously diverse number of ways that forms can be handled with JavaScript.

57. History Change listens to interactions with the browser history API

The History Change trigger listens for the following browser history API events: hashchange, pushState, replaceState and popstate.

Typically these are used on single-page websites, where page transitions are done without a page refresh.

When creating History Change triggers, you’ll typically want to work mainly with pushState and replaceState, as those are managed by the by the site code itself. popstate and hashchange can be triggered automatically by the web browser (and there are differences between browsers), which might lead to inaccuracies in your tracking.

58. Error listens to uncaught JavaScript exceptions

The Error triggers listens to uncaught JavaScript errors that occur on the website. If there’s a try...catch block anywhere in the error path, this trigger will not react to it.

Do note that Custom JavaScript variables automatically catch all exceptions, so this trigger will not help you debug errors in Custom JavaScript variables.

59. All triggers but the Click / All Elements trigger require that the original event bubble up

Google Tag Manager attaches its auto-event listeners to the document element of the page. This is the highest node in the web document. The reason GTM does this is because it allows you to handle events that take place anywhere on the page, even for elements that don’t exist when GTM first loads.

For this type of event delegation to work, the event must bubble up to the top of the document. It’s surprising how often bubbling is cancelled, leading to GTM’s events not working.

The only exception is the All Elements trigger, which uses the capture phase of the event path. This means that even if bubbling is stopped, the capture phase can still record the click. So if you find your Just Links trigger isn’t working, you can recreate the same logic with an All Elements trigger and some clever CSS selector / Custom JavaScript variable work.

For more information, see e.g. this article on GTM listener issues, and this on element capturing with the All Elements trigger.

60. Check Validation checks for event.preventDefault()

If you have Check Validation checked in your Just Links or Form trigger, the trigger will only fire if the event’s default action is not cancelled.

This is a useful feature to leverage, since often the default action of a link (redirect) or a form (submit) is prevented due to the link simply changing a content tab, or the form being incorrectly filled, for example. In these cases, you’ll want to have Check Validation checked, because you probably don’t want to track clicks on links that don’t redirect or submissions of forms that don’t actually submit.

61. Wait for Tags pauses the original event, but be careful

If you use the Wait for Tags option in the Just Links or Form triggers, Google Tag Manager halts the action of the link or form, respectively, to wait until all tags that use the trigger have fired. After tags signal completion, GTM allows the original event to continue.

This is a great feature. It mitigates the risk of losing data due to the link or form redirecting the user to a new page before the tags that use the trigger have fired.

However, there are many ways to do custom link redirects and form submissions. When GTM pauses the event, it’s not 100% reliable GTM understands what the custom behavior was. Thus, when GTM then proceeds with the paused action, it might be a different action altogether that GTM resumes.

This is typical in single-page apps, where internal links have a lot of complex logic added to them to prevent links from redirecting the user to new URLs.

So, when you use the Wait for Tags option, always remember to test the pages where the trigger is active. Test links and forms, both, to make sure that their functionalities are not compromised. If you are in doubt, simply uncheck Wait for Tags and accept a certain level of inaccuracy in your tracking.

62. Enable this trigger when… vs. This trigger fires on…

If you do check Wait for Tags or Check Validation, you’ll see the “Enable this trigger when…” option appear in the trigger settings.

The “Enable this trigger when…” option is for determining on which pages the trigger should listen to actions. Thus you can use it to have the trigger listen only on pages where you have thoroughly tested the trigger doesn’t mess with site functionality.

It’s a good idea to start with a generic Page URL contains / condition here, as it will simply set the trigger to listen to user actions on all pages of the site. If you do run into trouble, you can modify this condition to only activate the trigger on a specific subset of pages.

The “This trigger fires on…” option is for determining what conditions OTHER than the trigger event itself need to exist for any tags which use the trigger to fire. This is where you’ll add your “Click URL” and “Form ID” conditions, for example.

63. Data Layer object composition of an auto-event

When an auto-event trigger fires, the following items are pushed into dataLayer:

  • event - gets the value of the trigger event that was registered.

  • gtm.element - reference to the HTML element that was the target of the event.

  • gtm.elementClasses - string with the value from the class attribute of the target element (if any).

  • gtm.elementId - string with the value from the id attribute of the target element (if any).

  • gtm.elementTarget - string with the value from the target attribute of the target element (if any).

  • gtm.elementUrl - string with the value from the href or action attribute of the target element (if any).

  • gtm.triggers - a regular expression which determines if the trigger that activated is enabled on the current page.

In addition to these, you might see variables like gtm.uniqueEventId, eventCallback, eventTimeout and eventReporter. These are internal to GTM, but suffice to say that they govern how the Wait for Tags option works, among other things.

64. Auto-event variable

If you’re not satisfied with the Built-in variables (Click / Form) for auto-events, you can create your own. Google Tag Manager has a user-defined variable type called Auto-Event Variable, which lets you analyse pretty much any part of the auto-event target element you want.

For example, if you’ve added a custom data attribute data-gtm-cta="Subscribe call to action", and you want to check if the clicked link has this data attribute, you can create a new Auto-Event Variable that looks like this:

This comes in very handy, as you won’t be limited to the rather small number of available built-in variables.

65. Matches CSS selector with the Click / Form Element built-in variable

The matches CSS selector is one of the most effective ways you can create triggers in Google Tag Manager. When combined with the Click / Form Element Built-in variable, it gains a whole new level of awesomeness.

You can write complex CSS selectors to check whether the element that was the target of the auto-event matches what you expect. For example, let’s say you have the following HTML structure:

<div id="products">
  <div>
    <div>
      <a href="some-product.html">Product/a>
    </div>
  </div>
</div>
<div id="services">
  <div>
    <div>
      <a href="some-service.html">Service</a>
    </div>
  </div>
</div>

Now, you only want your Just Links trigger to fire when the user clicks the “Product” link. However, as you can see, the HTML structures between “Product” and “Service” are almost identical, making it difficult to pinpoint the exact element without having to resort to suboptimal selection mechanisms. Well, CSS selectors to the rescue. The following Just Links trigger will only fire when the click is on a link that is the descendant of a div with the id "products":

Just remember: matches CSS selector only works against an HTML element. So don’t try to match a selector against the Click ID or Click URL variables, for example. Only Click / Form Element will do.


Custom HTML tags

66. Code is automatically minified

Custom HTML tags automatically minify / uglify the JavaScript, so no need to do so yourself.

67. Code is injected to the end of <body>

When you write HTML code in a Custom HTML tag, the entire code block is always injected to the end of <body> when the trigger fires for the tag.

If you want to place the HTML code somewhere else, you need to write JavaScript (within the Custom HTML tag) that creates a new HTML element with your specifications, and then uses DOM methods to place it wherever you like on the page.

68. Can be used to add any HTML elements

You can use the Custom HTML tag to add any supported HTML5 code to the page. This means that tags such as these are all supported: meta, link, style, video, script, and so forth.

Naturally, with some JavaScript DOM magic, you can also modify existing elements or even remove them entirely from the page.

69. The document.write option is fixed to prevent the site from breaking

If you are adding a script which makes use of document.write to place elements on the page, you will need to check the “Support document.write” option in the Custom HTML tag editor.

If you don’t check this option, you run the risk of the document.write command clearing the entire page of all contents because of how document.write works post-page-load.

Dan Wilkerson has written a great guest post on this topic, so remember to check it out for more information.

70. Variables are automatically renamed in Custom HTML tags, too

Did you know that when you create a variable reference in GTM with {{Variable Name}} and you then change the variable name to {{Some Other Variable Name}}, all references are automatically updated?

Well this applies to Custom HTML tags, too, so you don’t have to worry about syntax errors when renaming variables. The new name will be automatically applied to all places in the container where the variable is referred to.


Built-in variables

71. Need to be enabled

The only Built-in Variables enabled by default in your web container are Event, Page URL, Page Path, Page Hostname and Referrer.

To enable others, go to Variables and click the red CONFIGURE under the heading “Built-In Variables”.

In the overlay that flies out, check the box next to each Built-in variable you want to enable. Only after enabling the variables will they be available in variable selection drop-downs.

72. Click and Form variables are copies of each other

You might have noticed that there’s a very similar set of six auto-event Built-in variables: Click/Form Element, Click/Form ID, Click/Form URL, Click/Form Target, Click/Form Classes, Click/Form Text.

These are identical in functionality, so you really only need to enable either Click or Form Built-in variables.


Custom JavaScript variables

73. Must be anonymous functions

When you create a Custom JavaScript variable, it should be an anonymous function. Yes, you can name it, but it doesn’t matter since there’s no way to call the variable with its name, as it’s locally scoped to whatever execution context invokes the variable when your container needs to do so.

// Not ideal:
function clickTextLowercase() {
  return {{Click Text}}.toLowerCase();
}

// Use this:
function() {
  return {{Click Text}}.toLowerCase();
}

To avoid any potential namespace conflicts, just use an anonymous function, since you call GTM’s variables with the syntax and not with whatever name you give the method itself.

74. Must have a return statement

All Custom JavaScript variables must have a return statement or Google Tag Manager will throw an error when you try to create a version of the container.

// Will not work:
function() {
  if (true) {}
}

// Works:
function() {
  if (true) {}
  return;
}

75. Can be used to return another function

Remember that a Custom JavaScript variable is just regular JavaScript, and as such you can use it to return another function. This is also called returning a closure.

For example, let’s say I want to create a utility function that takes a string as an argument and reverses all letters in it. First, I’d create a Custom JavaScript variable like this:

function() {
  return function(str) {
    return str.split('').reverse().join('');
  };
}

Then, when I want to use this utility in a Custom HTML tag or another variable, I can pass any string to the GTM variable as an attribute:

<script>
  (function() {
    // Create a reference to the Custom JavaScript variable we just created
    var reverseString = {{JS - ReverseString}}
    window.dataLayer.push({
      event: 'reverseComplete',
      reversedString: reverseString("Reverse me!")
    });
  })();
</script>

This Custom HTML tag calls the function returned by the Custom JavaScript variable with the string “Reverse me!”, resulting in a dataLayer.push() where the key reversedString will have the value “!em esreveR”.

76. Should avoid side effects

Custom JavaScript variables should avoid side effects. In other words, they shouldn’t do anything except process an input and produce an output. Any transformations or mutations should happen in local scope alone.

If you don’t respect this, you might run into all sorts of issues due to the fact that Google Tag Manager’s resolution of variables is unpredictable and can’t be counted on to only happen once per tag.

Here are some things to avoid:

  • Modifying, creating, or deleting items in the global window namespace.

  • Creating or processing custom HTTP requests.

  • Setting or removing items in globally available APIs, such as dataLayer, google_tag_manager, document.cookie and localStorage.

If you do want to modify global state, use Custom HTML tags or closures instead.

77. Can refer to other variables

This might seem like a no-brainer, but you can freely refer to other variables in Custom JavaScript variables, too.

function() {
  var hostname = {{Page Hostname}};
  return hostname === 'www.domain.com' ? 'Main domain' : 'Other domain';
}

Tag sequencing

78. Setup and cleanup are fired with the main tag, regardless of their own triggers

When you add a Setup or Cleanup tag to a sequence with a main tag, the Setup and Cleanup will be fired with the main tag regardless of what triggers they might actually have themselves.

So here’s a tip: When creating Setup and Cleanup tags, use them only for that purpose and do not add any triggers to them. This way you will never run the risk of these tags firing when they shouldn’t, as they are strictly bound to whatever sequence they are in.

79. Use onHtmlSuccess() and onHtmlFailure() to signal that the sequence can continue

When you have a Custom HTML tag as a Setup or Main tag, you can let Google Tag Manager know if the tag completed successfully or failed by using the onHtmlSuccess() and onHtmlFailure() methods.

For these to work, you first need to enable Built-in variables Container ID and HTML ID.

Here’s an example of a Custom HTML Setup tag, where we wait for the asynchronous POST request to complete before telling GTM to continue to the main tag. If the asynchronous request fails, we tell GTM that the Setup tag was a failure.

<script>
  (function($) {
    $.post('/test.php')
      .done(function() {
        google_tag_manager[{{Container ID}}].onHtmlSuccess({{HTML ID}});
      })
      .fail(function() {
        google_tag_manager[{{Container ID}}].onHtmlFailure({{HTML ID}});
      });
  })(jQuery);
</script>

If you didn’t have the onHtmlSuccess/Failure() methods there, Google Tag Manager would simply signal completion as soon as it reaches the last line of code in the tag. Thus the browser won’t wait for any asynchronous requests to complete, and you might end up with a nasty race condition if you need the request to complete before proceeding with the Main tag.

80. Use dataLayer.set() to change Data Layer variable values mid-sequence

If you want to change some value in GTM’s Data Layer while in the middle of a sequence, you can’t use dataLayer.push(), because GTM freezes its state for the duration of each message that is pushed into dataLayer.

However, there’s a workaround. You CAN update the value of GTM’s Data Layer variables, even though the same dataLayer message is still being processed. To do this, you need to use the google_tag_manager[{{Container ID}}].dataLayer.set('keyName', 'value') interface you learned of here.


Tag settings

81. Once per page is once per page load, once per event is once per GTM event

There are three options in the Tag firing options menu that you can find at the end of each tag’s settings.

Unlimited means the tag will fire whenever its triggers fire - no restrictions.

Once per event means that the tag will fire just once per the trigger event that caused it to fire. Thus if you have, for example, multiple Click triggers attached to the tag, and there’s a chance that some of these have overlapping trigger conditions, Once per event ensures that the tag fires just once per click.

Once per page means that the tag will fire only once per page load. No matter what triggers you have attached to it - if the tag fires on the page, it will not fire again until the page is reloaded from the web server. Tag Sequencing respects this, too, so if a Setup or Cleanup tag are set to fire just once per page, they will not fire multiple times even if the main tag does.

82. Tag priority is for a single GTM event - doesn’t necessarily mean tags are completed in the given order

The Tag priority field can be used to establish an order execution for tags that fire on the same trigger event. When a trigger event happens, Google Tag Manager executes tags in order of priority.

Note that Tag priority only establishes the order in which tags begin execution. It has no implications on when they complete. Thus, even if a tag starts its execution first, it might have a long, complex, asynchronous process involved, and it still ends up completing only after tags later in the priority order have already finished.


Workspaces

83. You will always have at least one workspace

Workspaces have come to stay, and it’s impossible to use Google Tag Manager without a workspace.

Thus, even if you try to delete all workspaces, GTM will always leave you with one.

84. When you create a version out of a workspace, that workspace is deleted

Workspaces are ephemeral - they only exist until a version is created from them. Once you create a version, it’s as if the workspace never existed. Thus workspaces shouldn’t be used as permanent container subsets that you could, for example, delegate to a certain user group only. Instead, workspaces should be approached as branches of a version control system. When you start working on a new feature, create a workspace first so that any changes you make are contained until you choose to create a version and merge the changes to the latest container version.

85. You don’t have to update your workspace until you are ready to create a version

When someone else updates the latest container version, all the workspaces in the container need to be updated. You’ll know this has happened when you see the following notice in the GTM UI:

You don’t have to update the workspace the minute you see this warning. You can bide your time, and only sync the changes once you are ready to create a version out of your workspace.

Nevertheless, the sooner you update the better. Why? Because once merges start piling up, you’ll have a hard time resolving all the conflicts at once.


AMP container

86. No Custom JavaScript or Custom HTML

Accelerated Mobile Pages place some pretty strict restrictions on what you can use Google Tag Manager for. You can’t execute arbitrary JavaScript code anymore, nor can you simply inject HTML via Google Tag Manager. In fact, all dynamic operations need to be handled server-side in GTM, because all that GTM returns is the AMP configuration JSON object, which has very limited capabilities for any advanced tracking.

So no Custom JavaScript variables or Custom HTML tags in the AMP container, unfortunately.

87. AMP is very restricting

As said, AMP is very restricting. Only JavaScript sanctioned by AMP and provided as an AMP module can be executed. This means that the development of the GTM AMP container is bound very closely to the roadmap of AMP itself. Any attempts to deviate would result in an invalid configuration JSON, which would, in turn, lead to an invalid AMP page.

It’s a good idea to closely follow the amp-analytics project, as it contains the latest release details for the amp-analytics module. This is the module used by Google Tag Manager’s AMP container.

88. Client ID is (currently) ambiguous

Universal Analytics’ Client ID is problematic with AMP, especially when run through the GTM container. There are actually four different Client ID scenarios for AMP analytics.

  1. When visiting the regular site (no AMP): _ga cookie stores the Universal Analytics Client ID.

  2. When visiting the regular site (AMP): AMP_ECID_GOOGLE cookie stores a randomly generated AMP Client ID.

  3. When visiting the site via the AMP cache: the AMP client ID is stored in localStorage on the ampproject.org domain.

  4. When visiting the site via Google search: the AMP client ID is stored on google.com and ampproject.org in localStorage.

In all of these scenarios, it’s possible that the Client ID is different, which means that you’ll have quite a bit of difficulty in tracking the same user across AMP and non-AMP sites, or between Google search and direct access to the site.

Some time ago, Dan Wilkerson and I wrote a hack around this, which does require some web server muscle, but it will make sure that all the scenarios above use the same Universal Analytics client ID in the _ga cookie.

89. New triggers, e.g. scroll

AMP introduces a bunch of really useful triggers, which I hope we’ll see in GTM for web soon, too.

Since you can’t execute arbitrary JavaScript, AMP provides stuff like scroll and visibility tracking out-of-the-box with amp-analytics. Google Tag Manager gives you a way to easily configure the triggers, but it should be noted that there’s not much room to configure the triggers. AMP is restricted in this way, too.

For example, I would like to configure the scroll trigger to track scroll-to-element rather than scroll-to-percentage. Unfortunately, this is not possible.

90. Can be augmented with the on-page JSON configuration

Remember that even if you use the AMP Google Tag Manager container, you can also add a JSON configuration block to the page. You can use this extra configuration to complement the tracking you setup via Google Tag Manager.

  ...
  <!-- AMP Analytics --><script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
</head>
<body>
  <!-- Google Tag Manager -->
  <amp-analytics config="https://www.googletagmanager.com/amp.json?id=GTM-XXXXX&gtm.url=SOURCE_URL" data-credentials="include"></amp-analytics>
  <script type="application/json">
  {
    "vars": {
      "pageType": "profile",
      "userType": "member"
    }
  }
  </script>

This is a pretty simple way to add some web server logic to your GTM AMP container.


GTM for mobile

91. Not the same as GTM for web

Let’s address the elephant in the room first. If AMP was restricted, then GTM for mobile is doubly so. Why? Because you’re no longer working with a website that self-corrects any JavaScript mistakes you happen to have in the site code. When working with mobile apps, the app needs to be well-formed without exceptions, and you can’t add runtime code to it without it being sanctioned by the platform the app is running on.

Also, since this isn’t the web anymore, there’s no “Custom HTML” or “Custom JavaScript” here, either. Android apps use Java, and iOS relies on Swift and Objective-C.

Due to these restrictions, it’s a valid concern to raise whether you need Google Tag Manager at all. It takes pretty much the same effort as simply using the analytics SDK directly.

The benefit of using GTM is that it will always leverage the latest analytics SDKs. Also, with the Firebase integration you have access to the entire Firebase suite of services, and most of them are really useful for app developers.

Finally, GTM is still GTM. You can implement tracking tags without having to worry too much about how the app itself works. Support for different tag templates isn’t too impressive yet, but this will surely change in the future.

92. An SDK you need to download and add to the project

Google Tag Manager isn’t a container file that is just fetched from the web and then everything works nicely. No, it’s actually a combination of SDKs (software development kit) and container binaries / JSON files that you need to integrate into the app itself.

With the latest version of Google Tag Manager for mobile (Firebase), implementing GTM to a project is really simple. You basically need to include just two podfiles: Firebase/Core and GoogleTagManager. The rest is added automatically via dependencies.

The app fetches the latest container version if a “fresh” container is available. You can’t always trust that a fresh container can be fetched, so it’s a good idea to always keep a recent version of the container in the app assets. So, when publishing a new release of the app, make sure to check if there’s an updated container version that needs to be added to the project before going live.

93. GA tracker object is not exposed

Google Tag Manager doesn’t expose the GA tracker object in the same way that GTM for web does. In other words, if you want to check what the user’s Client ID is, for example, there’s no command that lets you dig that information from the available GTM interfaces.

A hacky but working solution in these cases is to create a dummy tracker using the regular Google Analytics SDK, and mining tracker-specific information from this to be used in your Google Tag Manager setup.

94. GTM legacy uses dataLayer

GTM for mobile, before Firebase, is now called “Legacy”. If you’re used to GTM for web, the legacy SDK should be quite familiar to you, since it also uses a dataLayer construct to pass messages to Google Tag Manager. As before, there is the event key you need to use to fire the triggers, and there are Data Layer Variables you can use to access values in this message queue.

Note that unlike with GTM for web, there is no differentiation between dataLayer the message queue and GTM’s internal data model. The dataLayer structure acts as both.

95. Latest version of GTM for mobile uses Firebase

The most recent incarnation of GTM for mobile uses Firebase. Firebase is a cloud-based application framework that provides a number of services your apps can use, such as Firebase Analytics.

When you want to use GTM with Firebase, you need to actually use Firebase. You add tracking using regular Firebase Analytics tracking methods, which means that unless you use GTM to radically change the tracking, you will end up using Firebase Analytics in addition to the other analytics tools you want to track via Google Tag Manager.

96. You can intercept Firebase events using GTM

The way that Google Tag Manager works with Firebase is that it listens to all the events you log into Firebase. Once it detects an event, it goes through the triggers and tags you’ve configured in the container, and if a tag is set to fire on a specific Firebase event, it will go off.

And as with Google Tag Manager for web, the more tag endpoints you have, the more useful GTM becomes. You can use a single event stream (the logged Firebase events) to send data to multiple endpoints, such as Google Analytics, Firebase, and AppsFlyer.

97. Firebase GTM has (imperfect) support for GA Enhanced Ecommerce

At the time of writing, you can’t configure Universal Analytics Ecommerce tags via Firebase GTM. The reason is that GTM’s data model does not support array structures, which is how you’d need to send product data to Google Analytics.

Hopefully we’ll see support for Ecommerce soon. Until then, if you need to collect Ecommerce data to Google Analytics, you might want to use the GTM Legacy SDK or GA SDK.

Update: Support for Enhanced Ecommerce has finally reached Firebase (iOS and Android). Still missing product-scoped custom dimensions and metrics, though! (Thanks Yuhui for the tip in the comments).


Other stuff

98. Debugging is a complex process

End-to-end debugging with Google Tag Manager isn’t just a case of opening up Preview mode and being satisfied with what you see. It’s a complex process, involving not only front-end conflicts in your site code, but also things like race conditions, unresponsive tag endpoints, and dataLayer automated tests.

I recommend you take a look at this article: #GTMTips: Debugging Tag Execution Properly. Make sure you familiarize yourself with the Network tab of your browser’s developer tools. If Preview mode says that your tags fire but you don’t see any evidence in the endpoint you are looking at (e.g. Google Analytics reports), take a look at the Network tab to see if you are dispatching hits to /collect correctly. Google Tag Assistant Recordings is your friend, too, for all Google-related tagging issues.

Finally, I recommend adding dataLayer to your organization’s quality assurance process. This would mean writing unit tests and browser tests that verify dataLayer has a predictable composition with each new release of the site or app. I’ve written a simple framework for running automated tests against dataLayer, and you can read about it here.

99. Load sequence of GTM’s default events is important to understand

When Google Tag Manager loads on a site, it pushes three default events into dataLayer: gtm.js, gtm.dom and gtm.load.

gtm.js is pushed in the container snippet itself. Thus it introduces a state which contains all the Data Layer variables pushed before or at the time when the container snippet runs. This event is used by the Page View trigger. In other words, if you have tags that fire on the Page View trigger (or All Pages), any Data Layer variables you want to use need to be pushed into dataLayer before the container snippet is executed by the browser. Typically this means to physically add your dataLayer initialization in the page template above the GTM container snippet.

<script>
  // Always use this syntax to initialize dataLayer
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({
    userType: 'member',
    loyaltyLevel: 'premium'
  });
</script>
...
<script>
  // GTM container snippet here
</script>

gtm.dom is pushed into dataLayer when the browser signals the DOMContentLoaded event has taken place. This browser event happens when the browser has read the HTML template and built the Document Object Model (DOM) using the structure within. Thus, if you have tags which refer to DOM elements, you might want to make sure they don’t fire before the gtm.dom event. The trigger which uses this event is DOM Ready, so any tags firing on this trigger will have access to any elements described in the page HTML.

gtm.load is pushed into dataLayer when the browser signals the load event, which means that the entire page, and all linked assets such as images, scripts (both asynchronous and synchronous), and videos have completed downloading. This is the event you’d need to use if you want your tags to access the result of some asynchronous process, such as the download of the jQuery library (if downloaded asynchronously, as you should). The trigger which uses this event is Window Loaded.

It’s important to understand how these three events work. They are not fired at the exact time the underlying browser event takes place. For example, gtm.js will fire triggers only after the GTM library has downloaded, and this might be seconds after the browser originally reads the event in the page template. Thus these events don’t describe the time something happens but rather the state. gtm.js describes the state of GTM’s data when the container snippet was first read by the browser, gtm.dom reflects GTM’s state when the browser signalled DOMContentLoaded, and gtm.load is the state of GTM when the entire page had completed loading.

100. Some ad and content blockers block GTM from loading

Call it a feature or symptom of today’s web browsing behavior, but ad and content blockers are making life for web analysts difficult. Firefox, for example, offers Tracking Protection out-of-the-box, and it blocks Google Analytics by default.

Popular browser extensions like Ghostery and AdBlock Plus make it easy to block Google Tag Manager, too.

Whether you like this or not, it’s a common practice and you should therefore accept a certain level of inaccuracy in your data.

With Google Tag Manager, however, you can actively modify and extend the site experience with Custom JavaScript. If the user’s browser doesn’t load Google Tag Manager, these modifications will not be executed.

Thus, be very wary of potential GTM blocking when running code via Google Tag Manager. Never rely on GTM to handle your link redirects or form submissions, and never use GTM to fix on-site issues that should be fixed in front-end code. You are depriving an ever-increasing subset of your visitors of a full experience, and at worst destroying the site user experience altogether for them.


101. Use undefined to clear individual keys from Data Layer (upepo mwindaji)

The first guest tip comes from upepo mwindaji. You can clear individual values from GTM’s data model by pushing the keys with the value undefined. This effectively resets the value of the key.

window.dataLayer.push({
  event: 'userLoggedOut',
  loginStatus: 'loggedOut',
  userId: undefined,
  memberStatus: undefined
});

The code above, for example, clears the values of userId and memberStatus from GTM’s data model because the user logged out.

102. You can track to multiple Universal Analytics properties in the same container

This guest tip is from samgabell. Since GTM creates a unique tracker with every Universal Analytics tag, you can freely track to multiple Universal Analytics properties in the same container. There will be no interference between sets of tags tracking to different properties.

The only thing that will cause issues is if one set of tags is configured for cross-domain tracking with the allowLinker field set to true. In this case, a linker parameter in the URL will overwrite the Client ID stored in the _ga cookie, impacting all trackers on the site - even those that should ignore cross-domain tracking. You can read about a solution to this issue here.

Summary

So that was my 100 tips. Some of them were indeed very simple and obvious, but I’ve run into enough people having issues with all of the things covered here to justify my adding them to the list.

Google Tag Manager turns 5 years old this year. It’s been a colorful journey, and I’m sure the popularity of the tool has changed the landscape for all tag management solutions for the better. Via GTM, so many new people have had the pleasure to (or have been forced to) get acquainted with the wonderful world of JavaScript, and have thus accumulated new skills to help them on their digital journeys.

Now is your turn - are there invaluable tips that you’d want to share with the readers? I’d love to add them to the end of the list. Thank you!