How to track Youtube and Vimeo videos in Google Analytics and Google Data Studio via Google Tag Manager

track youtube vimeo videos

If you are using videos on your website, you have probably asked yourself many times if users are actually watching your videos and if they do, how much of the video do they watch?

Luckily, with some work in Google Tag Manager, you can get this tracking up and running and start collecting the data in Google Analytics. And later, you can use that data to create visually appealing charts in Google Data Studio, which are much easier to read and interpret than the reports from Google Analytics, a move for which both your shareholders and clients will thank you.

Table of contents:


Tracking Youtube videos via Google Tag Manager

Since Google Tag Manager has built-in support for Youtube, tracking Youtube videos is really easy and consists of enabling a few built-in variables and creating one trigger and one tag.

Enabling the built-in variables

The first thing to do is to enable the built-in tags, so from the Variables menu, by clicking on the Configure button from the right side, we need to enable the “Video Status”, “Video Title” and “Video Percent” built-in variable.

youtube variables google tag manager

Creating the trigger

After this, from the Triggers menu, we need to create a “YouTube Video” type trigger, with the “Start”, “Complete”, “Progress” and “Add JavaScript API support to all YouTube videos” options enabled.
For the “Progress” option, we need to enter “25, 50, 75” in the percentages field, as shown in the image below.

youtube trigger

After saving the trigger, we need to create the actual tag which will send the events to Google Analytics.

Creating the tag

For this, from the Tags menu, we should click on New and select a “Google Analytics: Universal Analytics” tag type with the “Track type” set as “Event”.

In the category field we can enter “Youtube Video”, in the action field we can enter the variables “Video Status” and “Video Percent” which we enabled earlier, while in the label field, we can enter the “Video Title” variable.

youtube tag in gtm

After this we need to choose our GA settings variable in the “Google Analytics Settings” field, add the “Youtube Video Interaction” trigger created earlier and save the tag.

youtube trigger in gtm

With this, our Youtube video tracking should be ready so after enabling the “Preview and debug” mode in GTM, we can test how it works.

Testing the implementation

If everything is working correctly, we should see the “Youtube Video” events in the “Real Time” report from Google Analytics.

Youtube tracking google analytics real time report

Tracking Vimeo videos via Google Tag Manager

Because Google Tag Manager does not offer a built-in integration with Vimeo like it does with Youtube, implementing the tracking for Vimeo videos is a little bit harder than it is for Youtube, but it still follows the same process: creating the variables, triggers and tags.

Pro tip: For Vimeo tracking, if you want to install the tracking faster, instead of manually creating all the variables, tags and triggers, you can import our GTM container which already has all the tags, triggers and variables in it. You can download the container from here (you need to unzip the file first, then upload the JSON file to GTM).

When importing the container, make sure to select “Merge” containers as in this way you will keep all your previous tags, variables and triggers in place.

If you want to create the tracking manually, you can follow the steps below, otherwise, if you are importing the already created container, you can skip it.

Creating the variables for Vimeo video tracking

Our implementation uses 3 variables: videoAction, videoName and “Custom Javascript – Is Vimeo Present”. The first 2 values get the value from the dataLayer while the “Custom Javascript – Is Vimeo Present” checks if a Vimeo video is present on the page and returns true or false.

To create the videoAction variable, from the “Variables” menu, click “New” and select “Data Layer Variable” as the variable type. In the “Data Layer Variable Name” field enter “attributes.videoAction” and name this variable “videoAction”. After this you can save the variable.

videoAction variable gtm

The next variable, videoName, is similar to videoAction with the only difference being that the value from “Data Layer Variable Name” field is “attributes.videoName”.

videoName gtm variable

For the third variable, “Custom Javascript – Is Vimeo Present”, you need to select “Custom JavaScript” as the variable type and paste the below code there:

//this function checks for the presence of an embedded Vimeo video
//if one or more videos are present, return true, otherwise return false
function () {
for (var e = document.getElementsByTagName("iframe"), x=0; x < e.length; x++) {
if (/^https?:\/\/player.vimeo.com/.test(e[x].src)) {
return true;
}
}
return false;
}

