February 11th, 2015 update
November 15th, 2014 update
  • track onunload event
  • minor fix to error reporting
June 20th, 2014 update
  • now supports multiple players on the same page
  • includes optional code to add the necessary enablejsapi=1 to the video URL
  • now track the video title instead of the video id
September 16th, 2015 update

During our GTM+UA webinar I was asked about tracking YouTube videos with the help of Google Tag Manager and Universal Analytics. I quickly demonstrated how we’re doing it in our WASP playground.

Here’s a step by step guide to the macros, tags and rules required to track YouTube video embeds on your website.

A note for Google Analytics users: For sake of demonstration I’m showing Universal Analytics, but you could easily replace the Universal Analytics Event by a regular Google Analytics one and everything will work fine.


We will use the YouTube JavaScript API to expose user interactions with the video. In order for this to work, each of your YouTube embeds will need to include “enablejsapi=1” to the link of the video, for example:

<iframe width="420" height="315" src="//www.youtube.com/embed/Rvw0uxuYBCo?enablejsapi=1" frameborder="0" allowfullscreen></iframe>

Note: the “Is Youtube present” macro could be modified to automatically add the enablejsapi if it’s not there, but this will result in a quick flash of the Youtube frame.

Note: by default, related videos will be shown and tracked. If you want to disable this, add “&rel=0” to the Youtube embed link.

What you’ll get

Under Behavior/Events/Top Events, you will see an event category named “video”, with event actions named “pause”, “play”, 0%, 25%, 50%, 75% and 100%. The event label will be the unique identifier of the video.

Custom Report for Video Performance

If you want to get details about how individual videos are performing, follow those steps:

  1. Go in yout Behavior/Events/Top Events/video report;
  2. Click on “Customize” in the top left of the report;
  3. Under Dimension Drilldown, remove the “Event Category” dimension and switch “Event Action” with “Event Label”, so it now reads Event Label/Event Action;
  4. Modify the filter so it now reads “Event Category equals video”, so only videos are shown in this new custom report;
  5. Voilà! You now have detailed performance info about each individual video embedded on your website.


Is Youtube present?

You have two options here, either use this macro to automatically detect when a video is embedded on a page, or use a rule to include the Youtube Listener tag only on pages where you know there’s a video.

