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.

19 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.

    5

    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

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.