Google Analytics 4 Pageview Custom Dimensions and Content Groupings

I decided it’s time I’d learn GA4 so I figured I’d implement it net new and figure it out along the way. A couple of things I rely on heavily on my sites are Content Groupings and Custom Dimensions. Luckily, GA4 handles these a lot better than UA—even if they are still a bit cumbersome.

I learned that there is only one Content Group parameter called content_group (instead of 5). It seems at first glance that event-scoped custom dimensions are capable of doing that which content groups used to handle (thanks to the new event-first rather than session-first paradigm).

To set customer dimensions, you can just set arbitrary parameters like canonical_name or page_author.

Shown below is an example of the query parameters that are sent with the page_view hit. Notice that the “ep.” (event parameter) prefix is added by the GA code. (I only passed canonical_name as a parameter) and GA adds the prefix when the hit is sent. This is also true for content_groups.

The query string parameters correspond to the Measurement Protocol

  • en: page_view
  • ep.criteria_id: 2554
  • ep.canonical_name: New%20Zealand
  • ep.countent_group4:
  • ep.countent_group3: NZ
  • ep.countent_group1: Country
  • ep.countent_group2: Active
  • ep.debug_mode: false

Notice that I set debug_mode to false so that I could take advantage of the debug view in the Google Analytics UI. That thing is pretty neat and helpful for spot-checking custom parameters.

One thing that seemed silly was that I couldn’t just arbitrarily name my content groups and assign them in the UI, but hey, it’s still a lot better than UA.

Defining and Naming Your Custom Dimensions

After you send the parameter with the hit, you have to set up the custom dimension in the GA4 UI. You can set them up by going into the left panel and selecting “Custom Definitions.”

The nice thing is that, if you’ve already set a parameter, you can just select the parameter name from the list and all you have to do is provide a Dimension Name and Description.

Pageview Custom Dimensions in Reports

To see the custom dimensions in a real report, you can go into the left panel and select Engagement > Pages and screens. Then you can find the dimension by clicking the big blue plus sign (+) and selecting whichever you like.

If you’re curious about how it works and you want to see it for yourself, you can go to the site and open up your Network console and filter for “?collect” to see the GA hits. The site is here: Please visit and send some data into that GA4 account!

Add a Website Skype Button to Talk With Leads and Customers

This post demonstrates several ways to add click-to-call and click-to-chat Skype button functionality into your website. Beyond just being a modern way to use a web browser, it enables a modern approach to interacting with leads and customers. There is good reason to do this. Let me tell you why…

Example Website Skype Button

A New Definition of “Social” Brands

The meaning of “Social” in terms of business and e-commerce is changing. There are a couple of trends that are having a big influence of this shift.

The first trend is that there are so many social and messaging platforms available, we are allowed to pick and chose the level of privacy and involvement we associate with each one. For example, I use Facebook and Whatsapp for close friends and LinkedIn and Skype for business. (No surprise they are both owned by Microsoft now) These expectations are evolving considerably with the birth of ever new platform and the generations that are native to each.

The second trend is social commerce; the consumer expectation that a relationship with a brand will be not only tangible but also responsive. There is a higher expectation of interactivity An example of this is how Facebook Pages are reporting response times – putting a brand’s level of interactivity on display for all to see.

So what does it mean? Social does not mean just sharing.Social means interacting.

This post offers an easy way to become more interactive with your customers.

Read More

9 Pro Tips for GTM Auto Event Tracking and the Click Element Variable

This week I did a Google Tag Manager implementation where I had no control over the site’s source code and no access to the site’s developers. It’s an imperfect situation but luckily, GTM Auto Event Tracking affords a solution that is very effective considering these constraints.

Google Tag Manager Should Be Easy

This post is meant share a few tips on how to implement, customize, and debug Google Tag Manager in a situation where you are using Auto Event Tracking. This is especially helpful when you want  to track events on pages that you do not have any control over. The heart of Auto Event Tracking and the focus is this post is the {{Click Element}} Auto Event Variable, also known in the dataLayer as “gtm.element.” Through the whole event tracking process, there are three tips:

  1. Setting up Click Event Tracking using “phantom” event triggers
  2. Custom JavaScript Variables to collect data about the page/interaction
  3. Debugging Auto Event Variables and Trigger

Read More

Solving Google Tag Manager Race Conditions with eventCallback

Google Tag Manager is a great solution for implementing Web analytics on AJAX-heavy apps. But at times it may seem like it adds more complexity than it relieves. Synchronizing data and events in the dataLayer is an example of this. Sometimes you will want to fire a tag that aims to send data that will show up in the dataLayer after an AJAX call and because you don’t have any guarantee about the timing of this AJAX call you may run into a bit of a race condition.

Fire Events With Data

