Also known as: a story of one ninja’s journey to tag management nirvana.
This blog post was co-authored by my good friend and esteemed, highly respected, super-expert colleague Sam Briesemeister. Any of the good ideas and “smarts” presented herein are definitely Sam’s. This post is his brain child. Any snark, poorly structured sentences, or failed jokes are solely my (Yehoshua Coren’s) responsibility. Same goes for the first-person voice in this post; Sam is not to blame.
Unembarrassed plug: you may obtain a license to the code that has changed the way my consultancy does implementations by contacting me directly; details at the end of the post.
Some background
If you don’t want to join me around the GTM campfire while I tell my story and just jump to the good stuff, scroll down to the section heading Implementation Manifesto.
Analytics Ninja was an early adopter of Google Tag Manager. I remember when a little birdy whispered in my ear that Google was going to be launching something called Google Tag Manager (I’ve never applied for GACP status, so haven’t ever had much lead time with regards to product launches) I basically shrugged and said “so what?” The bird then loudly chirped, “Google TAG MANAGER“. I still didn’t get it.
Fast forward a few months and I landed my first large enterprise client. When I asked them for access to their GA account, they said “which one?”.
“How many do you have?” asked a naive Yehoshua.
“50”.
“50!! Oh my! Why is that?
“Well…. One for US Men’s clothing, one for US Women’s clothing , one for EU Men’s, one for EU Women’s etc etc”.
I quickly realized that this brand had basically no online brand visibility and needed to consolidate all of their tags, and fast. BAM! Google Tag Manager had just launched. Perfect use case. I (smartly) waited about 4 months to make sure that the newly launched product was up to challenge for an enterprise client, and after a tremendous amount of planning and code work, we removed all the inline GA code from all of the multiple regions’ websites and I hit the publish button.
I was even smart enough to record a screencast of the launch; it was the GA Real Time interface lighting up with little circles of traffic as data from around the globe started populating the new GA property. A beautiful sight…
But I digress…. The way that my small team and I initially worked on an implementation was to use the tag management system as a way to inject custom code and then use GTM to send data to tags. In particular, I was a big fan of having one tag for GA Event Tracking. The dataLayer was the messaging mechanism to read values from {{gaEventCategory}}, {{gaEventAction}}, and {{gaEventLabel}}; and the {{gaEvent}} event was the trigger for the tags. We (which means Eliyahu at the time) even wrote a whole library that served as a wrapper to pass data to _gaq, ga(‘send’), and the dataLayer depending on whether or not the client had GTM.
The code itself was comprised of small code blocks that would push values from the DOM into the dataLayer based upon jQuery dependent CSS selectors and eventListeners. Versioning was managed through our own internally made UI based Jenkins / Grunt build system (NinjaBuild), fully integrated into GitHub, and files were hosted on my own S3 / Cloudfront buckets on AWS. Especially since GTM didn’t support large Custom HTML tags at the time, this seemed to be a good approach. Once GTM started supporting larger Custom HTML tags, I came to realize that it wasn’t in the best interest of my clients to have my company host the code that was being injected into their site (apart from the fact that I was paying for the nominal network fees).
To this day, I have a slough of former client code gathering dust within my S3 bucket that I simply don’t touch lest I torch their tracking and analytics. This is something that I am not proud of and did not do a good job communicating to clients at the time. As Analytics Ninja began to work with more and more large clients, I learned a lot along the way about governance and now am able to effectively steer my clients away from practices such as the ones I used to do. #AlwaysLearning. #ProfessionalGrowth.
Fast forward a few years…. I was having trouble staying on top of the tremendous demand of client work and was frustrated that when I was in a bind to get something done I didn’t have the coding skills to maintain or update the multiple hundreds of lines of jQuery code that was powering our implementations. So my friend Sam (mentioned in the preamble to this post) said, “What if I were to make this EASIER for you?”
And behold….. The following approach was gifted to me.
Our Implementation Manifesto
Objectives
- Minimize complexity for third-party (i.e. non Analytics Ninja) developers to add meaningful context for analytics.
- Minimize impact of site design changes on analytics context and tracking effects. (Read: Oh, the homepage redesign broke tracking?)
- Establish a pattern that can scale to many more projects, for large organizations, while supporting the inevitable needs for customization.
- Reduce integration and maintenance costs. Increase resilience.
General Principles
- Keep it “shallow” – this is not always simple to do, but it’s valuable in the long-term to keep it easy for anyone to “jump in” and tune the tracking configuration.
- It’s OK to use Custom Javascript Variables, but simplicity wins the long game.
- Use GTM for what it’s good at; rely on its features wherever possible.
- Let GTM manage the analytics: Having many tags is only mildly annoying, but they are searchable with a sound naming convention.
- Complex Javascript (e.g. frameworks in Custom HTML tags) should drive the data layer, but be agnostic to Analytics. Minimize their complexity: they cost more to maintain.
First Step: ACTIVATE EVERYTHING
First and foremost, in most cases, it’s strongly advantageous to use all of Tag Manager’s built-in variables, especially:
- Click Classes
- Click Element
- Click ID
- Click Target
- Click Text
- Click URL
- Form Classes
- Form Element
- Form ID
- Form Target
- Form Text
- Form URL
These make it easy to rely on GTM’s built-in click listeners. When configuring triggers, you’ll want to use Just Links especially when tracking links that navigate to a new page, and All Elements for buttons, images, or other interactive components.
An important distinction to remember:
- For Just Links (gtm.linkClick) events, the Click Element is the (surrounding) <a> element, even when click occurs on a subordinate (child) element, such as an image. This trigger also allows you to wait for tags, which provides some important timing assurances, specifically the page unloading when there is a navigational click before the data is sent to a tag endpoint.
- For All Elements events (i.e. gtm.click), the Click Element is the precise, innermost origin of the event (e.g. the image within the link). This level of precision of the Click Element can oftentimes turn into a “gotcha” unless you make sure your CSS selector in your trigger is well configured.
Fetching Context
It’s sometimes useful to derive data from other parts of the DOM using Native Javascript or jQuery for interrogating a page. Example page content:
Supposing I need to retrieve the actively selected color (in div.color.menu), I would create a GTM Custom Javascript Variable like so:
This process requires a few steps:
- Identify the element containing the desired context data (i.e. “context element”).
- Find the nearest, easily-identified parent element shared by both the context element, and the element of user interaction.
- Provide a CSS selector that matches that parent element.
- Provide a CSS selector that matches the context element within the parent.
- Provide a sensible default value.
Using jQuery allows that to fail safely and silently. Providing a fixed default, like “(not set)” will simplify negative matching, to prevent dependent triggers from firing when this value is unavailable. When GTM is in debug/preview mode, this script will be executed on every datalayer event, but in live/production mode, GTM will execute it only when triggers or tags evaluate its result.
I am not quite as allergic to using jQuery in an implementation as some of my colleagues are. That said, is a dependency. I definitely see the value of using Native Javascript in areas where jQuery doesn’t add particular value. In other words, if you can “keep it simple” and avoid jQuery, do so.
The fixed default value (e.g. “(not set)”) also ensures that reports in Google Analytics will not exclude those interactions implicitly, when those values are not defined. (When fields like event label are empty in Google Analytics, related hits are simply not counted in reports that attempt to populate a column or pivot based on that field. This can lead to some confusing reports). In other words, if you run a report with Event Category, Event Action, and Event Label as dimensions and Total Events as the metric, if there are circumstances where Event Label is EMPTY, the number of total events for Event Category + Event Action for a specific interaction will be greater than the total events metric for EC + EA + EL. The rows from the empty Event Label will simply drop out of the query, even though there was an EC+EA combination. That’s why the GA Team has chosen to always set an empty campaign name as “(not set)”, but has not done the same thing for Event Label or Custom Dimensions as of yet.
Another common approach that we take with regards to undefined values in GTM dropping out of the GA hit and thereby not being available for query is to set the value of HIT LEVEL dimensions to default to a “(not set)” string.
Matching Container Elements
In many sites I’ve worked with, an interaction in any descendant element of a specific container should be tracked as an event related to the whole container. Consider the following example:
Assume also that the site provides Javascript that, when clicking on any element within the div.product-listing-item (such as the product image), the interior link (See The Product) is also triggered. Or perhaps there are multiple <a> tags in the element….
In this case we’d want all clicks within div.product-listing-item to be treated as an “Product Listing Click” interaction. To support this, another Custom Javascript Variable is helpful:
Leveraging this variable in a GTM Trigger is simple:
The basic principles of this technique can be leveraged in many other cases too:
- Keep the Custom Javascript as simple as possible
- Returning one of multiple string values is OK to match several cases.
- Use regular expressions in trigger conditions to support multiple valid cases.
Further, using this technique, you could Lookup Table Variables as a means to pivot the selection of other variables, for example:
Spoiler Alert: At Analytics Ninja we rely on CSS selectors and the clean approach to the DOM traversal described only when needed. However, the above use case of parsing the DOM and then using a look up table to return values is something that we have never done in a client implementation. The reason for this is because we strongly (understatement) prefer to partner with the a site’s developers to implement excellent semantic markup (read: data attributes). Which brings me to the next portion of our manifesto…
Contextual Data Markup
In addition to stubbornly demanding that developers integrate a proper dataLayer object into their site, I also strongly advocate for devs to add static descriptive markup to the page, as HTML5-standard “data attributes” instead. This has several advantages, including:
- The data context is owned by the site maintainers: they bear responsibility for its quality, and have a stake in the outcomes of their product (the software/site).
- Because the values are string attributes on HTML, their software (CMS, etc) should have no trouble injecting them.
An Example (Vanilla) Case
This HTML block demonstrates the use of “data attributes” in adding details to the page that are meaningful to Analytics, but have limited bearing on the user experience. This provides a very simple, and generally safer method for adding content semantics, which both third-party developers and site maintainers can leverage.
It’s useful to think about a site as a collection of components or modules, which are related to objects in the site’s data store, and even physical objects in the case of an e-commerce site. Ideally data attributes that are common to a whole component (i.e. related to a specific instance of an object within that component) would be applied at the highest level of that component. In the case above, we have multiple products; each product has its relevant attributes tagged at the outermost element that represents it.
A few real examples:
Tag Manager Methodology With Data Context (Codename – Heisenberg)
In order to abstract, streamline, and simplify the integration of contextual data markup within Google Tag Manager, we use a non-open source javascript “plugin” to integrate data markup into GTM’s native messaging bus of the dataLayer.
Editor’s note:
I realize that many of my esteemed friends, colleagues, and other readers of this blog are strong proponents of code being open sourced. I also appreciate that those very same esteemed friends, colleagues, and other readers of this blog appreciate the importance of Intellectual Property and the hard work that goes into writing creative, functional, elegant code. In the use cases presented here in this post, Analytics Ninja has acquired a license to use the code being discussed below and we feel that this gives our implementations a qualitative value add for our clients. While this blog post is not meant to be a sales pitch per se (I really am truly jazzed by how our implementations have developed and matured over the years), there is an option available for companies, consultancies, or other agencies to license usage of this code of you’re interested. Sharing is caring, as they say. Especially if you pay for it.🤔
When the context plugin is running with GTM, and a site’s developers have added context markup (namely “data-” attributes to surrounding elements), interactions with the page will produce a dataContext attribute, attached to every dataLayer event.
Based upon the following example, a click on the first “Add to Cart” link would produce the following in the dataLayer, using GTM’s native (yes, native) link click listeners:
Note that context is inherited; the click occurred on the inner link, but all of its parent elements’ data attributes were added to the dataContext. All clicks will inherit data context attributes; if a child element and its parent both provide the same data attribute, the child (innermost) value will take precedence.
In other words, once an element is clicked, the Heisenberg code will traverse the DOM upwards, gobbling up all data-* attributes and appending them directly to dataLayer push that hits GTM.
Nom! Nom! Nom! Data Attributes!
Using this technique, with careful application of data attributes, we have seen a significantly reduced reliance on CSS selectors, by using the technique of validating dataLayer variables as trigger conditions. This reduction of CSS dependency increases resilience of the GTM configuration to unexpected changes in site layout, design, etc.
The ability to traverse the DOM to grab the semantic contextual meaning of different elements has been a game changer for the way we work. And I don’t even like when people use the term “game changer”!You may be thinking, “Hey, but doesn’t gtm.element.dataset also contain the values of data attributes?” My answer –> yes it does, but only for that EXACT Element. And not every sort of interaction is a navigational click, so just tagging all <a> attributes is not a comprehensive solution.
Element Visibility Use Case
Recently we started working with a client about whom I noticed that they had data attributes surrounding their product tiles. I shared the example in an image above as well. What I did not share above is that they had a much larger amount of markup on the product-tile-wrapper class. I was quite pleasantly surprised to find out that I was able to fully configure their Enhanced Ecommerce product list impressions simply by enabling a Element Visibility Trigger coupled with the Heisenberg plugin. With almost no work whatsoever, they had a rich Enhanced Ecommerce dataLayer push for products based upon actual item visibility.
Adding Event Listeners
The other core functionality of Heisenberg is the ability to add Event Listeners to the DOM with ease. This is something that has been on my mind for quite some time, as Doug Hall spoke about this five (??) years ago at Superweek. (Here is a link to his still evergreen, awesome presentation). Within Heisenberg, the code is a simple function named observer that adds an eventListener in a single line.
Doug’s example is similar.
Within Heisenberg, the current namespace is “CONTEXT“; which means to say that any additional events that are added to the dataLayer are named “context.change” or “context.beforeunload.” I wager that Doug will complain that we’re using an “Anti-Pattern” for not maintaining the gtm.eventName namespace, but I can say that we’ve maintained gtm.element itself (see slide 35), so hopefully he won’t be too grumpy after reading this post.
Measuring Related Content Blocks
Before I summarize, I want to share one more example where having a good dataLayer and good data markup has shown good results for my clients. Unfortunately, Google Analytics Enhanced Ecommerce is the only part of the platform does a good job with handling impressions of multiple items on a page. I say “unfortunately”, because many of my clients have Content Listings on their site, in addition to Product Listings. While I very much like Simo Ahava’s approach to using Enhanced Ecommerce for content (you got a link, bruh), I’ve always been challenged by use cases where there is an Ecommerce + Content overlap. I don’t want to send a bunch of non-product ecommerce data to GA when the company actually sells stuff. Furthermore, the “translation layer” whereby the end business user of the data needs to understand that Add To Cart means Begin to Scroll has always been roadblock for me.
As such, I’ve been turning to Promotions as a way to capture impressions and clicks on content. I’m not totally satisfied with this workaround either, but it does have a number of benefits. The biggest benefit for me is that the interactions are cordoned off to be “Promotions” and don’t impact the broader ecommerce “Product” object. The area where this approach is weak is that Promotions are thin –> Name, ID, and Creative are the only dimensions you get.
Returning to the context of this post, by having a contentListing object (array of objects) in the dataLayer together with nice, clean data attributes on within the markup of the page, makes is simple and fun to provide clients with visibility into their content performance.
Some final words…
Professionally, it’s been very fulfilling to watch my team and I grow, develop, and improve our tag management practice. Originally, we had reams of jQuery based code hosted on my own S3 buckets that pushed gaEventCategory, gaEventAction, and gaEventLabel to the dataLayer and the TMS was there simply to allow us to inject that code without deployment cycle hurdles. When business related necessities called for something different, the Implementation Manifesto you just read was born.
In many ways, I think that Mr. Miyagi has a lot to teach us about tag management. Certainly, in our current practice there is a lot of gentle, precise “up” then “down” motion to retrieving context on the page. As per this manifesto: Keep it simple! Use GTM for what it is good at! Let GTM manage the analytics!
When we saw an opportunity to partner with various enterprise Dev Teams, and help them understand the importance of having clear, clean markup that was less likely to get torched by site updates, and that all of the semantic data context of the measurement layer (data layer + data attributes == chocolate + peanut butter), I must admit that it makes me totally want to “get down”.
First ever Google Tag Manager Boogie.
Image below: Dancing with the Brian Kuhn, the principal author of gtm.js
Addendum
Towards the beginning of this post I mentioned that anyone who was interested in obtaining a license for the Heisenberg code which will make your company’s (or client’s) GTM implementations oh so awesome should check the bottom of this post for details. Now that I’ve reached the end of this post, I realize that I don’t really have any particular details other than to suggest that you email me.
¯\_(ツ)_/¯ Include the word HEISENBERG in all caps in your subject line and then I’ll know that you’re yelling at me about plugin.
ninja [at] analytics-ninja.com
Leave a Reply