Inside the Minds of the Machine

APIs, SEO and Content Marketing, Talking Tech

Using Google Tag Manager for Custom Event Tracking

Google Tag Manager (GTM) can be a very powerful tool for managing analytics code, tracking bugs, and custom event tracking such as transaction progress. Instead of dropping separate codeblocks for Google Analytics, other analytics providers, or tracking JavaScript on every page, you can use a single GTM codeblock on every page of your website (easily done if using any sort of CMS or templating system).

Within the GTM user console, you can create and manage ‘Tags‘ (GTM-speak for snippets of HTML or JavaScript, such as your Google Analytics tracking code) that will automatically be included within any page that contains your GTM codeblock. You can add ‘Triggers‘ to control what conditions will cause those Tags to appear and build ‘Variables‘ to let you pass data between Google Analytics or even between Tags. Combining these elements together gives you the power to track all sorts of things in a single interface — including creating your own custom analytics events.

I was particularly interested in tracking custom user interaction events. However, I couldn’t find a complete walkthrough from Google or elsewhere on how to put this all together, so here’s hoping the following example will save you the couple of days it took me to figure it all out.

If you’re already an expert JavaScript programmer and you know GTM quite well and just want to figure out how to create a custom event and track it in Google Analytics, I’ll cut to the chase and give you the recipe now:

  1. Create a GTM container and add the GTM codeblock to pages on your site.
  2. Create a ‘Tag’ to be your event listener.
  3. Create two ‘Triggers’ — one for which pages to listen for the event, and one that fires when your event happens.
  4. Create one or more ‘Variables’ to store the info from your event listener ‘Tag’
  5. Create another ‘Tag’ to take the info you stored in the ‘Variables’ and pass it to Google Analytics.
  6. Go into the Google Analytics console and watch the events come in.

I’m sure this list was so perfectly clear that any bright 4th-grader wouldn’t need more explanation, but in the spirit of showing my work I guess I should walk through an example with a little more detail.

One note: the admin interface for GTM changed significantly in June 2015, so if you’ve worked with GTM before, know that when I refer to ‘Triggers’ and ‘Variables’, these are what used to be called ‘Rules’ and ‘Macros’. As far as I can tell, this naming shift is only about making the labeling clearer, and had no impact on function. I think the new admin interface is quite an improvement, but if you’re used to the old admin (as I was) it may take a bit to get your bearings.

Getting Started

Sign up for Google Tag Manager (it’s free) and create a GTM container. Once you get that ‘container’ created, you’ll be given a block of code to put on pages of your website. For basic testing, it’s ok to just add it to one page. The strength of GTM is being able to manage all of the analytics across your site in one place, so once you get into production you should have one ‘container’ (codeblock) that you put across all pages of your site.

The problem – tracking plays in a custom video player

Here at WNET, we are a member station of the Public Broadcasting System, and PBS provides a custom video player that we can put on our own station website, This player is in an IFRAME, and the IFRAME is hosted in the domain. We wanted to track in Google Analytics when a visitor came to and played a video in that iframe. The same-origin policy means I can’t read events on my webpage; fortunately the PBS video player pushes out status information using the postMessage interface, enabling cross-domain communication. My webpage can listen for those messages once I setup a listener using JavaScript.

The custom JavaScript

Our example deals with particularly complex challenge — listening for cross-domain window messages. If all you want to do is track when someone clicks on a donate button, setting up custom JavaScript is completely unnecessary. However, there are other events you might find interesting to track, like mouse-position or even scrolling; most social sharing buttons such as the Facebook ‘Like’ are rendered as cross-domain iframes, and they may have some postMessage data of interest.

This if/else structure is how we create the event listener that we’ll put at the beginning of our JavaScript. The ‘else’ condition sets it up in Internet Explorer, and receiveVideoMessage is the function which the listener will pass the messages to.