Macro Name: Youtube is present
Macro Type: Custom JavaScript
Custom JavaScript:
// Return "true" if there is at least one Youtube video on the page
function () {
    for (var e = document.getElementsByTagName('iframe'), x = e.length; x--;)
        if (/youtube.com\/embed/.test(e[x].src)) return true;
    return false;


Macro Name: event
Macro Type: Custom Event

dataLayer action

Macro Name: dataLayer action
Macro Type: Data Layer Variable
Data Layer Variable Name: action

dataLayer label

Macro Name: dataLayer label
Macro Type: Data Layer Variable
Data Layer Variable Name: label


Listen for Youtube activity

Tag Name: Youtube Listener
Tag Type: Custom HTML Tag
Firing Rule: Youtube present
<script type="text/javascript">
// OPTIONAL: Enable JSAPI if it's not already on the URL
// note: this will cause the Youtube player to "flash" on the page when reloading to enable the JS API
for (var e = document.getElementsByTagName("iframe"), x = e.length; x--;)
  if (/youtube.com\/embed/.test(e[x].src))
     if(e[x].src.indexOf('enablejsapi=') === -1)
        e[x].src += (e[x].src.indexOf('?') ===-1 ? '?':'&') + 'enablejsapi=1';

var gtmYTListeners = []; // support multiple players on the same page
// attach our YT listener once the API is loaded
function onYouTubeIframeAPIReady() {
    for (var e = document.getElementsByTagName("iframe"), x = e.length; x--;) {
        if (/youtube.com\/embed/.test(e[x].src)) {
            gtmYTListeners.push(new YT.Player(e[x], {
                events: {
                    onStateChange: onPlayerStateChange,
                    onError: onPlayerError
            YT.gtmLastAction = "p";

// listen for play/pause, other states such as rewind and end could also be added
// also report % played every second
function onPlayerStateChange(e) {
    e["data"] == YT.PlayerState.PLAYING && setTimeout(onPlayerPercent, 1000, e["target"]);
    var video_data = e.target["getVideoData"](),
        label = video_data.video_id+':'+video_data.title;
    if (e["data"] == YT.PlayerState.PLAYING && YT.gtmLastAction == "p") {
            event: "youtube",
            action: "play",
            label: label
        YT.gtmLastAction = "";
    if (e["data"] == YT.PlayerState.PAUSED) {
            event: "youtube",
            action: "pause",
            label: label
        YT.gtmLastAction = "p";

// catch all to report errors through the GTM data layer
// once the error is exposed to GTM, it can be tracked in UA as an event!
// refer to https://developers.google.com/youtube/js_api_reference#Events onError
function onPlayerError(e) {
        event: "error",
        action: "GTM",
        label: "youtube:" + e

// report the % played if it matches 0%, 25%, 50%, 75% or completed
function onPlayerPercent(e) {
    if (e["getPlayerState"]() == YT.PlayerState.PLAYING) {
        var t = e["getDuration"]() - e["getCurrentTime"]() <= 1.5 ? 1 : (Math.floor(e["getCurrentTime"]() / e["getDuration"]() * 4) / 4).toFixed(2);         if (!e["lastP"] || t > e["lastP"]) {
            var video_data = e["getVideoData"](),
                label = video_data.video_id+':'+video_data.title;
            e["lastP"] = t;
                event: "youtube",
                action: t * 100 + "%",
                label: label
        e["lastP"] != 1 && setTimeout(onPlayerPercent, 1000, e);

// Crossbrowser onbeforeunload hack/proxy
// https://developer.mozilla.org/en-US/docs/WindowEventHandlers.onbeforeunload
window.onbeforeunload = function (e) {
 var e = e || window.event;
 // For IE and Firefox prior to version 4
 e.returnValue = 'na';
 // For Safari
 return 'na';
window.onbeforeunload = trackYTUnload;
function trackYTUnload() {
 for (var i = 0; i < gtmYTplayers.length; i++)
 if (gtmYTlisteners[i].getPlayerState() === 1) { // playing
 var video_data = gtmYTlisteners[i]['getVideoData'](),
 label = video_data.video_id+':'+video_data.title;
 event: 'youtube',
 action: 'exit',
 label: label
// load the Youtube JS api and get going
var j = document.createElement("script"),
    f = document.getElementsByTagName("script")[0];
j.src = "//www.youtube.com/iframe_api";
j.async = true;
f.parentNode.insertBefore(j, f);

UA Youtube event

If you are using Google Analytics instead of Universal Analytics, simply change the Tag Type to Google Analytics and everything will work fine.

Tag Name: Youtube Event
Tag Type: Universal Analytics
Tracking ID: UA-XXXXXX-Y
Track Type: Event
Event Tracking Parameters:
Category: {{event}}
Action: {{dataLayer action}}
Label: {{dataLayer label}}
Firing rule: Youtube event


Youtube embed found on a page

Rule Name: Youtube present
Conditions: {{event}} equals gtm.dom AND {{youtube is present}} equals true

Youtube is telling us something

Rule Name: Youtube event
Conditions: {{event}} equals youtube

Putting it all together

That’s it! Now you can track Youtube video embeds on your own website thanks to Google Tag Manager and Universal Analytics.

Don’t forget to use Google Tag Manager “debug & preview” with WASP to make sure everything works fine before you publish.

This topic, and many more, are covered in our GTM+UA  Master Class. Please check the Cardinal Path training calendar for more locations.

Check out the new, and up to date version by Nicky Yuen.

  • This is great Stephane. Thanks for sharing. I like the use of {{event}} equals gtm.dom.

  • Sam Briesemeister

    Stephane, very nice approach you have here. On first glance it looks like your code (starting in onYouTubeIframeAPIReady) may be using some global variables (e and x) that could cause serious interference with other parts of a poorly-coded site. It looks like you may just need to declare them with “var” in that for(…) loop header. Thanks for sharing!

    • Stephane Hamel

      Excellent point Sam – and a general best practice!
      I have updated the article accordingly.

      • TaniaSteenkamp

        Hi Stephane, Thanks for this article I found it incredibly helpful, and thanks for updating it on an ongoing basis. You rock!

  • Elena

    Thank you for sharing. How would I set up this event in my Google Analytics account so I could see the results of tracking in the report?

    • Stephane Hamel

      You can look under Behavior/Events/Top Events and you will see an event category called “video”. If you drill-down in this entry, you will find play, pause, 0%, 25%, 50%, 75% and 100%. This will give you an appreciation of the number of videos that were looked at until the end. If you wanted to have details about a specific video, you could customize the default view and simply put the “event label” dimension before the “event action”. (I will updated the article with more info about this)

  • Harm te Molder

    Hi Stephane,

    Just implemented this and it works great. Had to fix the Custom HTML Tag though. You are missing three “)”s after the match-functions. Or was that a copy-paste error of mine?

    • Stephane Hamel

      You are right – looks like the copy/paste on my end scrambled just those – fixed now. Thanks!

  • Thanks for this great post I need to go implement this like now!

  • AlexMorask

    Hey Stephane,

    This is awesome. Thank you for posting. I just have one quick question. Do you have any ideas as to why the event label (the video ID) wouldn’t be coming through in GA? I’ve gotten the event to fire and I see category and actions coming through, but the labels are not populating. I know this is somewhat vague, but I figured I’d ask to see if you’ve ran into this trouble before. Thank you!

    • Stephane Hamel

      @harmtemolder:disqus noticed a little glitch and that’s what might have been causing this problem. The post is updated now. Basically, where you see the label assigned through the getVideoUrl() call, make sure there’s a closing parenthesis before [1] (somehow got mangled when I pasted the code)

      • AlexMorask


        Made the change. Works perfectly now. Thanks so much for posting, this is really awesome.

      • Guest

        Hey Stephane,
        I am not able to fix this.Event label is still not coming through GA. For event category, it shows gtm.dom and action is undefined. Please help!

  • Simple steps which solved my query, I was searching for the steps yesterday and today i got it. That awesome and wonderful for me

  • lfaber

    Nice stuff Stephan! I had been struggling with this for weeks. Now to get tracking working when videos play within a Lightbox (yes that’s a challenge). 😉

  • lfaber

    Small typo in the “What You’ll Get” section. The event category should be “youtube” not “video”. 😉

  • ryandigital

    Hi Stephane,

    Thank you so much for this post! It works on my end. Good thing that I found your article. It solved my problem! Thank you! But I still have one more to solve..

    How can I apply this to other media players like Vimeo? I do have an embedded Vimeo video in my site and I am not sure how your scripts can be converted to track the video plays.

  • Audrey

    Hi Stéphane,
    Thanks so much for this article, extremely helpful!

    Could you maybe elaborate on how we can modify the “Is YouTube present” macro to automatically add the enablejsapi if it’s not there?

    • I would like to know this as well! Thanks!

      • Mark H

        As of now it seems like it’s unavailable – probably due to the upgrade to UA just starting.

        I bet though you can have your own php to add it if it’s not present across all pages.

        • Shawn W

          Any updates on this one? I’m also curious.

  • Cameron

    love the post Stephane… how would I modify the tracking if I only wanted to capture plays? At present Google Analytics is creating a new event every time someone clicks on the video (even to pause or skip).

  • Rachel Sweeney


    Thanks this is great. Small problem with me is that I can’t see to make it work. I’m wondering if it’s because we are using a third party player that gives us more control over the look and feel. Will this only work with the standard setup?

  • Marcelo Capucci

    Hi Stéphane. This awesome code need some update to work if the video is already playing, or I’m missing something? Thanks!

  • EndersDrift

    Hi, I’m using the code seen here: http://stackoverflow.com/questions/23634106/using-google-analytic-api-for-video-how-can-i-have-two-videos-on-one-page and am trying to figure out how I can track a second video? Do you maybe have a page of the finished project? I don’t really understand all of the listener stuff.

  • R. Stephen Gracey

    I am still not getting any video url or label, and when I use the {{event}} as the category, I get “undefined,” so I’ve hard-coded that as “youtube,” but I can’t figure out why it’s not working. Anyone else still having this trouble?–all my videos get lumped together as “label,” and so it’s impossible to determine which is which.

    • Veronica

      Are you filtering out your own traffic maybe?

  • Nick Lindauer

    I’m getting the same error as reported by Stephen Gracey – all events are coming through as undefined. The category is gtm.dom – action is undefined and label is not set – any ideas?

    • Jack

      Any updates?>

  • Jose Ramón Cajide

    Hi Stéphane.

    I have tried another ways of tracking Youtube videos before but your approach is the best.

    I like to use event tracking through GTM so I will try to adapt this great script.

    Now, I cannot get your script to work when the video source in the iframe doesn’t include the enablejsapi=1 parameter altough the script should dynamically add it.

    Do you know it this could be a problem related to GTM?

    Thank you very much

  • Julia K.

    Hello Stephan,
    Thanks a lot for the explanations and the code.

    Is this code completly free to use (is it from Cardinal Path?). And is there any disclamer to add?

  • Christoffer Luthman

    You have to append the URL to the clip with “?enablejsapi=1&origin=http://yoursite.com”.

  • Sean

    Great article! I put it to use right away. Thanks for taking the time.

  • Olli

    I am also getting the event category as ‘undefined’, action and label work fine though. Is there a solution?

  • Todd Miller


    Excellent work. Quick question, can you elaborate on the 0% designation?
    Assuming this video was played and then stopped or paused somewhere
    before the 25% mark? Is this correct? Also, is the “Play” designation
    suppose to be an accumulative metric of all video plays regardless of
    where the user started, stopped or paused?


    • Alex

      I’ve been using this to track videos on a site for a couple of weeks now. From my experience, 0% records when the video first begins playing and a ‘play’ is also recorded. Subsequent ‘pause’ and ‘play’ are recorded if the users starts and stops the video (yes, an accumulative metric). And at the very end, 100% is recorded, as is a pause.

      Hope that answers your question.

  • Stephane Hamel

    For anyone interested, I have just updated the code with some cool stuff:
    – now supports multiple players on the same page
    – now tracks the video title instead of just the id
    – includes code to add the necessary enablejsapi=1 to the Youtube video URL

    As always, feedback and comments are welcome! 🙂

    • Audun Rundberg

      In the code where you’re tracking playback by 0%, 25%, etc, the code is sending the ID and not the video title.

      label: e[“getVideoUrl”]().match(/v=([^&]+)/)[1]

      should be

      label: label

      like in the others.

      Oh, and I love this. 🙂

      • Stephane Hamel

        Good catch! Made the minor fix in the article

    • farahat

      May I ask how can I make it trigger after a certain number of seconds rather than percent.
      I want to be able to track event when the user watches 30 seconds of the video. I tried to tweak the code with current time, but cant get it to work.

      • Stephane Hamel

        The OnPlayerPercent function is where the action happens – you could modify the code to check if e[“getCurrentTime”]() is over 30 seconds and fire an event once.

  • Steven Johnson

    Will this listener work if the ?autoplay=1 query param is present on youtube embed source? I was able to get this code to work on a site beautifully a few months ago, but it will not work on the one I am implementing now – the only substantial difference between the two sites seems to be the presence of ?autoplay=1.

  • Northpoint Toyota

    Great Post, I will use it when I set up my GTM.
    With regard to DIMENSION WIDENING – identifying the video name, the data import sounds a bit messy. When I had traditional event tracking code on my YT embedded Videos I simply used the GA Search and Replace filter to replace the video code e.g. replace the Label Rvw0uxuYBCo with your Video Title. The filter comes into play after the event is triggered so it should work with GTM as well? Could this work providing the YT code is the Event Label?

  • munaazanjum

    Thank you +Stéphane Hamel for updating the code. Yup, the title shows up. Awesome!!

  • Awesome!! Tks!!!

  • devumvertcap

    Hi Stéphane,
    could you give me a hint how I can get the tracking to work after manipulating the DOM with jquery? I would like to append one div containing a youtube iframe to another div, triggered via a button click. After clicking the button, the tracking does not work anymore.

    Best regards,

    • Azucena Coronel


      I am as well wondering how to do this. Did you find any workaround?

      Thanks in advance,

      • devumvertcap

        Hi Azucena,

        I am working with simple jquery hide/show now, that works for me. however, this is only a workaround

        Best regards

    • Fuadz Miah

      Exactly the same situation. Would love to hear from Stephane.

  • Cyj Andrew

    This is really, awesome, thanks Stephane.
    The code samples worked first time, and the description of the process is really clear.
    Much appreciated.

  • Gabriel

    Really helpful article, I didn’t knew GTM could do cool tricks like this.

    However, I don’t know what I’ve been doing wrong but I can’t get the youtube events to show in Google Analytics. The are fired in the GTM debugger, the datalayer has all the correct information, the events seems to be firing in WASP but I just get the gtm.dom events in GA. No sign of any youtube events. It seems to be the same problem as Marc H and Shawn W.

    Any idea?

    • Gabriel

      I found the issue:

      Make sure the “Youtube Event” tag has “Youtube event” as firing rule and not “Youtube present”

      Found the issue while reading this excellent article:


      • TaniaSteenkamp

        Hi Gabriel,

        I just don’t seem to be able to get “Youtube Event” to fire at all, I checked and “Youtube event” is my firing rule…any suggestions of where I might be going wrong?

  • AG

    this is awesome, thanks!

  • L Nguyen

    Hi Stephanie, after installing the code into GTM and testing in debug mode, I see that the events are firing, however they are not firing under my personal UA account. The UA string that shows up is UA-444060xx-1 (last numbers not being disclosed)

    This may be the reason why many of us are not seeing the events fire in our GA acct. Is this the same for everyone else? or just me?

    After deleting the code, I no longer see events in UA-444060xx-1 fire an event in debug mode. Can you please help explain why this might be happening

  • Rob McConachie

    Hi Stéphane,

    Thanks for creating this. I was on http://mrrobwad.blogspot.ca/ and saw a posting in which you posted you maybe creating one for Vimeo? Any update on that?

    Much appreciated.


  • Guillaume Laurier

    Hi Stéphane,

    This worked perfectly with one of my videos that is directly embedded on a page. However, I have a video on my home page that is in a pop up. There is an image that, if you click on it, a youtube video will pop up on the screen and play. The thing is that the google tag manager youtube listener cannot listen to it. Is there a way we can fix this?


  • Alex

    How would this be enabled using the new V2 Google Tag Manger interface ? (Currently in Beta but will be required Jan 1st). Thanks.

  • Hi Stephane: I (along with others) am struggling with getting this to work with Popups. I do have it working perfectly on a site that has iFrames and Popups. Well, the iFrame videos work perfectly. I have been working with the Developer and have given him the requisite instructions per your post. However, the YouTube events are not getting pushed to the Data Layer. In our case, we are using the Magnific Plugin: dimsemenov.com/plugins/magnific-popup/

    Any help or suggestions will be greatly appreciated (by me & the other posters)

  • Kris416

    Hi Stephane, Really great article. This is wonderful. Just to add an option to it: I removed the video id form the code because it was passing in an “ugly” id and then the video title. For our purposes to have a cleaner looking data set I am just passing in the video title. For us it will unclutter the naming in dashboards and excel downloads etc.

    Alas, I can’t get it to work on a second video played within the player after the initial video is finished. I will work with our dev’s to try to crack that problem.

  • savanzi

    Hi Stephane,
    this works perfectly with Google Tag Manager default UI.
    Now, with GTM “New UI”, are there any changes necessary to let it work, or are there some simplifications that can be done, taking advantage by New UI features?

  • Frank

    Hi Stéphane, everything works despite the error tracking – I see values as ‘undefined-4’ or ‘undefined-5’. Any idea why? Thanks!

    Btw it’s a bit annoying that if a video ends it is tracked as “paused” but I guess we can live with that 🙂

  • BeckyVardaman

    Thanks so much for this! I’m having trouble getting it to work with this code:
    Our code:

  • Crimsonjade

    Hi, I’m wondering if you have a similar solution for Vimeo? I’ve been trying to do so using vimeo.ga.js (https://github.com/sanderheilbron/vimeo.ga.js/issues/14), but all I’m seeing is ‘undefined’ since the implementation…so I’m not sure what I’m doing wrong…I’d like to be able to use GTM with UA, but without having to deal with embedding code directly into the page (as we cannot). Thanks in advance!

  • Veronica

    Question: I followed the steps and when in Preview mode, it works. When I go to my GA account, I see my own events ONLY when I am in debug mode. But when I exit debug mode and I play a video I don’t see my own events in my GA account. I switched back to debug mode to test, and I see my own events. Then I don’t when I turn off debug.

    Any help on why this is happening?

    NEVERMIND – forgot to publish!!!