Creating the triggers

Our setup uses 2 triggers: “Pageview – Vimeo Player is Present” and “Vimeo Video Interaction”.
For the “Pageview – Vimeo Player is Present” trigger, from the “Triggers” menu, click “New” and create a “DOM Ready” type trigger, with the value of “Custom Javascript – Is Vimeo Present” variable set to “true”.

vimeo is present gtm trigger

For the “Vimeo Video Interaction” trigger, choose “Custom Event” as the trigger tag and add “vimeoTrack” in the “Event Name” field.

vimeo video gtm trigger

Creating the tags

Our Vimeo video tracking solution uses 2 tags: a custom HTML video interaction listener and one event tag which sends the data to Google Analytics.

For the Vimeo video interaction listener tag, create a Custom HTML tag and copy the below code in it.

<script>
;(function(document, window, config) {
'use strict';
// The API won't work on LT IE9, so we bail if we detect those UAs
if (navigator.userAgent.match(/MSIE [678]\./gi)) return;
config = cleanConfig(config);
var handle = getHandler(config.syntax);
if (document.readyState !== 'loading') {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
// Watch for new iframes popping in
document.addEventListener('load', init, true);
function init() {
var videos = filter_(selectAllTags_('iframe'), isVimeo);
if (!videos.length) return;
loadApi(function() {
forEach_(videos, listenTo);
});
}
function isVimeo(el) {
return el.src.indexOf('player.vimeo.com/video/') > -1;
}
function loadApi(callback) {
if (isUndefined_(window.Vimeo)) {
loadScript('https://player.vimeo.com/api/player.js', callback);
} else {
callback();
}
}
function listenTo(el) {
if (el.__vimeoTracked) return;
el.__vimeoTracked = true;
var video = new Vimeo.Player(el);
var percentages = config._track.percentages;
var eventNameDict = {
'Play': 'play',
'Pause': 'pause',
'Watch to End': 'ended'
};
var cache = {};
video.getVideoTitle()
.then(function(title) {
forEach_(['Play', 'Pause', 'Watch to End'], function(key) {
if (config.events[key]) {
video.on(eventNameDict[key], function() {
handle(key, title);
});
}
});
if (percentages) {
video.on('timeupdate', function(evt) {
var percentage = evt.percent;
var key;
for (key in percentages) {
if (percentage >= percentages[key] && !cache[key]) {
cache[key] = true;
handle(key, title);
}
}
});
}
});
}
function cleanConfig(config) {
config = extend_({}, {
events: {
'Play': true,
'Pause': true,
'Watch to End': true
},
percentages: {
each: [],
every: []
}
}, config);
forEach_(['each', 'every'], function(setting) {
var vals = config.percentages[setting];
if (!isArray_(vals)) vals = [vals];
if (vals) config.percentages[setting] = map_(vals, Number);
});
var points = [].concat(config.percentages.each);
if (config.percentages.every) {
forEach_(config.percentages.every, function(val) {
var n = 100 / val;
var every = [];
var i;
for (i = 1; i < n; i++) every.push(val * i);
points = points.concat(filter_(every, function(val) {
return val > 0.0 && val < 100.0;
}));
});
}
var percentages = reduce_(points, function(prev, curr) {
prev[curr + '%'] = curr / 100.0;
return prev;
}, {});
config._track = {
percentages: percentages
};
return config;
}
function getHandler(syntax) {
syntax = syntax || {};
var gtmGlobal = syntax.name || 'dataLayer';
var uaGlobal = syntax.name || window.GoogleAnalyticsObject || 'ga';
var clGlobal = '_gaq';
var dataLayer;
var handlers = {
'gtm': function(state, title) {
dataLayer.push({
event: 'vimeoTrack',
attributes: {
videoAction: state,
videoName: title
}
});
},
'cl': function(state, title) {
window[clGlobal].push(['_trackEvent', 'Videos', state, title]);
},
'ua': function(state, title) {
window[uaGlobal]('send', 'event', 'Videos', state, title);
}
};
switch(syntax.type) {
case 'gtm':
dataLayer = window[gtmGlobal] = window[gtmGlobal] || [];
break;
case 'ua':
window[uaGlobal] = window[uaGlobal] || function() {
(window[uaGlobal].q = window[uaGlobal].q || []).push(arguments);
};
window[uaGlobal].l = +new Date();
break;
case 'cl':
window[clGlobal] = window[clGlobal] || [];
break;
default:
if (!isUndefined_(window[gtmGlobal])) {
syntax.type = 'gtm';
dataLayer = window[gtmGlobal] = window[gtmGlobal] || [];
} else if (uaGlobal&& !isUndefined_(window[uaGlobal])) {
syntax.type = 'ua';
} else if (!isUndefined_(window[clGlobal]) && !isUndefined_(window[clGlobal].push)) {
syntax.type = 'cl';
}
break;
}
return handlers[syntax.type];
}
function extend_() {
var args = [].slice.call(arguments);
var dst = args.shift();
var src;
var key;
var i;
for (i = 0; i < args.length; i++) {
src = args[i];
for (key in src) {
dst[key] = src[key];
}
}
return dst;
}
function isArray_(o) {
if (Array.isArray_) return Array.isArray_(o);
return Object.prototype.toString.call(o) === '[object Array]';
}
function forEach_(arr, fn) {
if (Array.prototype.forEach_) return arr.forEach.call(arr, fn);
var i;
for (i = 0; i < arr.length; i++) {
fn.call(window, arr[i], i, arr);
}
}
function map_(arr, fn) {
if (Array.prototype.map_) return arr.map.call(arr, fn);
var newArr = [];
forEach_(arr, function(el, ind, arr) {
newArr.push(fn.call(window, el, ind, arr));
});
return newArr;
}
function filter_(arr, fn) {
if (Array.prototype.filter) return arr.filter.call(arr, fn);
var newArr = [];
forEach_(arr, function(el, ind, arr) {
if (fn.call(window, el, ind, arr)) newArr.push(el);
});
return newArr;
}
function reduce_(arr, fn, init) {
if (Array.prototype.reduce) return arr.reduce.call(arr, fn, init);
var result = init;
var el;
var i;
for (i = 0; i < arr.length; i++) {
el = arr[i];
result = fn.call(window, result, el, arr, i);
}
return result;
}
function isUndefined_(thing) {
return typeof thing === 'undefined';
}
function selectAllTags_(tags) {
if (!isArray_(tags)) tags = [tags];
return [].slice.call(document.querySelectorAll(tags.join()));
}
function loadScript(src, callback) {
var f, s;
f = document.getElementsByTagName('script')[0];
s = document.createElement('script');
s.onload = callCallback;
s.src = src;
s.async = true;
f.parentNode.insertBefore(s, f);
function callCallback() {
if (callback) {
callback();
s.onload = null;
}
}
}
})(document, window, {
'events': {
'Play': true,
'Pause': true,
'Watch to End': true
},
'percentages': {
'every': 25,
'each': [25, 75]
}
});
/*
* Configuration Details
*
* @property events object
* Defines which events emitted by YouTube API
* will be turned into Google Analytics or GTM events
*
* @property percentages object
* Object with configurations for percentage viewed events
*
*   @property each Array|Number|String
*   Fires an event once each percentage ahs been reached
*
*   @property every Array|Number|String
*   Fires an event for every n% viewed
*
* @property syntax object
* Object with configurations for syntax
*
*   @property type ('gtm'|'cl'|'ua')
*   Forces script to use GTM ('gtm'), Universal Analytics ('ul'), or
*   Classic Analytics ('cl'); defaults to auto-detection
*
*   @property name string
*   THIS IS USUALLY UNNECESSARY! Optionally instantiate command queue for syntax
*   in question. Useful if the tracking library and tracked events can fire
*   before GTM or Google Analytics can be loaded. Be careful with this setting
*   if you're new to GA/GTM. GTM or Universal Analytics Only!
*/
/*
* v1.0.2
* Created by the Google Analytics consultants at http://www.lunametrics.com
* Written by @notdanwilkerson
* Documentation: https://github.com/lunametrics/vimeo-google-analytics/
* Licensed under the MIT License
*/
</script>

As a firing trigger we should set the “Pageview – Vimeo Player is Present” trigger we created earlier. For the name, we chose “cHTML – Vimeo Listener”.

custom html gtm vimeo

For the next tag, we should create an “Event” track type tag with category set to “Vimeo video”, action set to “videoAction” variable and label set to “videoName” variable.

ga vimeo gtm tracking

For the trigger we should use the “Vimeo Video Interaction” trigger we created in the previous step. After this we can give our tag a descriptive name such as “GA – Event – Vimeo Video” and save it. The only thing remained to do is test it.

Testing the implementation

To test the implementation, enable the preview mode in GTM (or refresh it, if it’s already enabled) and go to one of your pages where you have a Vimeo video. If everything is working correctly, when you will start watching the video, you should see the events in Google Analytics “Real Time” report. If you do so, you can publish the GTM changes as the Vimeo video tracking is working.

vimeo tracking events

Reporting the data in Google Data Studio

If you want to take your reporting to the next level and make it easier for users to read the data, you can create a pivot table in Data Studio which will show how many times each video was played and viewed until the end.

Assuming that you already connected Google Analytics with your Data Studio dashboard, when you are in a page from your dashboard, go to “Insert” from the top menu and choose “Pivot table”. This will add a blank pivot table on your page.

create pivot table data studio

Next, you need to select “Event Label” as the Row dimension and “Event Action” as the Column dimension. For the Metric field you can use “Total Events”.

select dimension data studio

Optionally, to make the table more user friendly, you can click on the “ABC” button next to the Row and Column dimensions and rename them to “Video Name” and “% watched”.

By default, this table will show the data from all events, so in order to include only the video events, we need to create a filter for it.

To do so, in the data panel, scroll down until the filters section and click on “Add a filter”. In there, set the condition to include only events where “Event Category” equals “Youtube Video” (or contains “Video” if you also want to include Vimeo videos).

video filter data studio

After adding the filter, the pivot table report should be ready and should look like this:

video report data studio

Adding video tracking to your site is not hard, but it takes a lot of time and involves many steps, which makes it more prone to implementation errors. That is why, to be sure that video tracking is implemented and working correctly, it is best to let a Google Tag Manager expert do it.

26 Comments Leave a reply

  1. This is a great article. Very helpful guide for those looking to track video views on their website. Google Tag Manager isn’t necessarily the most intuitive platform, so this comprehensive guide (which is clearer than most that are out there) should become a bookmarked resources for marketers in this space. It’s so important to understand the level of interaction with your video content. Video can be expensive, and if not expensive, at least time-consuming. Understanding how your videos are important can help you fine-tune the content and get the most ROI.

    6

    0
  2. Good tutorial. I finally got the Vimeo tracking working after wondering for so much time whether my visitors are watching the videos or not.

    1

    0
  3. How can I modify this so that it doesn’t track on every play/pause? Only track first play, 25%,50%,75%,100%?

    2

    0
    1. You can track the play/pause by enabling “Pause, Seeking and Buffering” in the “Youtube Video Interaction” trigger.

      0

      0
        1. By default, the script should also track pause and multiple play button clicks for Vimeo videos. Have you tried to embed the Vimeo video in a different way on the site?

          0

          0
          1. Correct. I only want it to track the first time play is clicked and not track any pauses. So only one play and no pauses. I do not want it tracking everytime they may click play after a pause.

            0

            0
          2. In this case, you need to set “Pause” to “false” on line 230 from the cHTML – Vimeo Listener tag.

            0

            0
    1. Unfortunately no. You can either disable it completely so no play clicks are tracked or leave it enabled and have all play clicks tracked. By the way, in Google Analytics, you can use unique events instead of total events when analyzing your data as in this way, only one play action will be taken into account.

      1

      0
  4. I followed the recommended steps to set up tracking for Vimeo events using Google Tag Manager. It works well for all Vimeo embeds EXCEPT Livestream embeds with a Playlist. It recognizes the DOM is ready, but no Vimeo Video Interaction was fired. Does it have something to do with a different iframe src url format?

    Please advise. Thanks!

    0

    0
  5. I downloaded and uploaded the container for vimeo videos. When I preview and test in GTM, the tags are firing correctly, but I don’t see the events at all in GA. Anything I might be missing?

    0

    0
    1. Can you check if your Google Analytics settings variable is configured correctly in GTM and that GA property ID matches with the tracking id from GA?

      If tags are firing in GTM then events should be sent to GA. Also, make sure you are not using any ad-blockers which might block Google Analytics events.

      0

      0
      1. Hi Victor,

        I am able to have other GTM tags tracked properly from google analytics. Where in this container do I need to make sure to have my GA tracking ID? I put in in under the Google Analytics settings in the GA – Event – Vimeo Video tag. As for ad blocker do you just mean for my personal browser?

        -Steven

        0

        0
        1. Hi Steven!

          Yes, you need to enter the GA tracking ID in the “GA – Event – Vimeo Video” tag in the “Tracking ID” field, as shown in the image below.

          Setting up GA tracking id in GA - Event - Vimeo Video tag

          Alternatively, if you have a Google Analytics settings variable with the correct tracking id, you could use it instead of setting up the tracking id manually. In this way the GA settings will be consistent across all your events. Just don’t forget to disable “Enable overriding settings in this tag” when using the Google Analytics settings variable.

          As for ad blockers – yes, if you use an ad-blocker in your personal browser, you could disable it while testing this.

          0

          0
          1. Hi Victor,

            Disabling “Enable overriding settings in this tag” did the trick for me, for the most part. It works for all the videos that aren’t live stream videos. We also have livestream videos we would like to track. For these videos, I don’t have the cHTML – Vimeo Listener tag firing as I test in GTM. Any thoughts on changes that could be made to make sure to track live stream videos as well? (The live stream video is embedded and automatically plays as the page is opened.

            1

            0
  6. Thanks for a great guide! I already had YouTube tracking set up and was looking to set up something similar for Vimeo videos, but to make it completely similar I’m missing one thing: The Video URL. Because some of my video titles are very similar, I’d like to have the video URL sent to Google Analytics as well. For YouTube it’s just a question of enabling the built in variable Video URL, but then there’s Vimeo… Do you know of an easy way to get the Vimeo video URL too?

    0

    0
    1. You’re welcome Rasmus!

      The script does not track the Video URL by default but since you requested it, I customized the script to also track the Vimeo Video URL. You can get the script from https://codepen.io/analytics_help/pen/rNeJWKq

      To make this work, you will also need to create a new Data Layer custom variable called videoURL. When creating this variable, in the “Data Layer Variable Name” field, enter attributes.videoURL as shown in the image below.

      videoURL variable

      Next, you should add this variable to the “GA – Event – Vimeo Video” tag. In my tests, I added the videoURL in event label field, next to videoTitle variable.

      Video GTM tag

      If everything works well, you should see the video URL next to the video title in the event label dimensions from GA.

      Event sent to GA

      Event in GA

      If you still need help with this tracking, feel free to contact us.

      P.S.
      Although we like helping our readers, finding a solution to this request took us some time. From exploring the various tracking options, to testing, implementation, etc, it all takes time.

      So would you (and other readers that find this solution useful) be kind and return the favor by sharing this article (or maybe some of our newer articles)?

      Thanks for the understanding.

      1

      0
  7. Interesting piece, would be interesting to use a bit similar setup to trigger “viewContent” events for the FB pixel as well and send not only data to Google Analytics but to FB as well for retargeting of people watching content

    1

    0
    1. Good idea! You can already do this with the existing setup. The only thing to do is to create a tag which sends the ViewContent event to Facebook and add the video title as a parameter. Here is an example of such tag:

      Facebook Video ViewContent

      You could ignore the if statement from my example as I added it to filter out false positive events which sometimes GTM sends to Facebook. The actual call to Facebook is on line 3.

      As for the trigger, you could use the same trigger used for GA event tag.

      0

      0

Leave a Reply

Thanks for choosing to leave a comment. Please keep in mind that comments are moderated and your email address will NOT be published. Please Do NOT use keywords in the name field. Let's have a personal and meaningful conversation.