In the world of javascript you might rely on a call back function to solve this problem. This same idea works with Google Tag Manager, but it has its own way of going about it. The Google Tag Manager solution to this type of problem is to push an event to the dataLayer with the new AJAX delivered data and then set firing rules for any beacons that depend on that information to said event. For instance:


  "newDataAboutX" : {
    "thing1" : "xyz",
    "thing2" : 12
  "event" : "dataAboutXReceived"
Then you would set all beacons that require data about X to fire when:
{{event}} Equals dataAboutXReceived
You can then use dataLayer variables, {{newDataAboutX.thing1}} and  {{newDataAboutX.thing2}} in any beacons that fire on dataAboutXReceived and be sure that they are available.  *A note about accessing dataLayer variables: You can access nested dataLayer variables with dot notation. This also extends to arrays. (for example: myDataLayerArray.3).

Google Tag Manager ‘eventCallback’ Function

If that isn’t enough, Google Tag Manager offers an ‘eventCallback’ function for any object that is pushed to the dataLayer. This is a callback function that is called once all tags that are triggered by this rule are fired. This is often used to navigate to another page after capturing data about the event that would normally cause a URL change. ie: outbound link clicks, navigation clicks or Enhanced Ecommerce  promotion clicks, product listing clicks and product adds to cart.
For example:


        'actionField':{'list':'Search Results'}, 
          'price': productObj.price,
          'brand': productObj.brand,
          'variant': productObj.variant
       document.location = productObj.url

Product Scope Custom Dimension & Metrics in Google Analytics

Google Analytics Enhanced Ecommerce provides insight into Ecommerce performance in detail that was impossible using standard Google Analytics methods. To get the most out of Enhanced Ecommerce, you must understand the breadth of Enhanced Ecommerce data collection capabilities. This tutorial will take you from Ecommerce product page to Enhanced Ecommerce Product Data.

Enhanced Ecommerce Product Page

A pretty standard Ecommerce Product Page. Thanks to

Enhanced Ecommerce Reporting

Enhanced Ecommerce Product Reporting with Custom Dimensions and Metrics.

Product Data Problem and Solution

The new product schema was created to provide insight in a way that answers questions that are specific to products rather than pages or events. Consider event hits; their schema uses a sentence-like structure to describe an action.  This does not map well to a product entity that is not bound by specific instances in time. The product schema is more similar to pageview hits that collect attributes of a page entity over time. But still, the data is collected on the page is in a different fashion.

For more, see Carmen Mardiros’ great conceptual explanation of Enhanced Ecommerce Data Layer and my slides on Enhanced Ecommerce schema.

Google Analytics Enhanced Ecommerce assigns a collection of properties to each product.  At least one property is mandatory (either name or id), and other properties are optional: brand, category (up to five tiers), variant, price, quantity, coupon, and position. These properties describe either an attribute of the product or the context of the product action. These standard features provide a holistic understanding of how users interact with products in an Ecommerce store. But they don’t give the whole picture of how customers interact with products within the context of any particular business.

 Understand how users interact with products based on qualitative dimensions like its availability, backorder date, release date or physical dimensions or appearance.

For instance, you may want to understand how your customers’ shopping behavior changes for products based on qualitative dimensions like its availability, backorder date, release date or physical dimensions or appearance. Likewise, there is more to understand about products’ quantitative information such as the cost of goods sold, previous price, discount, or profit. This is where custom dimensions and metrics come in.

Product Data Collection

Google Analytics collects data about product entities in a way that appropriately fits the concept of a product; each product is represented by Javascript Object. An Object is simply a thing represented by a collection of properties about that thing. In other words, a product entity would be represented by an Object that has a name property of  “Clapper Light Switch” and a price property with a value of 30.00.

To extend the ability for an Object to describe a product entity, we can specify additional properties like “cost of goods sold,” or “backorder date.” At the code level, this means adding one more property “key”:”value” pair to the product entity object. The only difference is that the properties name will be a placeholder such as “dimension9” or “metric4.” The dimension or metric name will be assigned later within the Google Analytics interface. In Universal Analytics it would look like this:

ga("create", "UA-XXXXX-Y");
ga("require", "ec");
ga("ec:addProduct", {
   "id": "81301",
   "name": "Xantech AC1 Controlled AC Outlet",
   "price": "78.55",
   "brand": "Xantech",
   "category": "AC1 Controlled AC Outlet",
   "variant": "white",
   "dimension3": "In stock - Ships Today", // stock status custom dim.
   "metric2": 5,                           // rating stars custom metric
   "quantity": 1 });
ga("ec:setAction", "add");
ga("send", "event", "Product", "Add to Cart", "Xantech AC1 Controlled AC Outlet");

and using the Google Tag Manager data layer, it would look like this:

   "event": "addToCart",
   "ecommerce": { 
      "currencyCode": "USD",
      "add": {
         "products": [{
            "id": "81301",
            "name": "Xantech AC1 Controlled AC Outlet",    
            "price": "78.55", "brand": "Xantech",            
            "category": "AC1 Controlled AC Outlet", 
            "variant": "white", 
            "dimension3": "In stock - Ships Today",  // stock status
            "metric2": 5,                            // review stars
            "quantity": 1 

For a great working example of this, see:

Setting Custom Dimension & Metric Names

The first thing to note is, if you are using the Google Tag Manager data layer to collect product data, make sure you have checked the “Enable Enhanced Ecommerce Features” and “Use data layer” boxes shown below. Google Tag Manager Add to Cart Event No matter if you are using the data layer or standard Universal Analytics collection code, you will have to do two things:

  1. Ecommerce and Enhanced Ecommerce Reports must be enabled.  Just go into your Admin section > choose a View > Ecommerce Settings and toggle  Enable Enhanced Ecommerce Reporting to “ON.”
  2.  Custom Dimension and Metric must be activated, named, and configured as you want them to appear in Google Analytics reports. This is also done within the Admin interface. Go to Admin > choose a Property > Custom Definitions and click Custom Dimensions or Custom Metrics. Set the name, the scope to “Product”, and the state to “On.” For Custom Metrics, set the appropriate formatting type.

Enhanced Ecommerce Product Custom Metric Setup Note that these hits must be set at the product hit level. Otherwise, the data will not be collected as expected, if at all.

Enhanced Ecommerce Product Data Reports

Now you are ready to appreciate all your shiny new insights. To find these reports within the Reporting interface go to Conversions > Ecommerce >  select a Product or Sales report.

Enhanced Ecommerce Reporting

From there you can see all your products and sort, filter, and view them by any their newly recorded properties. Metrics can also be added to Custom Reports to provide aggregate insights.

Inspecting, Debugging and Perfecting Product Data Collection

You may see something funny in your reports at this point or nothing at all. In my experience, Enhanced Ecommerce data is published as soon as the hit it was sent with is recorded. This is usually relatively quick. (Under 10 minutes) If something looks amiss, don’t worry. There are usually a few simple fixes that can be made to make sure the data is being collected and reported correctly. Assuming you have done everything correctly up to this point, there may be a few things you need to check and fix. Here’s a list:

Debugging Enhanced Ecommerce Hits

This is what you are looking for in the Google Analytics Debugger.

  • Make sure the hit is being sent to the right property.
  • Use mandatory product fields (name or id) as report dimensions.  This is helpful when starting out. If you are looking at a  report with a primary dimension of Product List, but are not yet collecting product list data, the report will appear to be empty.
  • Make sure Ecommerce data is sent with a standard Google Analytics Hit. Enhanced Ecommerce data is buffered on the page until a stand Google Analytics hit is sent. Then the Ecommerce data is collected with that hit.
  • Make sure Javascript Object data structure is correct and without errors. Use my data layer debugger bookmarklet to verify that the data is in the data layer. Also, keep an eye on the Javascript console and use to make sure there are no errors and everything is formatted correctly.
  • Try this cool method of inspecting each object that is pushed to the data layer is valid by using JSON.stringify to view Objects in the data layer. Just type the following command into your Javascript console and inspect the object in JSON (JavaScript Object Notation).
// where 0 is the index of the first object in the data layer array

Accessing Nested Values with Google Tag Manager Variables

When Google Analytics event hits carry Enhanced Ecommerce info, you may want to use a product’s attributes as the values for the event’s Category, Action, or Label or event Custom Dimension or Metric. Similarly, product data can be applied as a custom dimension on the pageview hit that, for example, is sent to on a product page to carry product detail view Enhanced Ecommerce information. In these case, if you are using Google Tag Manager, you can access the values of the product or actionField data using a Data Layer v2 Variable. These GTM Variables allow you to access property values that are nested within Objects or Arrays in the data layer.

For instance, if you wanted to access the name of a product that was just added to a shopping cart (as shown above), you would use the following format without the quotes: “”. Note that the 0 is specifying the index (zero-based count) of the Object noted in the array that is enclosed in [brackets].

Thanks, Simo for getting me back on track with this.

A Note on Product Hit Scope Dimensions and Metrics

Custom Dimensions and Metrics are product level, and won’t be applied to an event or page on which the happen or to a user that interacts with them. That is done by setting the dimension or metric at the hit level. Just make sure to configure the hit Dimension or Metric accordingly.  Check out this old but good explanation of  hit scope by Justin Cutroni.

Start Collecting and Start Optimizing!

This may seem complicated, but the power that it provides is well worth time spent in a detailed implementation. Please leave a comment if you have any questions. Or send me an email at and let’s get this started!

Thank you to for the pretend data. I want everything in your store.

Scale Optimizely Testing with Google Tag Manager

You already know that Google Tag Manager is an incredibly powerful tool but you may not know that it can literally do anything. In fact, Google Tag Manager made me pancakes this morning. Not really, but a man can dream.

Google Tag Manager is comprised of three components: Tags (the things that do stuff with data), Triggers (formerly Rules, the things that decide when/if stuff is done), and Variables (formerly Macros, the things that express the data). This post is one post of many that shows that, thanks to the Custom HTML Tag, Google Tag Manager can do virtually anything.

Custom HTML Tags let you place any HTML, CSS, or Javascript on to the page that you might want. This could be anything from a tracking pixel, to CSS rules similar to Optimizely’s global CSS insert, to JavaScript AJAX commands to communicate with another API like Google Analytics, Lytics or if you must, SiteCatalyst. So when we say Google Tag Manager can do anything, we mean it. But that’s just because Javascript can do anything.

On Page Data Collection with Google Tag Manager

That being said, when we approach any on-page data collection problem, the question boils down to what is available and what needs to be passed on? To solve the problem simply clarify those two variables and use Google Tag Manager to draw the line between input and output.

Optimizley Goal Tracking is an excellent example of this. The input is pretty clear. We want to measure specific actions (most often clicks) that users might take or experience that will give us insight into the effect that our experiment is having. Aside from pageview tracking, output is less understood.

At a high level, we know what we want to see in terms of outputs: lines on a graph, hopefully with our new variation showing an improvement on the original. But how does it get there? While you can easily attach event triggers to clicks within the Optimizely interface. There are other events like Form Submit Events, YouTube Video Events , and Scroll Depth Events that are not quite as easy to capture.

This is where Custom Events and Optimizely’s Javascript API come in. At a basic level, whenever a Optimizley Goal happens, it is pushed into a queue of all the Goals that may have or may yet happen on the page. This queue of events are sent to Optimizely’s servers using Javascript AJAX (Asynchronous JavaScript and XML) when the Javascript commands can be executed. (This is much like how the  Google Tag Manager Data Layer works.)

At the Javascript code level, this is how it looks when a Goal is pushed onto the queue.

Optimizely Event Javascript

* Now is a good time to note, that Optimizely does not differentiate between Click Events and Custom Events. They are all Custom Events at the code level. The only thing that differentiates Event Goals is their names. (This is what you see when you click “Advanced”in the Goal setup process.) That is also what is used in the above code for “eventName”.

So now that we have a pretty good idea about what our output should be, it’s time to draw the line between input and output with Google Tag Manager.

Optimizely Custom Event Goal Setup

After a few Optimizely test implementations I came to realize I was repeating myself and I am a HUGE D.R.Y. principal person. With each implementation I found myself in the experiment setup clicking on an element to track and going through the process of making sure that this element actually tracked as expected. The problem was that every time I would setup these Goals, I would say to myself, “we are already tracking this with Google Analytics using Google Tag Manager.” So this is how I came up with this (fairly) universal method for tracking Optimizely Event Goals.

With Google Tag Manager, we specify triggers to determine when a Google Analytics Event is fired. This trigger also signals when Optimizely Event Goal actions take place. So the light went on and I decided to make both things happen at the same time.

Tracking Optimizely Event Goals With GTM

As I mentioned before, Google Tag Manager relies on “events” being pushed to the data layer similar to Optimizely Custom Event Tracking. Google Tag Manager’s syntax looks like this:

dataLayer push code

Where the “event” value, in this case: “customizeCar”, is used to trigger the firing of GTM tags. With each push to the dataLayer, additional attributes can also be specified to associate with that event. (This makes GTM much more scalable to use for multiple tag types. Also, now is a good time to read the documentation on this.)

Optimizely, on the other hand, sends a single event name for each goal. (Queue light bulb) We can just use the dataLayer event value from GTM for Optimizely Goal Events!

Configuring GTM Custom HTML Tags

You will need two things to do this: a Data Layer Variable to get the value of the dataLayer event and a, you guessed it, Custom HTML tag to pass the event to Optimizely.

GTM MenuData Layer Event Name Variable

GTM already provides a {{event}} auto-event variable but this is not what we are looking for. The Auto-Event Variable aka: {{event}} is used for listening for events and triggering tags but it won’t return the value of the current dataLayer “event” variable. We will need to make a variable to do this. Our new varialble will look like this:

Universal Optimizely Custom Event Tag with Goal Tag Manager

To send the event to Optimizely we will need a Custom HTML tag to run a little JavasScript script to interact with the Optimizely Javascript API and send the Custom Event Goals.

GTM Custom HTML Tag

Trigger the Custom Event Goal

This is where the logic happens! Decide which dataLayer auto event variable you would like to send to Optimzely as Custom Event Goals and set them up as triggers, as shown below. Because this trigger is (fairly) universal, it can be setup with multiple custom event triggers (shown below) as long as the dataLayer event names are unique.

GTM Customization

So that (fairly) universal tag works best when you are passing in data layer events that have unique names. Putting in the planning up front can really pay off down the road. We can apply the same idea to a few other metrics that you might have considered while setting up Optimizely like Form Submit Events, YouTube Video Events , and Scroll Depth Events.

Form Submit Custom Event Goal

The Custom HTML tag will look like this:

The Trigger will look like this – be sure to specify your form by element ID, Class or maybe data attributes. This is what the tag would look like if it had a distinctive element ID:

GTM Custom Form Event

Instead of setting it all up, download and merge this container including tag, variables and triggers here. [Instructions]

YouTube Video Custom Event Goals

If you are doing a test that involves optimizing a video, you want to track video plays with Optimizely.

The Custom HTML tag will look like this:

GTM Custom YouTube Event

This one is a little more complex so check out this explanation, or download and merge the complete container here[Instructions]

Page Scroll Depth Custom Event Goals

You might use this as a negative goal. The Custom HTML tag will look like this:

GTM Custom Scroll Event

This one is a little more complex so check out this explanation, or download and merge this container here[Instructions]

I hope all this offers an idea of the flexibility and limitless power of Google Tag Manager and answers the question, can I track that? Just remember, Optimizely is a great testing platform but Google Analytics is a great web analytics platform. If you need help with either, let us know. This is what we do and we love it.

Data-Driven Marketing Starts With the Data Layer

The term “digital marketing “ should describe the marketing methods as much as it describes the marketing medium.  

Too often, marketers are satisfied with their “digital marketing” efforts when they send a tweet, “blast” an email or run a search ad because their message is transmitted over a digital medium.  Unfortunately, this misunderstanding so vastly underestimates the potential power of digital marketing; well…I just had to write a blog post about it.

If you take just one idea away from this blog post take this: The  access to data that the digital medium affords, when interpreted correctly and acted upon confidently, infinitely strengthens the performance of the digital marketing medium.

If you take two ideas away from this, take this too: The data layer is the very first place to start to capture and leverage your data. It provides the capability to merge data at its source. This is the foundation for data-driven action in real-time.

I wrote this post to illustrate the value of the data layer in the context of today’s digital marketing stack. I explain the present state of the digital marketing medium and how and why the data layer has a role in it. If you’re done reading, please tell your friends. The share buttons are above. Otherwise please read and leave a comment below.

I deal with data

I have data skills!

What is the data layer?

It is essentially a menu of available data, from which data consumers (defined and explained later) can order from within your web app or page.  Technically speaking, the data layer is a JavaScript data structure that receives and holds data about the user, the user’s interactions, the app, the view or page, and the context of the user interaction within an app or page.  The data layer can then be accessed by a tag management system or application for data collection or to trigger marketing actions.

The tag management system sits on top of the data layer and is responsible for routing data to data consumers. This could be as simple as a few lines of home-baked of Javascript or as intricate as Google Tag Manager. Ultimately, a tag management system is the logic that decides where the data is routed and to some extent how the data in the data layer will be used.

Why does the data layer exist?

To understand the role that the data layer plays in digital marketing, it helps to understand two currently sexy terms: Web App and the Digital Marketing Stack. I will explain those later but let me pull a little Chuck Palahniuk on you and set you up with these three eerily similar but presently unrelated premises:

The data layer exists because:

  • Web apps have multiple players providing and consuming data within the app.
  • Similarly to the concept of data independence, data access should be flexible and independent from the external user-facing layer of an application.
  • Data access and acquisition should not be barriers to data-driven action.

And now a story…

How the data layer came to be

(and A Brief History of the Web Apps.)

In the beginning there was HTML. It was and is the commonly used document structure for web documents.  In a very basic sense, the HTML language codes how text is hierarchically structured in a document. So as you remember in 1997, we could request an HTML document from a server far away and we could view it in our browser as a webpage, and things were great.

Sometimes, people would add some style to these documents and, depending on the browser you were using, things may or may not have been so great. The problem with adding style to an HTML document was that writing the style rules to the document itself meant that any time there was a need to change the style of a document, it meant changing the document itself. This was very messy and inefficient.

Then came CSS, a separate additional layer of style rules that your browser would apply to an HTML document.  Colors, sizes and blinking distraction exploded on the page in a barf of glory. People in the Wild Wild West of the web now had a simple method of keeping their theme of yellow Courier font over the top of a picture and John Wayne across all the pages of their site by using the same style sheet across their site. It wasn’t always tasteful and it certainly wasn’t interactive but it was consistent. Web developers hailed the separation of style and content layers.

Hello World! Here comes JavaScript!…and ten more pop-ups about things you wouldn’t bring up at a dinner party. JavaScript could both blow your mind and ruin your browsing experiences depending on your browser. Like document style, it was often a jagged mess until people realized the virtue in separating the JavaScript code (the behavior of the page), from the content and style of the document. Recognize the theme?

JavaScript is what is responsible for the prolific < booming voice > WEB APP </ booming voice >.  It is behind all of the shifting, scaling, constantly updating, interactivity that we now know and love. Possibly, the most powerful aspect of what JavaScript offers is the ability to get more data from a server without requesting a whole new web page. This is called AJAX and is what makes Facebook login for your favorite ecommerce site possible. It is also what makes web analytics products like Google Analytics possible.

Today, interactive Web Apps have become the norm. From Facebook, to Gmail, to EBay, to your website, web apps do more than just serve dynamic content from a server. With the help of AJAX, they can host an entire ecosystem of plugins, pixels, scripts, snippets, iframes and APIs that communicate with the other servers of the web! There are data providers (players that add data to the page like an embedded YouTube video, Qualaroo survey or the app) and there are data consumers (players that take data on the page for their own use such as analytics platforms like Google Analytics, MixPanel and SiteCatalyst, marketing tags like Adwords, Facebook or Adroll, email marketing platforms like Campaign Monitor or Mailchimp and testing and optimization platforms like Optimizely using all this data to trigger their own events using logic defined by each specific data consumer.)

“There ought to be a layer!” some smart person shouted after this mix of independent data and logic got pretty messy.  Again, a new web feature arrived and a new layer arose.Each layer of an application separated the business of each feature from the business of other features. By separating all the layers, it became possible to manipulate and modify each layer without affecting other layers or the application as a whole. The web is stable. Occam put his Razor away.

Enter the data layer. There is data in the application, there is data in the users’ interactions, and there is data coming from outside sources. The data layer exists to separate all this data from the business logic of the application. This way the application is only responsible for serving the user, and all other data consumers have a central point of access for the data they use.

How the data layer works (by example)

Let’s say that your brand just produced a video to promote a new product.

Your digital marketing stack looks like this: Your brand’s website uses Google Analytics to track user interactions and session source from several paid and earned channels that you cultivate. You have remarketing tags for Facebook, YouTube and Adwords and you use Optimizely for testing and dynamic content. Finally, you use Google Tag Manager to manage and trigger your marketing tags.

Because you’re smart, you recognize that just posting this video on YouTube is not going to have any impact on sales. (The right people have to see it!) You also recognize, based on your last video campaign, that users who saw the video were 15% more likely to convert, making it very important for users to view the video.

So you feature your video on the homepage.  As users come in, you use JavaScript to check their referrer and place that information in the data layer.  Based on the referrer you can infer their likelihood to convert; we know that a user who comes in via a certain branded paid search campaign is very likely to convert. We tag users who visit from this campaign with a value of “high” and place that data in the data layer. You could capture this in Google Analytics in a “User Value” custom dimension.

As users interact with the home page, some watch your new video and are tagged as “video viewer” (again, this could be captured as a Custom Dimension) and some navigate past the home page without viewing the video.  These users are not tagged. Because users of this segment have proven to be worth such a high value based on their referral source, you have set up Google Tag Manager to tag this specific audience with both Facebook and YouTube video remarketing tags. This user must see this video!

A couple days later, the user returns via a Facebook video post tagged with UTM parameters that are again read by JavaScript. This time, because the user came from the video post, we use Google Tag Manager to tag this user as a “video viewer.” When this happens, another event happens: this user is added to an Optimizely audience. Optimizely then cookies this user so that when they navigate back to the home page, instead of seeing the video front and center, they see a persuasive call to action above the video that shortcuts them toward the ultimate conversion page. Finally, a transaction is registered in Google Analytics along with a few events and a branded search campaign and Facebook video campaign source for attribution.

In this case, there were four players involved, but the most important thing to recognize is that without the data layer to unify all the data that each player was consuming, each player would have continued to act independently. The data layer provided the capability to merge data at its source, making it possible for all these players to interact with each other in real-time. The value created by each part of the digital marketing stacks working together is greater than the sum value of their parts.



Value is in the aggregate. Be a data gangster.

So much data! So much opportunity!

There is data everywhere. As long as there is a common thread between a single user and your website/application, there will be data that describes that relationship. Managing this data, finding the value in it and finding the value that it provides to other data role players is the key to maximizing digital marketing success.

To learn more about the data layer in action, see: Enhanced Ecommerce Using the Data Layer.

Inspecting and Debugging the Google Tag Manager dataLayer

the datalayer in action

ah data jokes…

The Data Layer Inspector and Debugger

This project began as a solution to a problem that I often faced setting up Google Tag Manager and specifically the data layer. The solution that I ended making was dataLayer logger, inspector, and debugger that offered the same features as the Google Analytics Debugger but for the data that was being held in or pushed to the dataLayer. It pretty-logs all the new contents of the dataLayer as it is pushed no matter if the dataLayer.push() happens on the page, in a script or in a custom HTML tag within GTM. Its all there as soon as it happens and it saves a fair bit of time.

datalayer debugger

Each object that is pushed to the dataLayer is logged like this.

One interesting difference between what I ended up with is that unlike the Chrome extension, this works across browsers (as far as I cared to test) and can be run as a standalone script, a custome HTML tag in GTM or even as bookmarklet. (Bare in mind I started this project about a month ago and, if you don’t already know, you will see why declaring that now makes sense shortly.) Also, Simo, if you ever read this, please integrate this into your Chrome extension.

The Google Tag Manager Debugging Problem

If you have ever worked with Google Tag Manager, you recognize that one of the most important, if not the most important feature of GTM is the dataLayer. This was a very conscious choice by the makers of GTM and other tag management companies to solve a problem that has arisen as websites have invariably become web apps with multiple players playing roles on a page, consuming and passing in data. That the data layer offered is a central stage for all the actors in the data game to meet and access the data that they require. It was an elegant solution to eliminated a lot of redundancy and has truly made data access and use a lot more agile.

The problem was that the Google Tag Manager interface, in its infancy was a bit less transparent than was ideal. GTM initially made claims that it would not require IT but that, for all but the most trivial cases was flat out untrue. In the beginning and still at the time that I write this, the process involves a fair bit of Javascript coding and hence, quality analysis and debugging that comes along with it. So if you have pushed to or pulled from the GTM dataLayer, you have entered this into your console more than once … many times more than one.

datalayer array

The tedious way to inspect the dataLayer.

You have inspected the dataLayer array and to see if that event was fired at the appropriate time or if all customer or event attributes made it through accurately. I did, quite often and that why I said, “These nine keystrokes, they can be automated.”

The Rabbit Hole of Problem Solving

Initially I thought it would be cool just to copy the style of the Google Analytics Chrome extension and just show every new push. But two things changed that initially course, this is not the same problem as that and I was getting really interested in the jQuery source. (Which is amazing if you every want to learn some really killer idioms.)

I learned about the Chrome Console API which as luck has it means I learned about the other cool Webkit browser, Firefox’s Console API. After that I realized I was half way there so I learned about decent but not quite as advanced Safari and IE console APIs. (Can you believe there are even cross browser considerations for the Javascript console!?)

So the main feature is just a nifty combo of the method and string styling in the better two browsers to display the pushed object. This all goes on because the main change that the script makes is that it redefines the dataLayer.push method to wrap around the original GTM version of the dataLayer.push method. Once I got that far I had to ask myself why don’t I do this, why don’t I do that and so on until I ended up with a console API of sorts specifically for one, in my opinion conceptually significant, Javascript array. If you want to learn more about all the properties and methods of the Data Layer Debugger I will write a bit of an API reference for kick later. Otherwise the comments in the code do a decent job explaining the what everything does.

The Bookmarklet

If you want to try it out click the bookmarklet below and open up your Javascript console and click around a bit. You’ll see it working, hopefully as expected. If you want yours to keep, drag the bookmarklet into your bookmark bar and click it on any page with a dataLayer and watch the interaction take place.


The Custom HTML Script

Here is the GTM debugging script on GitHub. Its kind of useless now that the good folks at Google Tag Manager have baked this functionality in to the GTM interface. ( I like to think they saw my code on github and said, “that guy Trevor, he’s on to something…” Try it out, maybe you will find it useful.

(function (window) {

  'use strict'; // if you say so Mr. Crockford.

  // In case the dataLayer_debugger obj does not exist, make it.

  if (window.dataLayer_debugger === undefined) {

      // Set the value of the dataLayer_debugger variable
      // to the return value of an immediately evoked function.

      window.dataLayer_debugger = (function(){

      // Instantiate the main dldb object to be returned as mentioned above

      var dldb = {

        // Cache the dataLayer array.

        "dlCache" : window.dataLayer,

        // Cache the default dataLayer.push method.

        "pushCache" : window.dataLayer.push,

        // Acts as the on/of switch for on,off methods or book marklet.

        "keepState" : false,

        // Counts how many pushes occur.

        "pushCount" : window.dataLayer.length || 0,

        // time starts when GTM rule is fired or bookmarklet click

        "startTime" : new Date(),

        // to hold array of callback functions to call inside new .push method

        "callbacks" : [],

        // Does this debugger off cool logging features?

        "coolConsole" : navigator.userAgent.toLowerCase().match(/chrome|firefox/) ? true : false,

        // An object that holds all the current dataLayer values

        "current" : google_tag_manager.dataLayer


      // Append methods to dldb object:

      // Returns time elapsed from startTime.
      // Used for timestamping dataLayer.push logs. = function () {

        var now =  (new Date() - dldb.startTime) / 1000;

        return now;

      // Returns whether the debugger is on or off.

      dldb.state = function () {

        var state = window.dataLayer_debugger.keepState ? "On" : "Off";

        return state;

      // Turns the debugger on by changing the keepState variable,
      // changes the dataLayer.push method to add debugging functionality

      dldb.on = function () {

        dldb.keepState = true;
        window.dataLayer.push = window.dataLayer_debugger.push;

        console.log ("The dataLayer debugger is On");
        console.log ("Time is: " + + " To reset timer: dataLayer_debugger.reset('time')");

        for (var d = 0; d < window.dataLayer.length; d++){

          var obj = window.dataLayer[d],
          keys = Object.keys(obj);

          for (var k = 0; k < keys.length; k++){

            var key = keys[k],
            val = obj[key];

            // Set the new value of current

            //dldb.current[key] = val;

        window.dataLayer_debugger.log(window.dataLayer_debugger.current,"current dataLayer");


      // Turns debugger off,
      // changes dataLayer.push method back to normal. = function () {

        dldb.keepState = false;

        window.dataLayer = dldb.dlCache;
        window.dataLayer.push = dldb.pushCache;

        console.log("The dataLayer debugger is " + window.dataLayer_debugger.state() + ".");
        console.log("Current time is: " +;

      // Set one or many callback functions
      // to the debugging version of dataLayer.push method.

      dldb.setCallback = function (callback){


      // Resets the timer, counter, and/or callbacks depending on arguments.
      // No arguments resets all- essentially the same as page refresh.

      dldb.reset = function () {

        for (var r = 0; r < arguments.length; r++){

          var arg = arguments[r];

          if (arg === "time") {

            dldb.startTime = new Date();

          }  if (arg === "count") {

            dldb.pushCount = 0;

          } else if (arg === "callbacks") {

            dldb.callbacks = [];

          } else {

            dldb.startTime = new Date();
            dldb.pushCount = 0;
            dldb.callbacks = [];


      // Redefines the dataLayer.push method to add debugging functionality,
      // calls (applys) all the functions in the dldb.callbacks array,
      // calls the original dataLayer.push methon on all function arguments.

      dldb.push = function () {

        for (var a = 0; a < arguments.length; a++) {

          var obj = arguments[a];

          dldb.pushCount += 1;

 'dataLayer.push: #' + dldb.pushCount + ", Time: " +;



          window.dataLayer_debugger.validate(obj, ['transactionTotal','transactionId'], ['transactionAffiliation', 'transactionShipping', 'transactionTax', 'transactionProducts'], "transaction");
          window.dataLayer_debugger.validate(obj, ['network','socialAction'],[], "social");

          // Call all callbacks within the context of the pushed object.

          if (window.dataLayer_debugger.callbacks){

            var callbacks = window.dataLayer_debugger.callbacks;

            for (var j = 0; j < callbacks.length; j++) {

              var callback = callbacks[j];



          // Calls original cached version of dataLayer.push.


      // Pretty-logs an object's contents.

      dldb.log = function (object, optName){

        var ks = Object.keys(object).sort();

        for (var v = 0; v < ks.length; v++){

          if (ks[v] === 'event'){

        }"object: " + (optName || ""));

        // Check for "event" property.
        // Put "event" property of pushed object first in list.

        try {

          for (var i = 0; i< ks.length; i++) {

            var key = ks[i],
            val = object[key],
            space = 25 - key.length;

            var logMessage = key + new Array(space).join(' ') + ': ';

            if (window.dataLayer_debugger.coolConsole) {

              var valType = (typeof(val) === 'object') ? '%O' : '%s';

              console.log( logMessage + valType, val);

            } else {

              console.log( logMessage + (typeof(val) !== 'object' ? val : "") );

              if (typeof(val) === 'object') {



            // Set the new value of current

            dldb.current[key] = val;

        catch (e) {

          console.log("dataLayer error: " + e.message);

          if (window.dataLayer_debugger.coolConsole) {

            console.log("object was %O", object);

          } else {

            console.log( "object was:" );

      // Validates an object against an array mandatory valid key
      // and a second optional array of optional keys.
      // The last argument is the is a string name of the type of objec. ie 'social'

      dldb.validate = function (testObj, validKeys, optKeys, type) {

        var checked,
        checkedKeys = [],
        checks = validKeys.length > optKeys.length ? validKeys : optKeys;

        for (var j = 0; j < checks; j++) {

          validKey = validKeys[j];

          if (testObj[validKey]){

            checked = validKeys.splice(j,1);


          else if (optKeys && testObj[optKey]) {

            optKey = optKey[j];

            checked = optKeys.splice(j,1);


            if (optKey === "transactionProducts" ) {

              var products = testObj.transactionProducts;

              for (var p = 0; p < products.length; p++) {

                var product = products[p];

                window.dataLayer_debugger.validate(product, ["name","sku","price","quantity"],  "product");


        if (validKeys.length) {

          console.log("Invalid " + (type ? type + " " : "") + "object pushed to dataLayer. Missing: " + validKeys.join(", "));

          return false;

        } else if (optKeys.length) {

          console.log("Valid " + (type ? type + " " : "") + "object pushed to dataLayer. Optional keys not used: " + validKeys.join(", "));

        return true;

      // End method definitions and return the created object.

      return dldb;



  // Logic responsible for turning dataLayer_debugger on/off.

  if (!window.dataLayer_debugger.keepState) {


  else {;



Event Tracking on Squarespace Using Google Tag Manager

I like Google Tag Manager and Squarespace for the same reason: they simplify and streamline the processes website design and Google Analytics implementation while maintaining power and functionality of the product.  For the old sliced bread analogy, they are the greatest things to come along since the knife.

google-analytics-squarespaceSquarespace is a great plug-and-play, drag-and-drop Content Management System (CMS). It allows users to easily add all kinds of content in a simple non-technical way. 

Google Tag Manager is a Tag Management System for Google Analytics . (You could say TMS but nobody does.) It allows for on-page tagging and most other advanced analytics capabilities of Google Analytics. It greatly minimizes technical implementation and actual code that must be added to a website in order to track user interaction. Its semi-technical, in fact, it’s still pretty technical but once the code is implemented by your friendly webmaster or neighborhood hacker, the backend is pretty accessible to fairly tech savvy person.  I recommend checking out this short Google Tag Manager video to get a basic understanding of its features and capabilities. When I watched it, it blew my mind, but then again, I love analytics.

“Stepping Into” Google Tag Manager

That clever heading is a JavaScript joke. Don’t worry if you don’t get, I wouldn’t have either until this year.  If you do, then you are in pretty good shape to follow this guide. To get you up to speed, here are a few great references to learn what’s going on with the technical side of the following implementation.

If you were to do this from scratch, you would need a basic understanding of JavasScript, HTML, CSS and jQuery. Actually, since Squarespace uses YUI instead of jQuery you will have to also understand YUI. I’ll explain later. You can find everything you need to know about learning how to code here.

For the YUI to jQuery translation, use It does exactly what it sounds like: lists translations of all the functionalities of jQuery into YUI.

For help, references use Google Tag Manager Help, Squarespace Developer Center and the Holy Grail: if you ever get hung up.

Step 1: Get Your Google Tag Manager Account and Container

Now that you are a technically proficient hack, let’s dive in!

This has been covered many times so I will let Google take it from here. Go to Google Tag Manager’s website and follow steps 1-3. Do not follow step 4! Do it yourself!

Also check out Justin Cutroni’s extremely helpful guide for setting up your account.

Step 2:  Insert the Container and Initialize the Data Layer

This is where the fun starts; the beginning of analytics tracking glory!

Inserting the Container:

Google Tag Manager documentation recommends that you place your container just below the opening body tag. One way to do this is by adding the container into a code block at the top of each page, but that is just a lot of work. Instead, the functional alternative, which works just as well, is “injecting” the container in the header section.

Initializing the Data Layer:

The container is the brain that runs Google Tag Manager on your site but it doesn’t really do anything too special without the data layer.

You will also have to insert a bit of code above the container to initialize the data layer. Technically, this bit of code creates an empty data layer object, which will be filled as user interactions push information to the data layer array. The code looks like this:

    dataLayer = [];

<!-- Google Tag Manager -->

Step 3: Insert Code to Push Events to the Data Layer

Edit: Now much of this can be handled by Google Tag Manager’s auto-event listeners.

This method of event tracking offers the agility that differentiates GTM from previous methods of Google Analytics event tracking. It is truly where the magic happens.

Inject this snippet of javascript into the footer. This way loading the script will not slow downloading the visual elements of the page.

This snippet of code says, “every time a user clicks a link to a social media site, I will push the corresponding key/value pair to the data layer.  Most commonly this is done using jQuery, but Squarespace uses YUI so you have to play the game between the lines.


 YUI().use('event', 'node', function(Y) {
   // (1) These variables reference the 'class' link attribute. 
   // Each social media site link has its own 'class' attribute.
   // Note that by using an element class rather than element id,
   // any button that shares that class will fire the GTM rule 
   // triggerd by that class.    
   var twitterLink = Y.all('');
   var gPlusLink = Y.all('');
   var linkedInLink = Y.all('');
   // (2) The 'socialView' function tracks a button click event (2a) 
   // then pushes a corresponding data layer variable to the data layer. (2b)
   var socialView= function(socialLink,socialClick){
     socialLink.on("click", function(e){                //(2a)          
       dataLayer.push({'event': socialClick});          //(2b)
       //These are logs I used for debugging:
       console.log(socialLink + " has been clicked");    
       console.log(socialClick + " was pushed to the datalayer");
   // (3) Finally, 'socialView' (2) is called for each link class variable (1).
   // This pushes a data layer value (2b) into the "key : value: pair below.
  socialView(twitterLink, "twitterClick");
  socialView(gPlusLink, "gPlusClick");
  socialView(linkedInLink, "linkedInClick"); 

//edit: that last bit is pretty sub-par code.

Step 4: Setup Tag
Manager Rules and Tags

The Google Tag Manager interface is not immediately intuitive so give yourself some time to play around with it. Once you get lost in it, you will be amazed by the power it provides.

For this example, we will go through event tracking on the Google+ button on the footer of each page. This will also track any button click for any other button with a designated div “class” attribute of  “social-google.” I use the “class” attribute because my intent is to measure navigation to my social media profiles rather than on-page UX button testing. This can be modified pretty easily to track other events. Let me know if you have questions.

Part 1: Setup the Tag

This tag measures both an Event or Social Event and a Custom Dimensions. This way its possible to track navigation to my Google+ page in aggregate and also at the visitor level.

Part 2: Setup the Rule to fire the Tag

We are tracking a JavaScript event, not to be confused with a Google Analytics event so we use the {{event}} macro to trigger the rule. When the {{event}} matches the string assigned as the data layer variable.

Note: The {{event}} macro must exactly match the data layer and sure that the match is case sensitive!

In the case of the Google+ button, the data layer variable is “gPlusClick.”

Part 3: Create a Container Version

Create a version of your container. Name your container version with a specific and consistent naming convention. This will really help in keeping track of progress and contained tags.

Step 5: Debugging and Google Analytics Real-Time Feature

Good job so far!

Now, to makes sure this is all working properly, preview the new container version. There are few ways to check if your tags are working. First preview your container in “Preview and Debug” mode. If the tags are firing on the target user interaction publish the new container and use the Real-Time feature in Google Analytics to make sure that the virtual pageviews and/or events are being reported properly.

If you followed this whole process or you managed to customize your own event tracking, give yourself a pat on the back! If you have any questions, please comment below or reach me on Twitter.