{"id":122895,"date":"2025-03-09T21:17:52","date_gmt":"2025-03-09T21:17:52","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/100-google-tag-manager-learnings\/"},"modified":"2025-03-09T21:17:52","modified_gmt":"2025-03-09T21:17:52","slug":"100-google-tag-manager-learnings","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=122895","title":{"rendered":"100+ Google Tag Manager Learnings"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<p>I\u2019ve always been proud to avoid the typical headline clickbait of \u201cUltimate guide to pigeon care\u201d, \u201cAll you need to know about the Great Vowel Shift\u201d, \u201cDid you know that you\u2019ve been smoking peyote wrong your whole life?\u201d. I\u2019m 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.<\/p>\n<div style=\"aspect-ratio: 1974 \/ 1106;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/100-tips.jpg\" title=\"Google Tag Manager lessons learned\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1106\" width=\"1974\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/100-tips.jpg#ZgotmplZ\" alt=\"Google Tag Manager lessons learned\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now, I already have 50+ <a href=\"https:\/\/www.simoahava.com\/categories\/gtm-tips\/\">GTM Tips<\/a> written, so it\u2019s not like this is a novel idea, even on this blog. But this time I just wanted to write short, byte-sized things I\u2019ve learned along the way, and I want to share them with you.<\/p>\n<p>As you can read from the outrageously baiting title, there should be 100+ tips, but I only enumerated an even 100. That\u2019s because I want YOU to add your ideas to the end of this post, and let\u2019s see if we can keep it going. Yes, it\u2019s my shameful attempt to delegate content creation to the community. I am guilty of that, too, now.<\/p>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"container-javascript-snippet\">Container JavaScript Snippet<\/h2>\n<h3 id=\"1-initializes-the-datalayer\">1. Initializes the <code>dataLayer<\/code><\/h3>\n<p>The JavaScript snippet part of the GTM container has one very important function (among others). It initializes the <code>window.dataLayer<\/code> array. Thus, if you haven\u2019t initialized a <code>dataLayer<\/code> object yourself, the container snippet will do this for you. This ensures that <code>dataLayer.push()<\/code> works within GTM.<\/p>\n<h3 id=\"2-creates-the-script-loader-for-the-gtm-library\">2. Creates the script loader for the GTM library<\/h3>\n<p>Perhaps even more importantly, the JavaScript container snippet creates a <code><script\/><\/code> element, which loads the Google Tag Manager container library for your GTM container ID from Google\u2019s servers.\n<div style=\"aspect-ratio: 1116 \/ 104;\" class=\"figure nocaption\">\n    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/script-loader.jpg\" title=\"GTM container script loader\">\n    <img decoding=\"async\" class=\"fig-img\" height=\"104\" width=\"1116\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/script-loader.jpg#ZgotmplZ\" alt=\"GTM container script loader\"\/>\n    <\/a>\n<\/div>\n<h3 id=\"3-javascript-snippet-should-be-in-ltheadgt-but-can-be-almost-anywhere\">3. JavaScript snippet should be in  but can be (almost) anywhere<\/h3>\nThe latest (and best) <em>recommendation<\/em> for placing the JavaScript snippet is to put it in the <code\/> 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.\n<h3 id=\"4-pushes-the-initial-event-gtmjs\">4. Pushes the initial <code>event: 'gtm.js'<\/code><\/h3>\nThe JavaScript snippet also pushes the initial <code>event: 'gtm.js'<\/code> into <code>dataLayer<\/code>. 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 <code>dataLayer<\/code> <em>before the JavaScript container snippet is executed<\/em>.\n<div style=\"aspect-ratio: 980 \/ 156;\" class=\"figure nocaption\">\n    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/gtm-js-pushed.jpg\" title=\"event: gtm.js pushed\">\n    <img decoding=\"async\" class=\"fig-img\" height=\"156\" width=\"980\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/gtm-js-pushed.jpg#ZgotmplZ\" alt=\"event: gtm.js pushed\"\/>\n    <\/a>\n<\/div>\n<h3 id=\"5-multiple-container-snippets-on-a-page-are-supported\">5. Multiple container snippets on a page are supported<\/h3>\nYou can add multiple JavaScript container snippets on a page. This is <a href=\"https:\/\/developers.google.com\/tag-manager\/devguide?hl=en#multiple-containers\">officially supported<\/a>. The caveat is that they all need to use the same <code>dataLayer<\/code> name.\n<hr\/>\n<h2 id=\"container-ltnoscriptgt-snippet\">Container <noscript> snippet<\/noscript><\/h2>\n<h3 id=\"6-the-ltnoscriptgt-block-should-be-at-the-very-beginning-of-ltbodygt\">6. The <noscript> block should be at the very beginning of <\/noscript><\/h3>\n<p>At the time of writing, the <code><noscript\/><\/code> block should be added to the very beginning of <code\/>. This is the only way that <a href=\"https:\/\/support.google.com\/webmasters\/answer\/35179?hl=en\">Search Console Verification<\/a> using the Google Tag Manager method will work. Naturally, if you don\u2019t care about verifying the site using the GTM method, nor do you have any use for tracking non-JavaScript visits, you can leave the <code><noscript\/><\/code> block out altogether. Just don\u2019t place it in <code\/> as that would result in HTML validation issues.<\/p>\n<h3 id=\"7-only-executed-by-browsers-with-javascript-disabled\">7. Only executed by browsers with JavaScript disabled<\/h3>\n<p>The <code><noscript\/><\/code> snippet is only executed by browsers with JavaScript disabled. If you want to test it, you can disable JavaScript using your browser\u2019s developer tools (e.g. <a href=\"http:\/\/stackoverflow.com\/a\/13405409\">Chrome<\/a>).<\/p>\n<h3 id=\"8-loads-an-html-page-in-an-ltiframegt\">8. Loads an HTML page in an <iframe><\/iframe><\/h3>\n<p>The block loads an <code><iframe><\/iframe><\/code> element, which fetches its data as an HTML file from Google Tag Manager\u2019s 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.<\/p>\n<h3 id=\"9-only-the-page-view-trigger-works\">9. Only the Page View trigger works<\/h3>\n<p>Because the JavaScript-less GTM can\u2019t run JavaScript (d\u2019oh), only the Page View trigger is at your disposal. Thus, there\u2019s 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 <code><iframe><\/iframe><\/code> contents are fetched.<\/p>\n<h3 id=\"10-use-a-function--return-true--custom-javascript-variable-in-the-trigger\">10. Use a <code>function() { return true; }<\/code> Custom JavaScript variable in the trigger<\/h3>\n<p>A very handy way to fire tags only when executed in the <code><iframe><\/iframe><\/code> is to create a Custom JavaScript Variable with the following content:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">return<\/span> <span style=\"color:#00a\">true<\/span>;\n}\n<\/code><\/pre>\n<\/div>\n<p>This variable will only return <code>true<\/code> if the browser executes it, i.e. executes JavaScript. By adding <strong>{{Variable}} does not equal true<\/strong> as a trigger condition fires the trigger only in browsers where JavaScript is disabled.<\/p>\n<h3 id=\"11-only-the-custom-image-tag-is-useful\">11. Only the Custom Image tag is useful<\/h3>\n<p>Since the JavaScript-less container can\u2019t 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 <a href=\"https:\/\/www.bounteous.com\/insights\/2015\/03\/23\/gtm-iframe-no-javascript\/\">this LunaMetric guide<\/a> for inspiration.<\/p>\n<h3 id=\"12-can-utilize-data-layer-parameters-via-query-parameters\">12. Can utilize \u201cData Layer\u201d parameters via query parameters<\/h3>\n<p>You can feed \u201cData Layer\u201d values to the container HTML using query parameters in the <code><iframe><\/iframe><\/code> 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 <a href=\"https:\/\/www.simoahava.com\/analytics\/track-non-javascript-visits-google-analytics\/\">guide I\u2019ve written<\/a>.<\/p>\n<hr\/>\n<h2 id=\"the-datalayer-structure\">The <code>dataLayer<\/code> structure<\/h2>\n<h3 id=\"13-global-javascript-array\">13. Global JavaScript array<\/h3>\n<p>The <code>dataLayer<\/code> structure is a global JavaScript array, and can thus be accessed in any site code that can also access the <code>window<\/code> object. It\u2019s a good idea to always prefix the <code>dataLayer<\/code> name with <code>window.<\/code> to avoid conflicts with any locally scoped structures that use the same name.<\/p>\n<h3 id=\"14-you-can-use-a-different-name-than-datalayer\">14. You can use a different name than <code>dataLayer<\/code><\/h3>\n<p>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 <code>dataLayer<\/code>!<\/p>\n<div style=\"aspect-ratio: 1262 \/ 194;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/rename-datalayer.jpg\" title=\"Rename dataLayer\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"194\" width=\"1262\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/rename-datalayer.jpg#ZgotmplZ\" alt=\"Rename dataLayer\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"15-only-the-push-method-works-with-gtm\">15. Only the <code>.push()<\/code> method works with GTM<\/h3>\n<p>Google Tag Manager only reacts to the <code>.push()<\/code> method. You can <code>.splice()<\/code>, <code>.slice()<\/code>, <code>.shift()<\/code> and <code>.pop()<\/code> all you like. GTM only listens for <code>.push()<\/code> commands.<\/p>\n<h3 id=\"16-typically-only-plain-objects-work-with-gtm\">16. Typically only plain objects work with GTM<\/h3>\n<p>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.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">var<\/span> plainObject = {\n  someKey: <span style=\"color:#a50\">'someValue'<\/span>,\n  someOtherKey: <span style=\"color:#a50\">'someOtherValue'<\/span>\n};\n<span style=\"color:#0aa\">window<\/span>.dataLayer.push(plainObject);\n<\/code><\/pre>\n<\/div>\n<h3 id=\"17-you-can-use-any-javascript-type-as-a-value-of-a-key\">17. You can use any JavaScript type as a value of a key<\/h3>\n<p>All JavaScript types are supported as values when you push your <code>dataLayer<\/code> 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.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#0aa\">window<\/span>.dataLayer.push({\n  type_number: <span style=\"color:#099\">5<\/span>,\n  type_string: <span style=\"color:#a50\">'hello'<\/span>,\n  type_object: { someKey: <span style=\"color:#a50\">'someValue'<\/span> },\n  type_array: [<span style=\"color:#099\">1<\/span>,<span style=\"color:#099\">2<\/span>,<span style=\"color:#099\">3<\/span>,<span style=\"color:#099\">4<\/span>],\n  type_function: <span style=\"color:#00a\">function<\/span>() { <span style=\"color:#00a\">return<\/span> <span style=\"color:#a50\">'hello'<\/span>!; },\n  type_boolean: <span style=\"color:#00a\">true<\/span>\n});\n<\/code><\/pre>\n<\/div>\n<h3 id=\"18-only-event-key-can-trigger-tags\">18. Only <code>event<\/code> key can trigger tags<\/h3>\n<p>Only a message with an <code>event: 'someValue'<\/code> key-value pair can trigger tags. Any object without an <code>'event'<\/code> key is treated as just a \u201cmessage\u201d, and has no triggering power of its own.<\/p>\n<h3 id=\"19-you-can-also-push-a-command-array\">19. You can also <code>.push()<\/code> a command array<\/h3>\n<p>There\u2019s a special <em>command array<\/em> you can <code>.push()<\/code> into <code>dataLayer<\/code> if you want to execute methods for values already in the data model. So technically it\u2019s not just plain objects that <code>dataLayer<\/code> digests. There\u2019s more about this in tip #26.<\/p>\n<h3 id=\"20-never-overwrite-always-push\">20. Never overwrite, always <code>.push()<\/code><\/h3>\n<p>I usually hate to dole out best practices, so consider this a fact of life instead. Never, ever, ever, ever use this syntax:<\/p>\n<p>It\u2019s destructive. If this command is executed after the GTM container snippet or after you\u2019ve already established a <code>dataLayer<\/code> object, you will end up overwriting the existing object with this newly initialized structure. Worst-case scenario (surprisingly common) is that you\u2019ll end up breaking GTM, since you also overwrite the custom <code>.push()<\/code> listener added by the container library.<\/p>\n<p>Prefer this syntax instead:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#0aa\">window<\/span>.dataLayer = <span style=\"color:#0aa\">window<\/span>.dataLayer || [];\n<span style=\"color:#0aa\">window<\/span>.dataLayer.push({...});\n<\/code><\/pre>\n<\/div>\n<h3 id=\"21-the-array-is-capped-at-300\">21. The array is capped at 300<\/h3>\n<p>This is perhaps more obscure, but in addition to adding a <code>.push()<\/code> listener, the GTM container library also caps the length of the <code>dataLayer<\/code> structure at 300. This means that when you <code>.push()<\/code> the 301st item into <code>dataLayer<\/code>, the <em>first\/oldest<\/em> item in <code>dataLayer<\/code> is removed. As you will learn in the next section, this has no impact on GTM\u2019s data model. It just caps the <code>dataLayer<\/code> structure itself.<\/p>\n<hr\/>\n<h2 id=\"gtms-data-model\">GTM\u2019s data model<\/h2>\n<h3 id=\"22-copies-messages-queued-via-datalayerpush\">22. Copies messages queued via <code>dataLayer.push()<\/code><\/h3>\n<p>When you use the Data Layer Variable, Google Tag Manager doesn\u2019t fetch the value from the <code>dataLayer<\/code> array. Instead, it polls its own <a href=\"https:\/\/www.simoahava.com\/analytics\/google-tag-manager-data-model\/\">internal data model<\/a> 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\u2019s internal data model has access to the <strong>most recently pushed value for any given key<\/strong>.<\/p>\n<h3 id=\"23-gtm-freezes-variable-values-when-a-trigger-fires\">23. GTM freezes variable values when a trigger fires<\/h3>\n<p>Triggers only fire when the key <code>'event'<\/code> is pushed into <code>dataLayer<\/code>. When this happens, GTM \u201cfreezes\u201d 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 <code>dataLayer<\/code> 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 <code>'event'<\/code> that triggers the tag.<\/p>\n<div style=\"aspect-ratio: 1316 \/ 508;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/trigger-order.jpg\" title=\"Order of dataLayer.pushes for triggers\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"508\" width=\"1316\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/trigger-order.jpg#ZgotmplZ\" alt=\"Order of dataLayer.pushes for triggers\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"24-objects-are-recursively-merged\">24. Objects are recursively merged<\/h3>\n<p>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\u2019s more complicated.<\/p>\n<p>When you push an object into <code>dataLayer<\/code>, 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.<\/p>\n<div style=\"aspect-ratio: 2338 \/ 992;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/recursive-merge.jpg\" title=\"Recursive merge\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"992\" width=\"2338\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/recursive-merge.jpg#ZgotmplZ\" alt=\"Recursive merge\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>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.<\/p>\n<h3 id=\"25-arrays-are-recursively-merged\">25. Arrays are recursively merged<\/h3>\n<p>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.<\/p>\n<div style=\"aspect-ratio: 1626 \/ 400;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/array-merge.jpg\" title=\"Array recursive merge\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"400\" width=\"1626\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/array-merge.jpg#ZgotmplZ\" alt=\"Array recursive merge\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"26-you-can-run-javascript-methods-on-existing-data-layer-values-with-a-command-array\">26. You can run JavaScript methods on existing Data Layer values with a command array<\/h3>\n<p>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 <em>command array<\/em> that you push into <code>dataLayer<\/code>. 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.<\/p>\n<div style=\"aspect-ratio: 840 \/ 414;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/command-array.jpg\" title=\"Command array\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"414\" width=\"840\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/command-array.jpg#ZgotmplZ\" alt=\"Command array\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"27-version-1-vs-version-2-of-the-data-layer-variable\">27. Version 1 vs. Version 2 of the Data Layer Variable<\/h3>\n<p>You\u2019ve probably noticed that you can <a href=\"https:\/\/www.simoahava.com\/gtm-tips\/data-layer-variable-versions-explained\/\">choose a <strong>version<\/strong> when using the Data Layer Variable<\/a>. There are some very important differences between the two.<\/p>\n<p><strong>Version 2<\/strong> supports deep structures with dot notation. If you want to access array indices, you need to use dot notation too (<code>products.0.name<\/code> rather than <code>products[0].name<\/code>). Only Version 2 supports recursive merge.<\/p>\n<p><strong>Version 1<\/strong> does not support dot notation, and it only fetches the most recently pushed value whether it\u2019s an object or not. Thus there\u2019s no recursive merge &#8211; what you push is what you get.<\/p>\n<h3 id=\"28-google_tag_managergtm-xxxxdatalayer-methods\">28. <code>google_tag_manager['GTM-XXXX'].dataLayer<\/code> methods<\/h3>\n<p>If you want to access values stored in Google Tag Manager\u2019s data model from outside GTM or without using a Data Layer Variable, you can use the <code>google_tag_manager<\/code> interface.<\/p>\n<p><code>google_tag_manager['GTM-XXXX'].dataLayer.get('keyName')<\/code> fetches the value stored in GTM\u2019s data model for variable name <strong>keyName<\/strong>.<\/p>\n<p><code>google_tag_manager['GTM-XXXX'].dataLayer.set('keyName', 'someValue')<\/code> sets the value of <strong>keyName<\/strong> to <strong>someValue<\/strong> in GTM\u2019s data model. This is the equivalent to using <code>dataLayer.push({keyName: 'someValue'});<\/code><\/p>\n<p><code>google_tag_manager['GTM-XXXX'].dataLayer.reset()<\/code> purges GTM\u2019s data model, removing all stored keys.<\/p>\n<hr\/>\n<h2 id=\"preview-mode\">Preview mode<\/h2>\n<h3 id=\"29-preview-mode-works-with-a-cookie-on-wwwgoogletagmanagercom-in-the-preview-browser\">29. Preview mode works with a cookie on <a href=\"https:\/\/www.googletagmanager.com\">www.googletagmanager.com<\/a> in the preview browser<\/h3>\n<p>When you enter GTM\u2019s Preview Mode, you are transported through the domain <a href=\"https:\/\/www.googletagmanager.com\">www.googletagmanager.com<\/a> (the same domain that serves the gtm.js library), during which a cookie is written in your browser for that domain.<\/p>\n<div style=\"aspect-ratio: 1372 \/ 722;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/preview-cookie.jpg\" title=\"Preview mode cookie\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"722\" width=\"1372\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/preview-cookie.jpg#ZgotmplZ\" alt=\"Preview mode cookie\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>When you then visit your website, the request for the gtm.js library identifies that you have the Preview mode cookie written on <a href=\"https:\/\/www.googletagmanager.com\">www.googletagmanager.com<\/a>, and the preview container library is returned instead. So what you\u2019re basically dealing with is a third-party cookie, even though the cookie isn\u2019t set while browsing the site itself.<\/p>\n<h3 id=\"30-shows-the-state-of-tags-triggers-variables-and-data-layer-at-each-data-layer-message\">30. Shows the state of tags, triggers, variables, and Data Layer at each Data Layer message<\/h3>\n<p>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 <code>dataLayer<\/code>. 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.<\/p>\n<div style=\"aspect-ratio: 1830 \/ 528;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/messages-state.jpg\" title=\"Messages and state of data layer\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"528\" width=\"1830\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/messages-state.jpg#ZgotmplZ\" alt=\"Messages and state of data layer\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>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\u2019s important to understand that <strong>if you want to use a variable, it must have a value when the message that triggers the tag is pushed into <code>dataLayer<\/code><\/strong>.<\/p>\n<h3 id=\"31-summary-shows-the-state-at-the-latest-message\">31. Summary shows the state at the latest message<\/h3>\n<p><strong>Summary<\/strong> is not a message itself. It\u2019s 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\u2019d expect. That\u2019s because when you select a message (or Summary), the variables reflect what their values are <strong>at the time of the selected message<\/strong>. This way tags can show different values from those that were actually used.<\/p>\n<p>That\u2019s why it\u2019s really important to start debugging by selecting the message that fired the tag. Any other message and you might see confusing data.<\/p>\n<h3 id=\"32-variables-are-resolved-multiple-times---at-least-once-per-message\">32. Variables are resolved multiple times &#8211; at least once per message<\/h3>\n<p>If you\u2019ve 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:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#0aa\">window<\/span>._testCount = <span style=\"color:#0aa\">window<\/span>._testCount || <span style=\"color:#099\">1<\/span>;\n  <span style=\"color:#00a\">return<\/span> <span style=\"color:#0aa\">window<\/span>.alert(<span style=\"color:#0aa\">window<\/span>._testCount++);\n}\n<\/code><\/pre>\n<\/div>\n<p>Now when you go to Preview mode, you\u2019ll 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.<\/p>\n<p>This is because GTM <strong>resolves variables in Preview mode multiple times<\/strong>. Preview mode needs to resolve the variables <em>at least<\/em> 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.<\/p>\n<p>In a live container, variables won\u2019t be resolve this many times. Most likely they are only resolved when they are directly invoked, e.g. in triggers and tags upon injection.<\/p>\n<h3 id=\"33-preview-can-be-minimized\">33. Preview can be minimized<\/h3>\n<p>The Preview mode panel can be visually obstructive, so it\u2019s a good thing the developers added a minimize button some time ago:<\/p>\n<div style=\"aspect-ratio: 1718 \/ 566;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/minimize-preview.jpg\" title=\"Minimize GTM preview\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"566\" width=\"1718\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/minimize-preview.jpg#ZgotmplZ\" alt=\"Minimize GTM preview\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>After clicking it, you can bring the panel back up by clicking the small <strong>DEBUG ^<\/strong> icon in the lower right corner of the window.<\/p>\n<h3 id=\"34-to-quit-preview-you-need-to-exit-preview-mode-via-the-gtm-ui\">34. To quit preview, you need to exit preview mode via the GTM UI<\/h3>\n<p>The easiest way to quit Preview mode is to go to the Google Tag Manager user interface and click the \u201cLeave preview mode\u201d link:<\/p>\n<div style=\"aspect-ratio: 2136 \/ 676;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/leave-preview-mode.jpg\" title=\"Leave preview mode\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"676\" width=\"2136\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/leave-preview-mode.jpg#ZgotmplZ\" alt=\"Leave preview mode\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You can also go to your browser\u2019s content settings, and delete all cookies written on the <a href=\"https:\/\/www.googletagmanager.com\">www.googletagmanager.com<\/a> domain. This works with Shared Preview, too.<\/p>\n<div style=\"aspect-ratio: 1562 \/ 502;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/remove-cookies.jpg\" title=\"Exit preview and debug mode\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"502\" width=\"1562\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/remove-cookies.jpg#ZgotmplZ\" alt=\"Exit preview and debug mode\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Wouldn\u2019t it be handy if you could just quit Preview mode from the panel itself on the site? Yes, I think so too.<\/p>\n<h3 id=\"35-to-quit-a-shared-preview-you-need-to-follow-the-original-link\">35. To quit a shared preview, you need to follow the original link<\/h3>\n<p>If you want to quit Preview mode that has been shared with you, you should follow the original Share Preview link and click \u201cExit preview and debug mode\u201d.<\/p>\n<div style=\"aspect-ratio: 1672 \/ 514;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/exit-preview-debug.jpg\" title=\"Exit preview and debug mode\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"514\" width=\"1672\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/exit-preview-debug.jpg#ZgotmplZ\" alt=\"Exit preview and debug mode\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Note that you can also delete the cookies as described in the previous tip.<\/p>\n<h3 id=\"36-problems-with-the-preview-mode-not-showing-correctly-are-most-typically-due-to-css-conflicts\">36. Problems with the preview mode not showing correctly are most typically due to CSS conflicts<\/h3>\n<p>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.<\/p>\n<p>In these cases, it\u2019s 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.<\/p>\n<p>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 <a href=\"https:\/\/productforums.google.com\/forum\/#!forum\/tag-manager\">Product Forums<\/a>.<\/p>\n<div style=\"aspect-ratio: 1718 \/ 598;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/send-feedback.jpg\" title=\"Send feedback link\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"598\" width=\"1718\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/send-feedback.jpg#ZgotmplZ\" alt=\"Send feedback link\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"37-you-can-also-preview-without-the-debug-panel\">37. You can also preview without the debug panel<\/h3>\n<p>Note that you can also preview a container on the site without the benefit of the debug panel. Why you\u2019d 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 \u201cTurn on debugging when previewing\u201d, and then follow the link in your browser. This sets your browser into Preview mode without showing the debug panel.<\/p>\n<div style=\"aspect-ratio: 1138 \/ 918;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/turn-off-debugging.jpg\" title=\"Turn off debugging\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"918\" width=\"1138\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/turn-off-debugging.jpg#ZgotmplZ\" alt=\"Turn off debugging\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"38-preview-must-be-refreshed-after-every-change\">38. Preview must be refreshed after every change<\/h3>\n<p>GTM doesn\u2019t auto-refresh the Preview mode when you save changes in your container. You need to click the \u201cRefresh\u201d link to update the preview mode for yourself and anyone with the preview link.<\/p>\n<div style=\"aspect-ratio: 1534 \/ 522;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/refresh-preview.jpg\" title=\"Refresh preview link\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"522\" width=\"1534\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/refresh-preview.jpg#ZgotmplZ\" alt=\"Refresh preview link\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<hr\/>\n<h2 id=\"universal-analytics\">Universal Analytics<\/h2>\n<h3 id=\"39-gtm-creates-a-new-tracker-with-every-tag-instance\">39. GTM creates a new tracker with every tag instance<\/h3>\n<p>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.<\/p>\n<div style=\"aspect-ratio: 1506 \/ 556;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/multiple-trackers.jpg\" title=\"Multiple trackers\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"556\" width=\"1506\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/multiple-trackers.jpg#ZgotmplZ\" alt=\"Multiple trackers\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This might not be the most elegant technical design ever, but it\u2019s necessary in how GTM structures Universal Analytics tags. Basically each tag is its own sandbox, and settings are not shared from tag to tag.<\/p>\n<h3 id=\"40-settings-are-not-shared-across-tags\">40. Settings are not shared across tags<\/h3>\n<p>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 <code>ga('trackerName.send', 'pageview');<\/code>.<\/p>\n<p>If you want to share settings of a single tag with other tags, currently you need to set the <strong>Tracker Name<\/strong> field in the tag settings. But before you do, read the next tip.<\/p>\n<h3 id=\"41-you-can-set-a-tracker-name-but-most-often-this-is-risky-and-unnecessary\">41. You can set a tracker name, but most often this is risky and unnecessary<\/h3>\n<p>If you do set the Tracker Name, you are likely to run into a host of problems. First of all &#8211; 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\u2019ll need to take great care to reset any fields that you don\u2019t want values to leak into.<\/p>\n<p>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.<\/p>\n<h3 id=\"42-use-fields-to-set-for-setting-any-analyticsjs-fields\">42. Use Fields to Set for setting any analytics.js fields<\/h3>\n<p>You can use Fields to Set to set any <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/analyticsjs\/field-reference\">analytics.js<\/a> fields. These fields are set on the tracker object itself (see previous tip), but will work as if set on the hit itself.<\/p>\n<p>You can also add <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/protocol\/v1\/parameters\">Measurement Protocol parameters<\/a> to Fields to Set, but this is, in most cases, unnecessary.<\/p>\n<h3 id=\"43-if-a-field-has-the-variable-icon-you-can-use-variables-in-it\">43. If a field has the variable icon, you can use variables in it<\/h3>\n<p>Fields in Google Tag Manager support adding a variable if the field has the variable icon next to it.<\/p>\n<div style=\"aspect-ratio: 1824 \/ 392;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/variable-icon.jpg\" title=\"variable icon in fields\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"392\" width=\"1824\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/variable-icon.jpg#ZgotmplZ\" alt=\"variable icon in fields\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>By clicking the icon, a list of all available variables pops up. You can also use the auto-complete feature by typing <code>{{<\/code> into the field, after which an auto-complete menu shows, and you can continue typing to find the variable you\u2019re looking for.<\/p>\n<hr\/>\n<h2 id=\"enhanced-ecommerce\">Enhanced Ecommerce<\/h2>\n<h3 id=\"44-use-data-layer-option-uses-version-1-of-the-data-layer-variable\">44. Use Data Layer option uses Version 1 of the Data Layer Variable<\/h3>\n<p>When you select the <strong>Use Data Layer<\/strong> 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 <code>ecommerce<\/code> key from the Data Layer.<\/p>\n<div style=\"aspect-ratio: 1896 \/ 398;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/use-data-layer.jpg\" title=\"Use Data Layer\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"398\" width=\"1896\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/use-data-layer.jpg#ZgotmplZ\" alt=\"Use Data Layer\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Read that again. GTM only has access to the <strong>most recently pushed<\/strong> Enhanced Ecommerce payload in <code>dataLayer<\/code>. This means that if you first push impressions, for example, but don\u2019t 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 <code>dataLayer<\/code>. To avert this, either make sure you always add an <code>event<\/code> to all your Enhanced Ecommerce pushes, and always use a Custom Event Trigger to fire an Enhanced Ecommerce enable tag.<\/p>\n<p>Alternatively, you can use the far more flexible Custom JavaScript variable method.<\/p>\n<h3 id=\"45-requires-data-layer-object-to-be-syntactically-flawless\">45. Requires Data Layer object to be syntactically flawless<\/h3>\n<p>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 \u201cUse Data Layer\u201d or the Custom JavaScript Variable method, the payload must be syntactically accurate. It <strong>must<\/strong> have all the required keys, it <strong>must<\/strong> be structured correctly, and it <strong>must<\/strong> obey certain limitations to the structure (<a href=\"https:\/\/www.simoahava.com\/analytics\/enhanced-ecommerce-tips-and-learnings\/\">more details about structure<\/a>).<\/p>\n<p>You should always make sure you\u2019re following the official <a href=\"https:\/\/developers.google.com\/tag-manager\/enhanced-ecommerce\">developer guide<\/a> to the letter.<\/p>\n<h3 id=\"46-the-currency-type-is-just-a-string-with-a-monetary-value\">46. The Currency type is just a string with a monetary value<\/h3>\n<p>If you read the official <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/analyticsjs\/enhanced-ecommerce\">Enhanced Ecommerce developer guide<\/a>, you might have noticed references to a type called \u201ccurrency\u201d.<\/p>\n<div style=\"aspect-ratio: 1782 \/ 708;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/currency-type.jpg\" title=\"Currency data type\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"708\" width=\"1782\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/currency-type.jpg#ZgotmplZ\" alt=\"Currency data type\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Well, there\u2019s no such data type in JavaScript. What the guide means is a string that has a monetary value (without currency symbol). Don\u2019t use a thousand separator (e.g. \u201c1 045.99\u201d), and use the period as the decimal character.<\/p>\n<p>A valid \u201ccurrency\u201d type would be <code>\"1045.99\"<\/code>. Invalid types would be <code>\"1 045.99\"<\/code> and <code>\"1045,99\"<\/code>. Due to loose typing in JavaScript, you could just as well pass it as a number <code>1045.99<\/code>, but that will definitely lead to problems if the number is incorrectly formatted.<\/p>\n<h3 id=\"47-product-scoped-custom-dimensions-and-metrics-need-to-be-formatted-correctly\">47. Product-scoped Custom Dimensions and Metrics need to be formatted correctly<\/h3>\n<p><a href=\"https:\/\/www.simoahava.com\/gtm-tips\/product-scoped-custom-dimensions-and-metrics\/\">Product-scoped Custom Dimensions and Metrics<\/a> 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.<\/p>\n<p>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 <code>products<\/code> array, under each individual product you want to add the dimensions and metrics to. The dimensions and metrics must be named <code>dimensionX<\/code> and <code>metricX<\/code> where X is the index number for the given custom variable.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">{\n  ecommerce: {\n    purchase: {\n      actionField: {\n        ...\n      },\n      products: [{\n        id: <span style=\"color:#a50\">'1'<\/span>,\n        name: <span style=\"color:#a50\">'Shirt'<\/span>,\n        dimension1: <span style=\"color:#a50\">'Red'<\/span>,\n        metric1: <span style=\"color:#099\">132<\/span>,\n        quantity: <span style=\"color:#099\">1<\/span>,\n        price: <span style=\"color:#a50\">'10.99'<\/span>\n      },{\n        id: <span style=\"color:#a50\">'2'<\/span>,\n        name: <span style=\"color:#a50\">'Pants'<\/span>,\n        dimension1: <span style=\"color:#a50\">'Black'<\/span>,\n        dimension2: <span style=\"color:#a50\">'Adidas'<\/span>,\n        metric1: <span style=\"color:#099\">133<\/span>,\n        quantity: <span style=\"color:#099\">1<\/span>,\n        price: <span style=\"color:#a50\">'13.99'<\/span>\n      }]\n    }\n  }\n}\n<\/code><\/pre>\n<\/div>\n<h3 id=\"48-custom-javascript-variable-method-is-more-flexible-than-use-data-layer\">48. Custom JavaScript variable method is more flexible than Use Data Layer<\/h3>\n<p>I always implement Enhanced Ecommerce using the <a href=\"https:\/\/www.simoahava.com\/analytics\/enhanced-ecommerce-with-a-custom-javascript-variable\/\">Custom JavaScript variable method<\/a>. It gives me so much more flexibility, as I can simply create the original <code>dataLayer<\/code> 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.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">var<\/span> order = {{DLV - Order}};\n  <span style=\"color:#00a\">return<\/span> {\n    ecommerce: {\n      purchase: {\n        actionField: {\n          id: order.orderId,\n          revenue: order.price.totalWithTax,\n          tax: order.price.taxValue,\n          affiliation: order.store.name\n        },\n        products: [order.productsForGTM]\n      }\n    }\n  };\n}\n<\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 1756 \/ 418;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/eec-custom-javascript-variable.jpg\" title=\"Enhanced Ecommerce Custom JavaScript Variable\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"418\" width=\"1756\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/eec-custom-javascript-variable.jpg#ZgotmplZ\" alt=\"Enhanced Ecommerce Custom JavaScript Variable\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>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.<\/p>\n<hr\/>\n<h2 id=\"triggers\">Triggers<\/h2>\n<h3 id=\"49-variables-can-only-be-used-to-check-against\">49. Variables can only be used to check against<\/h3>\n<p>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\u2019t use a variable as the condition value itself.<\/p>\n<div style=\"aspect-ratio: 1848 \/ 510;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/variables-in-triggers.jpg\" title=\"Variables in triggers\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"510\" width=\"1848\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/variables-in-triggers.jpg#ZgotmplZ\" alt=\"Variables in triggers\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"50-use-a-custom-javascript-variable-to-check-for-dynamic-values\">50. Use a Custom JavaScript variable to check for dynamic values<\/h3>\n<p>If you DO want to check against dynamic values in your triggers, you can always use a Custom JavaScript variable. Let\u2019s 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:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">return<\/span> {{Click URL}}.indexOf({{Page Hostname}}) &gt; -<span style=\"color:#099\">1<\/span>;\n}\n<\/code><\/pre>\n<\/div>\n<p>This variable returns <code>true<\/code> if the clicked URL contains the current page hostname, and <code>false<\/code> otherwise. Now you can use a trigger like this:<\/p>\n<div style=\"aspect-ratio: 2048 \/ 940;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/dynamic-trigger-variable.jpg\" title=\"Dynamic trigger with Custom JavaScript variable\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"940\" width=\"2048\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/dynamic-trigger-variable.jpg#ZgotmplZ\" alt=\"Dynamic trigger with Custom JavaScript variable\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"51-event-is-implicit-in-all-but-the-custom-event-trigger\">51. <code>'event'<\/code> is implicit in all but the Custom Event trigger<\/h3>\n<p>All triggers require an <code>event<\/code> key in <code>dataLayer<\/code> to fire. Thus, when you create a trigger, they check for the value of <code>event<\/code>, and if there\u2019s a match the trigger fires. Only the Custom Event trigger requires you to explicitly state the value of <code>event<\/code> you want to fire against. Here are the basic trigger types and their implicit <code>event<\/code> values:<\/p>\n<ul>\n<li>\n<p>DOM Ready &#8211; gtm.dom<\/p>\n<\/li>\n<li>\n<p>Page View &#8211; gtm.js<\/p>\n<\/li>\n<li>\n<p>Window Loaded &#8211; gtm.load<\/p>\n<\/li>\n<li>\n<p>Click \/ All Elements &#8211; gtm.click<\/p>\n<\/li>\n<li>\n<p>Click \/ Just Links &#8211; gtm.linkClick<\/p>\n<\/li>\n<li>\n<p>Form submission &#8211; gtm.formSubmit<\/p>\n<\/li>\n<li>\n<p>History Change &#8211; gtm.historyChange<\/p>\n<\/li>\n<li>\n<p>JavaScript Error &#8211; gtm.pageError<\/p>\n<\/li>\n<li>\n<p>Timer &#8211; gtm.timer<\/p>\n<\/li>\n<li>\n<p>Scroll Depth &#8211; gtm.scrollDepth<\/p>\n<\/li>\n<li>\n<p>YouTube Video &#8211; gtm.video<\/p>\n<\/li>\n<\/ul>\n<h3 id=\"52-multiple-trigger-conditions-are-and-multiple-triggers-are-or\">52. Multiple trigger conditions are AND, multiple triggers are OR<\/h3>\n<p>Multiple conditions in a single trigger <strong>must ALL match<\/strong> for the trigger to fire. Thus a trigger like this should never work:<\/p>\n<div style=\"aspect-ratio: 1778 \/ 660;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/multiple-conditions.jpg\" title=\"Multiple trigger conditions\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"660\" width=\"1778\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/multiple-conditions.jpg#ZgotmplZ\" alt=\"Multiple trigger conditions\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Why won\u2019t it work? Because the hostname of the current page can\u2019t be two things at once.<\/p>\n<p>If you add multiple triggers to a tag, then <strong>any one of these will fire the tag<\/strong>. So, if you want your tag to fire when the page hostname is either <a href=\"http:\/\/www.domain.com\">www.domain.com<\/a> or <a href=\"http:\/\/www.other-domain.com\">www.other-domain.com<\/a>, you can create two triggers, one for each hostname, and add both to the tag.<\/p>\n<h3 id=\"53-use-regular-expressions-or-custom-javascript-variables-to-add-optionality-in-a-single-trigger\">53. Use regular expressions or Custom JavaScript variables to add optionality in a single trigger<\/h3>\n<p>There\u2019s an easier way to introduce optionality, though. First, if it\u2019s a simple string check, you can always use <a href=\"http:\/\/www.regular-expressions.info\/quickstart.html\">regular expressions<\/a>.<\/p>\n<div style=\"aspect-ratio: 1802 \/ 580;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/regex-trigger.jpg\" title=\"Regex in triggers\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"580\" width=\"1802\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/regex-trigger.jpg#ZgotmplZ\" alt=\"Regex in triggers\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>If you have more complex logic, a Custom JavaScript variable is your best friend, again.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">var<\/span> hn = {{Page Hostname}},\n      ut = {{DLV - userType}};\n  <span style=\"color:#00a\">if<\/span> (hn === <span style=\"color:#a50\">'www.mydomain.com'<\/span> &amp;&amp; ut === <span style=\"color:#a50\">'visitor'<\/span>) {\n    <span style=\"color:#00a\">return<\/span> <span style=\"color:#a50\">'visitor'<\/span>;\n  }\n  <span style=\"color:#00a\">if<\/span> (hn === <span style=\"color:#a50\">'www.mydomain.com'<\/span> &amp;&amp; ut === <span style=\"color:#a50\">'member'<\/span>) {\n    <span style=\"color:#00a\">return<\/span> <span style=\"color:#a50\">'member'<\/span>;\n  }\n  <span style=\"color:#00a\">if<\/span> (hn === <span style=\"color:#a50\">'www.other-domain.com'<\/span> &amp;&amp; ut === <span style=\"color:#a50\">'loyal'<\/span>) {\n    <span style=\"color:#00a\">return<\/span> <span style=\"color:#a50\">'loyal'<\/span>;\n  }\n  <span style=\"color:#00a\">return<\/span> <span style=\"color:#a50\">'other'<\/span>;\n}\n<\/code><\/pre>\n<\/div>\n<hr\/>\n<h2 id=\"auto-event-trigger\">Auto-event trigger<\/h2>\n<h3 id=\"54-just-links-listens-to-clicks-on-ltagt-elements-and-their-descendants\">54. Just Links listens to clicks on <a> elements and their descendants<\/a><\/h3>\n<p>When you create a <strong>Just Links<\/strong> trigger, it listens to clicks on <code><a\/><\/code> 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 <code>dataLayer<\/code>.<\/p>\n<p>For example, say the page HTML looks like this:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-html\" data-lang=\"html\">div <span style=\"color:#1e90ff\">id<\/span>=<span style=\"color:#a50\">\"content\"<\/span>&gt;\n  a <span style=\"color:#1e90ff\">href<\/span>=<span style=\"color:#a50\">\"https:\/\/www.google.com\/\"<\/span>&gt;\n    span&gt;Google<span style=\"color:#1e90ff;font-weight:bold\">span<\/span>&gt;\n  <span style=\"color:#1e90ff;font-weight:bold\">a<\/span>&gt;\n<span style=\"color:#1e90ff;font-weight:bold\">div<\/span>&gt;<\/code><\/pre>\n<\/div>\n<p>If someone clicks on the link, the click actually falls on the <code><span\/><\/code> element, but the Just Links trigger propagates the click to the <code><a\/><\/code> element, and returns that for you to leverage with Auto-event variables.<\/p>\n<h3 id=\"55-all-elements-listens-to-all-clicks\">55. All Elements listens to all clicks<\/h3>\n<p>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 <code><span\/><\/code> element because that\u2019s the element that was actually clicked.<\/p>\n<h3 id=\"56-form-listens-to-a-submit-event-dispatched-by-a-ltformgt-element\">56. Form listens to a <code>submit<\/code> event dispatched by a <\/h3>\n<form> element<\/p>\n<p>The Form trigger only works with an actual <code\/> element submitted with default HTML form functionality. This means that there actually needs to be a <code>submit<\/code> event dispatched by the form, and it must be allowed to bubble up.<\/p>\n<p>Any custom server-side validation, suppressing of the <code>submit<\/code> event, or customized form handling will result in the Form trigger not working. Thus, the Form trigger is typically the trigger you\u2019ll have the most difficulties with due to the ridiculously diverse number of ways that forms can be handled with JavaScript.<\/p>\n<h3 id=\"57-history-change-listens-to-interactions-with-the-browser-history-api\">57. History Change listens to interactions with the browser history API<\/h3>\n<p>The History Change trigger listens for the following browser history API events: <code>hashchange<\/code>, <code>pushState<\/code>, <code>replaceState<\/code> and <code>popstate<\/code>.<\/p>\n<p>Typically these are used on single-page websites, where page transitions are done without a page refresh.<\/p>\n<p>When creating History Change triggers, you\u2019ll typically want to work mainly with <code>pushState<\/code> and <code>replaceState<\/code>, as those are managed by the by the site code itself. <code>popstate<\/code> and <code>hashchange<\/code> can be triggered automatically by the web browser (and there are differences between browsers), which might lead to inaccuracies in your tracking.<\/p>\n<h3 id=\"58-error-listens-to-uncaught-javascript-exceptions\">58. Error listens to uncaught JavaScript exceptions<\/h3>\n<p>The Error triggers listens to <strong>uncaught<\/strong> JavaScript errors that occur on the website. If there\u2019s a <code>try...catch<\/code> block anywhere in the error path, this trigger will not react to it.<\/p>\n<p>Do note that Custom JavaScript variables automatically catch all exceptions, so this trigger will not help you debug errors in Custom JavaScript variables.<\/p>\n<h3 id=\"59-all-triggers-but-the-click--all-elements-trigger-require-that-the-original-event-bubble-up\">59. All triggers but the Click \/ All Elements trigger require that the original event bubble up<\/h3>\n<p>Google Tag Manager attaches its auto-event listeners to the <code>document<\/code> 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\u2019t exist when GTM first loads.<\/p>\n<p>For this type of <em>event delegation<\/em> to work, the event must bubble up to the top of the document. It\u2019s surprising how often bubbling is cancelled, leading to GTM\u2019s events not working.<\/p>\n<p>The only exception is the All Elements trigger, which uses the <em>capture<\/em> phase of the event path. This means that even if bubbling is stopped, the <em>capture<\/em> phase can still record the click. So if you find your Just Links trigger isn\u2019t working, you can recreate the same logic with an All Elements trigger and some clever CSS selector \/ Custom JavaScript variable work.<\/p>\n<p>For more information, see e.g. this article on <a href=\"https:\/\/www.simoahava.com\/gtm-tips\/fix-problems-with-gtm-listeners\/\">GTM listener issues<\/a>, and this on <a href=\"https:\/\/www.simoahava.com\/analytics\/capturing-the-correct-element-in-google-tag-manager\/\">element capturing with the All Elements trigger<\/a>.<\/p>\n<h3 id=\"60-check-validation-checks-for-eventpreventdefault\">60. Check Validation checks for <code>event.preventDefault()<\/code><\/h3>\n<p>If you have <strong>Check Validation<\/strong> checked in your Just Links or Form trigger, the trigger will only fire if the event\u2019s default action is not cancelled.<\/p>\n<div style=\"aspect-ratio: 1856 \/ 628;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/check-validation.jpg\" title=\"Check Validation\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"628\" width=\"1856\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/check-validation.jpg#ZgotmplZ\" alt=\"Check Validation\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>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\u2019ll want to have Check Validation checked, because you probably don\u2019t want to track clicks on links that don\u2019t redirect or submissions of forms that don\u2019t actually submit.<\/p>\n<h3 id=\"61-wait-for-tags-pauses-the-original-event-but-be-careful\">61. Wait for Tags pauses the original event, but be careful<\/h3>\n<p>If you use the <strong>Wait for Tags<\/strong> 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.<\/p>\n<p>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.<\/p>\n<p>However, there are many ways to do custom link redirects and form submissions. When GTM pauses the event, it\u2019s 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.<\/p>\n<p>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.<\/p>\n<p>So, when you use the Wait for Tags option, <strong>always remember to test the pages where the trigger is active<\/strong>. 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.<\/p>\n<h3 id=\"62-enable-this-trigger-when-vs-this-trigger-fires-on\">62. Enable this trigger when\u2026 vs. This trigger fires on\u2026<\/h3>\n<p>If you do check Wait for Tags or Check Validation, you\u2019ll see the \u201cEnable this trigger when\u2026\u201d option appear in the trigger settings.<\/p>\n<div style=\"aspect-ratio: 1894 \/ 1150;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/enable-this-trigger.jpg\" title=\"Enable this trigger vs. fire on\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1150\" width=\"1894\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/enable-this-trigger.jpg#ZgotmplZ\" alt=\"Enable this trigger vs. fire on\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The \u201cEnable this trigger when\u2026\u201d option is for determining <strong>on which pages the trigger should listen to actions<\/strong>. Thus you can use it to have the trigger listen only on pages where you have thoroughly tested the trigger doesn\u2019t mess with site functionality.<\/p>\n<p>It\u2019s a good idea to start with a generic <strong>Page URL contains \/<\/strong> 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.<\/p>\n<p>The \u201cThis trigger fires on\u2026\u201d 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\u2019ll add your \u201cClick URL\u201d and \u201cForm ID\u201d conditions, for example.<\/p>\n<h3 id=\"63-data-layer-object-composition-of-an-auto-event\">63. Data Layer object composition of an auto-event<\/h3>\n<p>When an auto-event trigger fires, the following items are pushed into <code>dataLayer<\/code>:<\/p>\n<ul>\n<li>\n<p><code>event<\/code> &#8211; gets the value of the trigger event that was registered.<\/p>\n<\/li>\n<li>\n<p><code>gtm.element<\/code> &#8211; reference to the HTML element that was the target of the event.<\/p>\n<\/li>\n<li>\n<p><code>gtm.elementClasses<\/code> &#8211; string with the value from the <code>class<\/code> attribute of the target element (if any).<\/p>\n<\/li>\n<li>\n<p><code>gtm.elementId<\/code> &#8211; string with the value from the <code>id<\/code> attribute of the target element (if any).<\/p>\n<\/li>\n<li>\n<p><code>gtm.elementTarget<\/code> &#8211; string with the value from the <code>target<\/code> attribute of the target element (if any).<\/p>\n<\/li>\n<li>\n<p><code>gtm.elementUrl<\/code> &#8211; string with the value from the <code>href<\/code> or <code>action<\/code> attribute of the target element (if any).<\/p>\n<\/li>\n<li>\n<p><code>gtm.triggers<\/code> &#8211; a regular expression which determines if the trigger that activated is enabled on the current page.<\/p>\n<\/li>\n<\/ul>\n<p>In addition to these, you might see variables like <code>gtm.uniqueEventId<\/code>, <code>eventCallback<\/code>, <code>eventTimeout<\/code> and <code>eventReporter<\/code>. These are internal to GTM, but suffice to say that they govern how the Wait for Tags option works, among other things.<\/p>\n<h3 id=\"64-auto-event-variable\">64. Auto-event variable<\/h3>\n<p>If you\u2019re 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.<\/p>\n<p>For example, if you\u2019ve added a custom data attribute <code>data-gtm-cta=\"Subscribe call to action\"<\/code>, 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:<\/p>\n<div style=\"aspect-ratio: 1804 \/ 674;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/auto-event-variable.jpg\" title=\"Auto-event variable\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"674\" width=\"1804\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/auto-event-variable.jpg#ZgotmplZ\" alt=\"Auto-event variable\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This comes in very handy, as you won\u2019t be limited to the rather small number of available built-in variables.<\/p>\n<h3 id=\"65-matches-css-selector-with-the-click--form-element-built-in-variable\">65. Matches CSS selector with the Click \/ Form Element built-in variable<\/h3>\n<p>The <strong>matches CSS selector<\/strong> is <a href=\"https:\/\/www.simoahava.com\/analytics\/matches-css-selector-operator-in-gtm-triggers\/\">one of the most effective ways<\/a> 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.<\/p>\n<p>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\u2019s say you have the following HTML structure:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-html\" data-lang=\"html\">div <span style=\"color:#1e90ff\">id<\/span>=<span style=\"color:#a50\">\"products\"<\/span>&gt;\n  div&gt;\n    div&gt;\n      a <span style=\"color:#1e90ff\">href<\/span>=<span style=\"color:#a50\">\"some-product.html\"<\/span>&gt;Product\/a&gt;\n    <span style=\"color:#1e90ff;font-weight:bold\">div<\/span>&gt;\n  <span style=\"color:#1e90ff;font-weight:bold\">div<\/span>&gt;\n<span style=\"color:#1e90ff;font-weight:bold\">div<\/span>&gt;\ndiv <span style=\"color:#1e90ff\">id<\/span>=<span style=\"color:#a50\">\"services\"<\/span>&gt;\n  div&gt;\n    div&gt;\n      a <span style=\"color:#1e90ff\">href<\/span>=<span style=\"color:#a50\">\"some-service.html\"<\/span>&gt;Service<span style=\"color:#1e90ff;font-weight:bold\">a<\/span>&gt;\n    <span style=\"color:#1e90ff;font-weight:bold\">div<\/span>&gt;\n  <span style=\"color:#1e90ff;font-weight:bold\">div<\/span>&gt;\n<span style=\"color:#1e90ff;font-weight:bold\">div<\/span>&gt;<\/code><\/pre>\n<\/div>\n<p>Now, you only want your Just Links trigger to fire when the user clicks the \u201cProduct\u201d link. However, as you can see, the HTML structures between \u201cProduct\u201d and \u201cService\u201d 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 <code>div<\/code> with the id <code>\"products\"<\/code>:<\/p>\n<div style=\"aspect-ratio: 1798 \/ 720;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/matches-css-selector.jpg\" title=\"Matches css selector\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"720\" width=\"1798\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/matches-css-selector.jpg#ZgotmplZ\" alt=\"Matches css selector\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Just remember: <strong>matches CSS selector only works against an HTML element<\/strong>. So don\u2019t try to match a selector against the Click ID or Click URL variables, for example. Only Click \/ Form Element will do.<\/p>\n<hr\/>\n<h3 id=\"66-code-is-automatically-minified\">66. Code is automatically minified<\/h3>\n<p>Custom HTML tags automatically minify \/ uglify the JavaScript, so no need to do so yourself.<\/p>\n<div style=\"aspect-ratio: 1796 \/ 1036;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/minified-custom-html-tag.jpg\" title=\"Minified custom HTML tag\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1036\" width=\"1796\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/minified-custom-html-tag.jpg#ZgotmplZ\" alt=\"Minified custom HTML tag\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"67-code-is-injected-to-the-end-of-ltbodygt\">67. Code is injected to the end of <\/h3>\n<p>When you write HTML code in a Custom HTML tag, the entire code block is always injected to the end of <code\/> when the trigger fires for the tag.<\/p>\n<p>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.<\/p>\n<h3 id=\"68-can-be-used-to-add-any-html-elements\">68. Can be used to add any HTML elements<\/h3>\n<p>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: <code>meta<\/code>, <code>link<\/code>, <code>style<\/code>, <code>video<\/code>, <code>script<\/code>, and so forth.<\/p>\n<p>Naturally, with some JavaScript DOM magic, you can also <strong>modify<\/strong> existing elements or even remove them entirely from the page.<\/p>\n<h3 id=\"69-the-documentwrite-option-is-fixed-to-prevent-the-site-from-breaking\">69. The <code>document.write<\/code> option is fixed to prevent the site from breaking<\/h3>\n<p>If you are adding a script which makes use of <code>document.write<\/code> to place elements on the page, you will need to check the \u201cSupport document.write\u201d option in the Custom HTML tag editor.<\/p>\n<div style=\"aspect-ratio: 1988 \/ 666;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/support-document-write.jpg\" title=\"Support document.write\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"666\" width=\"1988\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/support-document-write.jpg#ZgotmplZ\" alt=\"Support document.write\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>If you don\u2019t check this option, you run the risk of the <code>document.write<\/code> command clearing the entire page of all contents because of how <code>document.write<\/code> works post-page-load.<\/p>\n<p>Dan Wilkerson has written a great <a href=\"https:\/\/www.simoahava.com\/gtm-tips\/using-document-write-safely-gtm-tags\/\">guest post on this topic<\/a>, so remember to check it out for more information.<\/p>\n<h3 id=\"70-variables-are-automatically-renamed-in-custom-html-tags-too\">70. Variables are automatically renamed in Custom HTML tags, too<\/h3>\n<p>Did you know that when you create a variable reference in GTM with <strong>{{Variable Name}}<\/strong> and you then change the variable name to <strong>{{Some Other Variable Name}}<\/strong>, all references are automatically updated?<\/p>\n<p>Well this applies to Custom HTML tags, too, so you don\u2019t 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.<\/p>\n<hr\/>\n<h2 id=\"built-in-variables\">Built-in variables<\/h2>\n<h3 id=\"71-need-to-be-enabled\">71. Need to be enabled<\/h3>\n<p>The only Built-in Variables enabled by default in your web container are <strong>Event<\/strong>, <strong>Page URL<\/strong>, <strong>Page Path<\/strong>, <strong>Page Hostname<\/strong> and <strong>Referrer<\/strong>.<\/p>\n<p>To enable others, go to <strong>Variables<\/strong> and click the red <strong>CONFIGURE<\/strong> under the heading \u201cBuilt-In Variables\u201d.<\/p>\n<div style=\"aspect-ratio: 2090 \/ 886;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/built-in-variables-enable.jpg\" title=\"Enable built-in variables\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"886\" width=\"2090\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/built-in-variables-enable.jpg#ZgotmplZ\" alt=\"Enable built-in variables\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>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.<\/p>\n<h3 id=\"72-click-and-form-variables-are-copies-of-each-other\">72. Click and Form variables are copies of each other<\/h3>\n<p>You might have noticed that there\u2019s 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.<\/p>\n<p>These are identical in functionality, so you really only need to enable either Click or Form Built-in variables.<\/p>\n<hr\/>\n<h2 id=\"custom-javascript-variables\">Custom JavaScript variables<\/h2>\n<h3 id=\"73-must-be-anonymous-functions\">73. Must be anonymous functions<\/h3>\n<p>When you create a Custom JavaScript variable, it should be an anonymous function. Yes, you can name it, but it doesn\u2019t matter since there\u2019s no way to call the variable with its name, as it\u2019s locally scoped to whatever execution context invokes the variable when your container needs to do so.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#aaa;font-style:italic\">\/\/ Not ideal:\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">function<\/span> clickTextLowercase() {\n  <span style=\"color:#00a\">return<\/span> {{Click Text}}.toLowerCase();\n}\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Use this:\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">return<\/span> {{Click Text}}.toLowerCase();\n}\n<\/code><\/pre>\n<\/div>\n<p>To avoid any potential namespace conflicts, just use an anonymous function, since you call GTM\u2019s variables with the  syntax and not with whatever name you give the method itself.<\/p>\n<h3 id=\"74-must-have-a-return-statement\">74. Must have a <code>return<\/code> statement<\/h3>\n<p>All Custom JavaScript variables must have a <code>return<\/code> statement or Google Tag Manager will throw an error when you try to create a version of the container.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#aaa;font-style:italic\">\/\/ Will not work:\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">if<\/span> (<span style=\"color:#00a\">true<\/span>) {}\n}\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Works:\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">if<\/span> (<span style=\"color:#00a\">true<\/span>) {}\n  <span style=\"color:#00a\">return<\/span>;\n}\n<\/code><\/pre>\n<\/div>\n<h3 id=\"75-can-be-used-to-return-another-function\">75. Can be used to return another function<\/h3>\n<p>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 <strong>closure<\/strong>.<\/p>\n<p>For example, let\u2019s say I want to create a utility function that takes a string as an argument and reverses all letters in it. First, I\u2019d create a Custom JavaScript variable like this:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">return<\/span> <span style=\"color:#00a\">function<\/span>(str) {\n    <span style=\"color:#00a\">return<\/span> str.split(<span style=\"color:#a50\">''<\/span>).reverse().join(<span style=\"color:#a50\">''<\/span>);\n  };\n}\n<\/code><\/pre>\n<\/div>\n<p>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:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-html\" data-lang=\"html\">script&gt;\n  (<span style=\"color:#00a\">function<\/span>() {\n    <span style=\"color:#aaa;font-style:italic\">\/\/ Create a reference to the Custom JavaScript variable we just created\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> reverseString = {{JS - ReverseString}}\n    <span style=\"color:#0aa\">window<\/span>.dataLayer.push({\n      event: <span style=\"color:#a50\">'reverseComplete'<\/span>,\n      reversedString: reverseString(<span style=\"color:#a50\">\"Reverse me!\"<\/span>)\n    });\n  })();\n<span style=\"color:#1e90ff;font-weight:bold\">script<\/span>&gt;<\/code><\/pre>\n<\/div>\n<p>This Custom HTML tag calls the function returned by the Custom JavaScript variable with the string \u201cReverse me!\u201d, resulting in a <code>dataLayer.push()<\/code> where the key <code>reversedString<\/code> will have the value \u201c!em esreveR\u201d.<\/p>\n<h3 id=\"76-should-avoid-side-effects\">76. Should avoid side effects<\/h3>\n<p>Custom JavaScript variables should avoid side effects. In other words, they shouldn\u2019t do anything except process an input and produce an output. Any transformations or mutations should happen in local scope alone.<\/p>\n<p>If you don\u2019t respect this, you might run into all sorts of issues due to the fact that Google Tag Manager\u2019s resolution of variables is unpredictable and can\u2019t be counted on to only happen once per tag.<\/p>\n<p>Here are some things to avoid:<\/p>\n<ul>\n<li>\n<p>Modifying, creating, or deleting items in the global <code>window<\/code> namespace.<\/p>\n<\/li>\n<li>\n<p>Creating or processing custom HTTP requests.<\/p>\n<\/li>\n<li>\n<p>Setting or removing items in globally available APIs, such as <code>dataLayer<\/code>, <code>google_tag_manager<\/code>, <code>document.cookie<\/code> and <code>localStorage<\/code>.<\/p>\n<\/li>\n<\/ul>\n<p>If you do want to modify global state, use Custom HTML tags or closures instead.<\/p>\n<h3 id=\"77-can-refer-to-other-variables\">77. Can refer to other variables<\/h3>\n<p>This might seem like a no-brainer, but you can freely refer to other variables in Custom JavaScript variables, too.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">var<\/span> hostname = {{Page Hostname}};\n  <span style=\"color:#00a\">return<\/span> hostname === <span style=\"color:#a50\">'www.domain.com'<\/span> ? <span style=\"color:#a50\">'Main domain'<\/span> : <span style=\"color:#a50\">'Other domain'<\/span>;\n}\n<\/code><\/pre>\n<\/div>\n<hr\/>\n<h2 id=\"tag-sequencing\">Tag sequencing<\/h2>\n<h3 id=\"78-setup-and-cleanup-are-fired-with-the-main-tag-regardless-of-their-own-triggers\">78. Setup and cleanup are fired with the main tag, regardless of their own triggers<\/h3>\n<p>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.<\/p>\n<p>So here\u2019s a tip: When creating Setup and Cleanup tags, <strong>use them only for that purpose<\/strong> and do not add any triggers to them. This way you will never run the risk of these tags firing when they shouldn\u2019t, as they are strictly bound to whatever sequence they are in.<\/p>\n<h3 id=\"79-use-onhtmlsuccess-and-onhtmlfailure-to-signal-that-the-sequence-can-continue\">79. Use <code>onHtmlSuccess()<\/code> and <code>onHtmlFailure()<\/code> to signal that the sequence can continue<\/h3>\n<p>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 <code>onHtmlSuccess()<\/code> and <code>onHtmlFailure()<\/code> methods.<\/p>\n<p>For these to work, you first need to enable Built-in variables <strong>Container ID<\/strong> and <strong>HTML ID<\/strong>.<\/p>\n<p>Here\u2019s 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.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-html\" data-lang=\"html\">script&gt;\n  (<span style=\"color:#00a\">function<\/span>($) {\n    $.post(<span style=\"color:#a50\">'\/test.php'<\/span>)\n      .done(<span style=\"color:#00a\">function<\/span>() {\n        google_tag_manager[{{Container ID}}].onHtmlSuccess({{HTML ID}});\n      })\n      .fail(<span style=\"color:#00a\">function<\/span>() {\n        google_tag_manager[{{Container ID}}].onHtmlFailure({{HTML ID}});\n      });\n  })(jQuery);\n<span style=\"color:#1e90ff;font-weight:bold\">script<\/span>&gt;<\/code><\/pre>\n<\/div>\n<p>If you didn\u2019t have the <code>onHtmlSuccess\/Failure()<\/code> 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\u2019t 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.<\/p>\n<h3 id=\"80-use-datalayerset-to-change-data-layer-variable-values-mid-sequence\">80. Use <code>dataLayer.set()<\/code> to change Data Layer variable values mid-sequence<\/h3>\n<p>If you want to change some value in GTM\u2019s Data Layer while in the middle of a sequence, you can\u2019t use <code>dataLayer.push()<\/code>, because GTM freezes its state for the duration of each message that is pushed into <code>dataLayer<\/code>.<\/p>\n<p>However, there\u2019s a workaround. You CAN update the value of GTM\u2019s Data Layer variables, even though the same <code>dataLayer<\/code> message is still being processed. To do this, you need to use the <code>google_tag_manager[{{Container ID}}].dataLayer.set('keyName', 'value')<\/code> interface you learned of here.<\/p>\n<hr\/>\n<h2 id=\"tag-settings\">Tag settings<\/h2>\n<h3 id=\"81-once-per-page-is-once-per-page-load-once-per-event-is-once-per-gtm-event\">81. Once per page is once per page load, once per event is once per GTM event<\/h3>\n<p>There are three options in the <strong>Tag firing options<\/strong> menu that you can find at the end of each tag\u2019s settings.<\/p>\n<div style=\"aspect-ratio: 1898 \/ 792;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/tag-firing-options.jpg\" title=\"Tag firing options\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"792\" width=\"1898\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/tag-firing-options.jpg#ZgotmplZ\" alt=\"Tag firing options\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>Unlimited<\/strong> means the tag will fire whenever its triggers fire &#8211; no restrictions.<\/p>\n<p><strong>Once per event<\/strong> 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\u2019s a chance that some of these have overlapping trigger conditions, <strong>Once per event<\/strong> ensures that the tag fires just once per click.<\/p>\n<p><strong>Once per page<\/strong> means that the tag will fire only once per page load. No matter what triggers you have attached to it &#8211; 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.<\/p>\n<h3 id=\"82-tag-priority-is-for-a-single-gtm-event---doesnt-necessarily-mean-tags-are-completed-in-the-given-order\">82. Tag priority is for a single GTM event &#8211; doesn\u2019t necessarily mean tags are completed in the given order<\/h3>\n<p>The <strong>Tag priority<\/strong> 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.<\/p>\n<p>Note that Tag priority only establishes the order in which tags <strong>begin execution<\/strong>. It has no implications on when they <strong>complete<\/strong>. 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.<\/p>\n<hr\/>\n<h2 id=\"workspaces\">Workspaces<\/h2>\n<h3 id=\"83-you-will-always-have-at-least-one-workspace\">83. You will always have at least one workspace<\/h3>\n<p>Workspaces have come to stay, and it\u2019s impossible to use Google Tag Manager without a workspace.<\/p>\n<p>Thus, even if you try to delete all workspaces, GTM will always leave you with one.<\/p>\n<h3 id=\"84-when-you-create-a-version-out-of-a-workspace-that-workspace-is-deleted\">84. When you create a version out of a workspace, that workspace is deleted<\/h3>\n<p>Workspaces are ephemeral &#8211; they only exist until a version is created from them. Once you create a version, it\u2019s as if the workspace never existed. Thus workspaces shouldn\u2019t 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.<\/p>\n<h3 id=\"85-you-dont-have-to-update-your-workspace-until-you-are-ready-to-create-a-version\">85. You don\u2019t have to update your workspace until you are ready to create a version<\/h3>\n<p>When someone else updates the latest container version, all the workspaces in the container need to be updated. You\u2019ll know this has happened when you see the following notice in the GTM UI:<\/p>\n<div style=\"aspect-ratio: 1716 \/ 572;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/update-workspace.jpg\" title=\"Update workspace\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"572\" width=\"1716\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/update-workspace.jpg#ZgotmplZ\" alt=\"Update workspace\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You don\u2019t <em>have<\/em> 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.<\/p>\n<p>Nevertheless, the sooner you update the better. Why? Because once merges start piling up, you\u2019ll have a hard time resolving all the conflicts at once.<\/p>\n<hr\/>\n<h2 id=\"amp-container\">AMP container<\/h2>\n<h3 id=\"86-no-custom-javascript-or-custom-html\">86. No Custom JavaScript or Custom HTML<\/h3>\n<p>Accelerated Mobile Pages place some pretty strict restrictions on what you can use Google Tag Manager for. You can\u2019t 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.<\/p>\n<p>So no Custom JavaScript variables or Custom HTML tags in the AMP container, unfortunately.<\/p>\n<h3 id=\"87-amp-is-very-restricting\">87. AMP is very restricting<\/h3>\n<p>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.<\/p>\n<p>It\u2019s a good idea to closely follow the <a href=\"https:\/\/www.ampproject.org\/docs\/reference\/components\/amp-analytics\">amp-analytics<\/a> project, as it contains the latest release details for the amp-analytics module. This is the module used by Google Tag Manager\u2019s AMP container.<\/p>\n<h3 id=\"88-client-id-is-currently-ambiguous\">88. Client ID is (currently) ambiguous<\/h3>\n<p>Universal Analytics\u2019 Client ID is problematic with AMP, especially when run through the GTM container. There are actually <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/amp-analytics\/client-id#client_id_scenarios\">four<\/a> different Client ID scenarios for AMP analytics.<\/p>\n<ol>\n<li>\n<p>When visiting the regular site (no AMP): <code>_ga<\/code> cookie stores the Universal Analytics Client ID.<\/p>\n<\/li>\n<li>\n<p>When visiting the regular site (AMP): <code>AMP_ECID_GOOGLE<\/code> cookie stores a randomly generated AMP Client ID.<\/p>\n<\/li>\n<li>\n<p>When visiting the site via the AMP cache: the AMP client ID is stored in <code>localStorage<\/code> on the ampproject.org domain.<\/p>\n<\/li>\n<li>\n<p>When visiting the site via Google search: the AMP client ID is stored on google.com and ampproject.org in <code>localStorage<\/code>.<\/p>\n<\/li>\n<\/ol>\n<p>In all of these scenarios, it\u2019s possible that the Client ID is different, which means that you\u2019ll 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.<\/p>\n<p>Some time ago, Dan Wilkerson and I <a href=\"https:\/\/www.simoahava.com\/analytics\/google-analytics-client-id-amp-pages\/\">wrote a hack around this<\/a>, 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 <code>_ga<\/code> cookie.<\/p>\n<h3 id=\"89-new-triggers-eg-scroll\">89. New triggers, e.g. scroll<\/h3>\n<p>AMP introduces a bunch of really useful triggers, which I hope we\u2019ll see in GTM for web soon, too.<\/p>\n<div style=\"aspect-ratio: 1786 \/ 714;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/amp-triggers.jpg\" title=\"AMP triggers\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"714\" width=\"1786\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/amp-triggers.jpg#ZgotmplZ\" alt=\"AMP triggers\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Since you can\u2019t 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\u2019s not much room to configure the triggers. AMP is restricted in this way, too.<\/p>\n<div style=\"aspect-ratio: 1578 \/ 704;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/scroll-trigger.jpg\" title=\"Scroll trigger in AMP\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"704\" width=\"1578\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/scroll-trigger.jpg#ZgotmplZ\" alt=\"Scroll trigger in AMP\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>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.<\/p>\n<h3 id=\"90-can-be-augmented-with-the-on-page-json-configuration\">90. Can be augmented with the on-page JSON configuration<\/h3>\n<p>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.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-html\" data-lang=\"html\">  ...\n  <span style=\"color:#aaa;font-style:italic\"><!-- AMP Analytics --><\/span>script <span style=\"color:#1e90ff\">async<\/span> <span style=\"color:#1e90ff\">custom-element<\/span>=<span style=\"color:#a50\">\"amp-analytics\"<\/span> <span style=\"color:#1e90ff\">src<\/span>=<span style=\"color:#a50\">\"https:\/\/cdn.ampproject.org\/v0\/amp-analytics-0.1.js\"<\/span>&gt;<span style=\"color:#1e90ff;font-weight:bold\">script<\/span>&gt;\n<span style=\"color:#1e90ff;font-weight:bold\">head<\/span>&gt;\nbody&gt;\n  <span style=\"color:#aaa;font-style:italic\"><!-- Google Tag Manager --><\/span>\n  amp-analytics <span style=\"color:#1e90ff\">config<\/span>=<span style=\"color:#a50\">\"https:\/\/www.googletagmanager.com\/amp.json?id=GTM-XXXXX&amp;gtm.url=SOURCE_URL\"<\/span> <span style=\"color:#1e90ff\">data-credentials<\/span>=<span style=\"color:#a50\">\"include\"<\/span>&gt;<span style=\"color:#1e90ff;font-weight:bold\">amp-analytics<\/span>&gt;\n  script <span style=\"color:#1e90ff\">type<\/span>=<span style=\"color:#a50\">\"application\/json\"<\/span>&gt;\n  {\n    <span style=\"color:#a50\">\"vars\"<\/span>: {\n      <span style=\"color:#a50\">\"pageType\"<\/span>: <span style=\"color:#a50\">\"profile\"<\/span>,\n      <span style=\"color:#a50\">\"userType\"<\/span>: <span style=\"color:#a50\">\"member\"<\/span>\n    }\n  }\n  <span style=\"color:#1e90ff;font-weight:bold\">script<\/span>&gt;<\/code><\/pre>\n<\/div>\n<p>This is a pretty simple way to add some web server logic to your GTM AMP container.<\/p>\n<hr\/>\n<h2 id=\"gtm-for-mobile\">GTM for mobile<\/h2>\n<h3 id=\"91-not-the-same-as-gtm-for-web\">91. Not the same as GTM for web<\/h3>\n<p>Let\u2019s address the elephant in the room first. If AMP was restricted, then GTM for mobile is doubly so. Why? Because you\u2019re 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\u2019t add runtime code to it without it being sanctioned by the platform the app is running on.<\/p>\n<p>Also, since this isn\u2019t the web anymore, there\u2019s no \u201cCustom HTML\u201d or \u201cCustom JavaScript\u201d here, either. Android apps use Java, and iOS relies on Swift and Objective-C.<\/p>\n<p>Due to these restrictions, it\u2019s 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.<\/p>\n<div style=\"aspect-ratio: 1618 \/ 1534;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/firebase-templates.jpg\" title=\"Firebase templates\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1534\" width=\"1618\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/firebase-templates.jpg#ZgotmplZ\" alt=\"Firebase templates\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>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.<\/p>\n<p>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\u2019t too impressive yet, but this will surely change in the future.<\/p>\n<h3 id=\"92-an-sdk-you-need-to-download-and-add-to-the-project\">92. An SDK you need to download and add to the project<\/h3>\n<p>Google Tag Manager isn\u2019t a container file that is just fetched from the web and then everything works nicely. No, it\u2019s actually a combination of SDKs (software development kit) and container binaries \/ JSON files that you need to integrate into the app itself.<\/p>\n<p>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.<\/p>\n<div style=\"aspect-ratio: 1822 \/ 806;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/firebase-dependencies.jpg\" title=\"Firebase dependencies\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"806\" width=\"1822\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/firebase-dependencies.jpg#ZgotmplZ\" alt=\"Firebase dependencies\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The app fetches the latest container version if a \u201cfresh\u201d container is available. You can\u2019t always trust that a fresh container can be fetched, so it\u2019s 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\u2019s an updated container version that needs to be added to the project before going live.<\/p>\n<h3 id=\"93-ga-tracker-object-is-not-exposed\">93. GA tracker object is not exposed<\/h3>\n<p>Google Tag Manager doesn\u2019t 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\u2019s Client ID is, for example, there\u2019s no command that lets you dig that information from the available GTM interfaces.<\/p>\n<p>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.<\/p>\n<h3 id=\"94-gtm-legacy-uses-datalayer\">94. GTM legacy uses <code>dataLayer<\/code><\/h3>\n<p>GTM for mobile, before Firebase, is now called \u201cLegacy\u201d. If you\u2019re used to GTM for web, the legacy SDK should be quite familiar to you, since it also uses a <code>dataLayer<\/code> construct to pass messages to Google Tag Manager. As before, there is the <code>event<\/code> 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.<\/p>\n<div style=\"aspect-ratio: 1692 \/ 792;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/ios-analytics-module.jpg\" title=\"iOS analytics module\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"792\" width=\"1692\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/ios-analytics-module.jpg#ZgotmplZ\" alt=\"iOS analytics module\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Note that unlike with GTM for web, there is no differentiation between <code>dataLayer<\/code> the message queue and GTM\u2019s internal data model. The <code>dataLayer<\/code> structure acts as both.<\/p>\n<h3 id=\"95-latest-version-of-gtm-for-mobile-uses-firebase\">95. Latest version of GTM for mobile uses Firebase<\/h3>\n<p>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.<\/p>\n<div style=\"aspect-ratio: 1664 \/ 424;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/firebase-analytics.jpg\" title=\"Firebase Analytics via GTM\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"424\" width=\"1664\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/firebase-analytics.jpg#ZgotmplZ\" alt=\"Firebase Analytics via GTM\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>When you want to use GTM with Firebase, you need to actually <em>use Firebase<\/em>. 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.<\/p>\n<h3 id=\"96-you-can-intercept-firebase-events-using-gtm\">96. You can intercept Firebase events using GTM<\/h3>\n<p>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\u2019ve configured in the container, and if a tag is set to fire on a specific Firebase event, it will go off.<\/p>\n<div style=\"aspect-ratio: 1822 \/ 988;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/02\/firebase-ga-tag.jpg\" title=\"Firebase GA Tag\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"988\" width=\"1822\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/02\/firebase-ga-tag.jpg#ZgotmplZ\" alt=\"Firebase GA Tag\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>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.<\/p>\n<h3 id=\"97-firebase-gtm-has-imperfect-support-for-ga-enhanced-ecommerce\">97. Firebase GTM has (imperfect) support for GA Enhanced Ecommerce<\/h3>\n<p>At the time of writing, you can\u2019t configure Universal Analytics Ecommerce tags via Firebase GTM. The reason is that GTM\u2019s data model does not support array structures, which is how you\u2019d need to send product data to Google Analytics.<\/p>\n<p>Hopefully we\u2019ll see <a href=\"https:\/\/support.google.com\/tagmanager\/answer\/7003315?hl=en\">support for Ecommerce soon<\/a>. Until then, if you need to collect Ecommerce data to Google Analytics, you might want to use the GTM Legacy SDK or GA SDK.<\/p>\n<p><strong>Update<\/strong>: Support for Enhanced Ecommerce has finally reached Firebase (<a href=\"https:\/\/developers.google.com\/tag-manager\/ios\/v5\/enhanced-ecommerce\">iOS<\/a> and <a href=\"https:\/\/developers.google.com\/tag-manager\/android\/v5\/enhanced-ecommerce\">Android<\/a>). Still missing product-scoped custom dimensions and metrics, though! (Thanks <strong>Yuhui<\/strong> for the tip in the comments).<\/p>\n<hr\/>\n<h2 id=\"other-stuff\">Other stuff<\/h2>\n<h3 id=\"98-debugging-is-a-complex-process\">98. Debugging is a complex process<\/h3>\n<p>End-to-end debugging with Google Tag Manager isn\u2019t just a case of opening up Preview mode and being satisfied with what you see. It\u2019s a complex process, involving not only front-end conflicts in your site code, but also things like race conditions, unresponsive tag endpoints, and <code>dataLayer<\/code> <a href=\"https:\/\/www.simoahava.com\/analytics\/automated-tests-for-google-tag-managers-datalayer\/\">automated tests<\/a>.<\/p>\n<p>I recommend you take a look at this article: <a href=\"https:\/\/www.simoahava.com\/gtm-tips\/debugging-tag-execution-properly\/\">#GTMTips: Debugging Tag Execution Properly<\/a>. Make sure you familiarize yourself with the Network tab of your browser\u2019s developer tools. If Preview mode says that your tags fire but you don\u2019t 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 <code>\/collect<\/code> correctly. <a href=\"https:\/\/support.google.com\/analytics\/answer\/6277302?hl=en\">Google Tag Assistant Recordings<\/a> is your friend, too, for all Google-related tagging issues.<\/p>\n<p>Finally, I recommend adding <code>dataLayer<\/code> to your organization\u2019s quality assurance process. This would mean writing unit tests and browser tests that verify <code>dataLayer<\/code> has a predictable composition with each new release of the site or app. I\u2019ve written a simple framework for running automated tests against <code>dataLayer<\/code>, and you can read about it <a href=\"https:\/\/www.simoahava.com\/analytics\/automated-tests-for-google-tag-managers-datalayer\/\">here<\/a>.<\/p>\n<h3 id=\"99-load-sequence-of-gtms-default-events-is-important-to-understand\">99. Load sequence of GTM\u2019s default events is important to understand<\/h3>\n<p>When Google Tag Manager loads on a site, it pushes three default events into <code>dataLayer<\/code>: <strong>gtm.js<\/strong>, <strong>gtm.dom<\/strong> and <strong>gtm.load<\/strong>.<\/p>\n<p><strong>gtm.js<\/strong> is pushed in the container snippet itself. Thus it introduces a state which contains all the Data Layer variables pushed <em>before or at the time<\/em> when the container snippet runs. This event is used by the <strong>Page View<\/strong> 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 <code>dataLayer<\/code> <em>before<\/em> the container snippet is executed by the browser. Typically this means to physically add your <code>dataLayer<\/code> initialization in the page template above the GTM container snippet.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-html\" data-lang=\"html\">script&gt;\n  <span style=\"color:#aaa;font-style:italic\">\/\/ Always use this syntax to initialize dataLayer\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#0aa\">window<\/span>.dataLayer = <span style=\"color:#0aa\">window<\/span>.dataLayer || [];\n  <span style=\"color:#0aa\">window<\/span>.dataLayer.push({\n    userType: <span style=\"color:#a50\">'member'<\/span>,\n    loyaltyLevel: <span style=\"color:#a50\">'premium'<\/span>\n  });\n<span style=\"color:#1e90ff;font-weight:bold\">script<\/span>&gt;\n...\nscript&gt;\n  <span style=\"color:#aaa;font-style:italic\">\/\/ GTM container snippet here\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#1e90ff;font-weight:bold\">script<\/span>&gt;<\/code><\/pre>\n<\/div>\n<p><strong>gtm.dom<\/strong> is pushed into <code>dataLayer<\/code> when the browser signals the <code>DOMContentLoaded<\/code> 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\u2019t fire before the <strong>gtm.dom<\/strong> event. The trigger which uses this event is <strong>DOM Ready<\/strong>, so any tags firing on this trigger will have access to any elements described in the page HTML.<\/p>\n<p><strong>gtm.load<\/strong> is pushed into <code>dataLayer<\/code> when the browser signals the <code>load<\/code> 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\u2019d need to use if you want your tags to access the result of some asynchronous process, such as the download of the <a href=\"https:\/\/jquery.org\">jQuery<\/a> library (if downloaded asynchronously, as you should). The trigger which uses this event is <strong>Window Loaded<\/strong>.<\/p>\n<p>It\u2019s important to understand how these three events work. They are not fired at <em>the exact time the underlying browser event takes place<\/em>. For example, <strong>gtm.js<\/strong> 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\u2019t describe the <em>time<\/em> something happens but rather the <em>state<\/em>. <strong>gtm.js<\/strong> describes the state of GTM\u2019s data when the container snippet was first read by the browser, <strong>gtm.dom<\/strong> reflects GTM\u2019s state when the browser signalled <code>DOMContentLoaded<\/code>, and <strong>gtm.load<\/strong> is the state of GTM when the entire page had completed loading.<\/p>\n<h3 id=\"100-some-ad-and-content-blockers-block-gtm-from-loading\">100. Some ad and content blockers block GTM from loading<\/h3>\n<p>Call it a feature or symptom of today\u2019s web browsing behavior, but ad and content blockers are making life for web analysts difficult. Firefox, for example, offers <a href=\"https:\/\/support.mozilla.org\/t5\/Protect-your-privacy\/Tracking-Protection-in-Private-Browsing\/ta-p\/36899\">Tracking Protection<\/a> out-of-the-box, and it blocks Google Analytics by default.<\/p>\n<p>Popular browser extensions like <a href=\"https:\/\/www.ghostery.com\/\">Ghostery<\/a> and <a href=\"https:\/\/adblockplus.org\/\">AdBlock Plus<\/a> make it easy to block Google Tag Manager, too.<\/p>\n<p>Whether you like this or not, it\u2019s a common practice and you should therefore accept a certain level of inaccuracy in your data.<\/p>\n<p>With Google Tag Manager, however, you can actively modify and extend the site experience with Custom JavaScript. If the user\u2019s browser doesn\u2019t load Google Tag Manager, these modifications will not be executed.<\/p>\n<p>Thus, <strong>be very wary of potential GTM blocking when running code via Google Tag Manager<\/strong>. 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.<\/p>\n<hr\/>\n<h3 id=\"101-use-undefined-to-clear-individual-keys-from-data-layer-upepo-mwindaji\">101. Use <code>undefined<\/code> to clear individual keys from Data Layer (upepo mwindaji)<\/h3>\n<p>The first guest tip comes from <strong>upepo mwindaji<\/strong>. You can clear individual values from GTM\u2019s data model by pushing the keys with the value <code>undefined<\/code>. This effectively resets the value of the key.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#0aa\">window<\/span>.dataLayer.push({\n  event: <span style=\"color:#a50\">'userLoggedOut'<\/span>,\n  loginStatus: <span style=\"color:#a50\">'loggedOut'<\/span>,\n  userId: <span style=\"color:#00a\">undefined<\/span>,\n  memberStatus: <span style=\"color:#00a\">undefined<\/span>\n});\n<\/code><\/pre>\n<\/div>\n<p>The code above, for example, clears the values of <code>userId<\/code> and <code>memberStatus<\/code> from GTM\u2019s data model because the user logged out.<\/p>\n<h3 id=\"102-you-can-track-to-multiple-universal-analytics-properties-in-the-same-container\">102. You can track to multiple Universal Analytics properties in the same container<\/h3>\n<p>This guest tip is from <strong>samgabell<\/strong>. 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.<\/p>\n<p>The only thing that will cause issues is if one set of tags is configured for cross-domain tracking with the <code>allowLinker<\/code> field set to <code>true<\/code>. In this case, a linker parameter in the URL will overwrite the Client ID stored in the <code>_ga<\/code> cookie, impacting all trackers on the site &#8211; even those that should ignore cross-domain tracking. You can read about a solution to this issue <a href=\"https:\/\/www.simoahava.com\/gtm-tips\/cross-domain-tracking-with-multiple-ga-trackers\/\">here<\/a>.<\/p>\n<h2 id=\"summary\">Summary<\/h2>\n<p>So that was my 100 tips. Some of them were indeed very simple and obvious, but I\u2019ve run into enough people having issues with all of the things covered here to justify my adding them to the list.<\/p>\n<p>Google Tag Manager turns 5 years old this year. It\u2019s been a colorful journey, and I\u2019m 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.<\/p>\n<p>Now is your turn &#8211; are there invaluable tips that you\u2019d want to share with the readers? I\u2019d love to add them to the end of the list. Thank you!<\/p>\n<\/p><\/form>\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>I\u2019ve always been proud to avoid the typical headline clickbait of \u201cUltimate guide to pigeon care\u201d, \u201cAll you need to know about the Great Vowel Shift\u201d, \u201cDid you know that you\u2019ve been smoking peyote wrong your whole life?\u201d. I\u2019m ready to make an exception now by adding a BIG WHOPPING NUMBER to the title. You [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":122896,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[1531,30642,14495,1791],"dealstore":[],"offerexpiration":[],"class_list":["post-122895","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-google","tag-learnings","tag-manager","tag-tag"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>100+ Google Tag Manager Learnings - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=122895\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"100+ Google Tag Manager Learnings - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"I\u2019ve always been proud to avoid the typical headline clickbait of \u201cUltimate guide to pigeon care\u201d, \u201cAll you need to know about the Great Vowel Shift\u201d, \u201cDid you know that you\u2019ve been smoking peyote wrong your whole life?\u201d. I\u2019m ready to make an exception now by adding a BIG WHOPPING NUMBER to the title. You [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=122895\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-03-09T21:17:52+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/100-tips.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1974\" \/>\n\t<meta property=\"og:image:height\" content=\"1106\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"55 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=122895#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=122895\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"100+ Google Tag Manager Learnings\",\"datePublished\":\"2025-03-09T21:17:52+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=122895\"},\"wordCount\":10088,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=122895#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/100-tips.jpg\",\"keywords\":[\"Google\",\"Learnings\",\"Manager\",\"TAG\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=122895#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=122895\",\"url\":\"https:\/\/fivemor.com\/?p=122895\",\"name\":\"100+ Google Tag Manager Learnings - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=122895#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=122895#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/100-tips.jpg\",\"datePublished\":\"2025-03-09T21:17:52+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=122895#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=122895\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=122895#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/100-tips.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/100-tips.jpg\",\"width\":1974,\"height\":1106},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=122895#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"100+ Google Tag Manager Learnings\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"100+ Google Tag Manager Learnings - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=122895","og_locale":"en_US","og_type":"article","og_title":"100+ Google Tag Manager Learnings - Som2ny Network","og_description":"I\u2019ve always been proud to avoid the typical headline clickbait of \u201cUltimate guide to pigeon care\u201d, \u201cAll you need to know about the Great Vowel Shift\u201d, \u201cDid you know that you\u2019ve been smoking peyote wrong your whole life?\u201d. I\u2019m ready to make an exception now by adding a BIG WHOPPING NUMBER to the title. You [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=122895","og_site_name":"Som2ny Network","article_published_time":"2025-03-09T21:17:52+00:00","og_image":[{"width":1974,"height":1106,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/100-tips.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"55 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=122895#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=122895"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"100+ Google Tag Manager Learnings","datePublished":"2025-03-09T21:17:52+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=122895"},"wordCount":10088,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=122895#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/100-tips.jpg","keywords":["Google","Learnings","Manager","TAG"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=122895#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=122895","url":"https:\/\/fivemor.com\/?p=122895","name":"100+ Google Tag Manager Learnings - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=122895#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=122895#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/100-tips.jpg","datePublished":"2025-03-09T21:17:52+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=122895#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=122895"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=122895#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/100-tips.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/100-tips.jpg","width":1974,"height":1106},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=122895#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"100+ Google Tag Manager Learnings"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/122895","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=122895"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/122895\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/122896"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=122895"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=122895"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=122895"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=122895"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=122895"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}