if(typeof window.addEventListener==='function'){

There’s going to be lots of messages sent from the PBS Video Player, but I really just care about one — that video playback has started — and I want to get it into Google Analytics. GTM expects info to be in an array named ‘dataLayer’. There is nothing special about this array other than the name. GTM will automatically create a dataLayer array at some point, but just in case it’s a good idea at the beginning of your function to check if it exists, and create it if necessary.

function receiveVideoMessage(evt){
  if (typeof window["dataLayer"]==='undefined'){
    dataLayer = [];

Another thing to note is that your event listener is probably going to be hearing messages from LOTS of places. As I noted earlier, many social sharing widgets are actually IFRAMES sending window.message info all the time. Again, the same-origin policy restricts what we can find out about IFRAMES on other domains, but we can get the ‘origin’ property of an event. We’re only interested in messages from the server that provided the video player.

if ( evt.origin == '' ) {

The payload of a message will include a ‘data’ element, formatted as a serialized array. This means that the data of the message will be a key-value pair with ‘::’ between them.


Once we get the message we want — ‘video::started’ — we want to push info the dataLayer array as an ‘event’. We use ‘event’ because GTM expects it to be called that.

if (key==="video") {
  if (value==="started") {
      event: 'VideoEvent',

In this case I’ve named the event ‘VideoEvent’.

And that’s ALL of the code we’re writing. Here’s the complete listing for our example: {NOTE: Updated April 15 2020 to reflect changes to PBS video player URLs}

if ( typeof window.addEventListener==='function' ){
} else {

function receiveVideoMessage(evt){
  if ( typeof window["dataLayer"]==='undefined' ){
    dataLayer = [];
  if ( evt.origin == '' || evt.origin == '' ) {
    if (key==="video") {
      if (value==="started") {
          event: 'VideoEvent',

Creating the ‘Tag’ to listen for the event

Within the GTM web console, go into your ‘container’, click ‘Tags’ in the left column and then the ‘New’ button.

There are many different ‘Products’ you’ll be asked to choose from, but what we want is a ‘Custom HTML Tag’ — GTM’s place for you to put whatever code, including custom JavaScript, you want into the container (and thus the webpage the container is on.

You’ll ‘Configure Tag’ by pasting in your JavaScript into the ‘HTML’ field — wrap your code in <script></script>, just as you would if you were putting it into your webpage’s code.

Below the ‘HTML’ field is a checkbox to ‘Support document.write’ that you shouldn’t need, and also ‘Advanced Settings’ that are worth looking at just to see what’s there. They’re well explained (though I’ve never needed to use them).

As an alternative to our complex Javascript, it is probably worth exploring the ‘Event Listener’ product to see the built-in ‘Event Listener’ types. Google hides the ‘Event Listener’ product behind a ‘show more’ toggle.


If you pick the Event Listener product instead of the Custom HTML Tag product, you’ll see choices for ‘Click Listener, ‘Form Submit Listener’, and a few others. You can click into them, and once you do, you’ll see info explaining how to use them with ‘triggers’.

Create a ‘Trigger’ to determine which pages you listen for the event on

Once you create the event listener tag (either your custom JavaScript or the built-in ‘Event Listener’ product), you’ll want to add a ‘Trigger’ to determine what pages to run the event listener code on.

Choose the event ‘Page View’, and then configure the ‘trigger type’ to ‘Page View’, and then what pages to fire the trigger on. The ‘All Page Views’ option is a good start, unless you only want this listener to fire on specific pages.

Here’s what it looks like if you choose ‘Some Page Views’ and choose to only fire the trigger on pages where the URL path starts with ‘/video’.


Create another trigger to fire when the event you’re listening happens

If you used a built-in event listener, you’ve already setup the trigger for listening for the event.

If you’ve built a custom JavaScript event listener, go to the GTM web console for your container again, pick ‘Triggers’ from the left column and create a new trigger. This time it will be for a ‘custom event’, where the ‘event’ value equals whatever you named that ‘event’ that you pushed into the dataLayer. In our example code above, this would be ‘VideoEvent’. The event name is case sensitive.


The ‘add filters’ dialogue on this page would add unneeded complexity and I’ve never used it, so if you want to play with that do so at your own risk.

Create a ‘Variable’ to hold your custom data

‘Variables’ are a way to pass data between tags or from other data sources, and this is how you’ll pass the info between your event listener and Google Analytics.

In the GTM console click on ‘Variables’ — GTM has a ton of ‘Enabled Built-in Variables’, but we want to make a ‘User-Defined Variable’, which you have to scroll down to. Click ‘New’, and you’ll see a wealth of options for ‘Type’ — it is worth clicking through all of the options to see what is there, and if you used a built-in event, you will probably find what you’re looking for in ‘Auto-Event Variable’.

If you built your own JavaScript listener to store data in the dataLayer (as in our example), choose ‘Data Layer Variable’. For the ‘Data Layer Variable Name’, use the variable name you stored in your ‘event’. In our example, this was ‘Videoaction’. Note that ‘Videoaction’ is the variable name, not the ‘event’ name that contained it (which is ‘VideoEvent’).

Create another tag to pass the variable data to Google Analytics

We’re almost done!

Go back to the GTM web console and create a new tag. This time, we’ll choose the ‘Google Analytics’ product, and for the tag type select either ‘Classic Google Analytics’ or ‘Universal Analytics’ depending on what type of account you have.

Put in your GA property ID in the provided space, select ‘event’ for the ‘Track Type’, and in the Event Tracking Parameters, you could either hard-code a value or select one of those variables you’d created to pull in a value from the dataLayer.

Finally, in the ‘Fire On’ choce for this tag, select the trigger you created earlier that fires when the event you’re listening for happens. In our example, we fire this tag whenever something does a ‘push’ to the dataLayer that included an ‘event’ called ‘VideoEvent’. The JavaScript listener we created earlier does that when the video starts playing.

Preview, Test, and Publish

If you’ve worked with GTM before you’re almost certainly familiar with the ‘Preview’ function — it is excellent. In the GTM console in the upper right is the ‘Publish’ button, and there’s a dropdown there with three choices — Publish Now, Preview and Debug, and Save as New Version.

Choose Preview and Debug and now you can open a new window, go to the site you put your container on and click around to make sure the events you’re tracking get fired.

If they are, go on to your Google Analytics console to make sure that you’re seeing the events appear there.

Once it seems like it is all working, exit preview mode and publish your container, and enjoy your new analytics data!

IEG and WNET are not responsible for your or any third party’s use of code from this tutorial. All the information on this website is published in good faith “as is” and for general information purposes only; WNET and IEG make no representation or warranty regarding reliability, accuracy, or completeness of the content on this website, and any use you make of this code is strictly at your own risk.