Video content is a powerful tool for engaging website visitors, but understanding how users interact with your videos is critical for measuring ROI and improving your content strategy. In this comprehensive guide, we’ll show you how to implement robust Vimeo video tracking in Google Tag Manager (GTM) that sends data directly to Google Analytics (GA4).
Why standard Vimeo analytics isn’t enough
While Vimeo offers basic analytics, integrating video data with your GA4 property provides several advantages:
- Unified reporting: See video engagement alongside other website metrics
- User journey analysis: Understand how video interactions fit into the overall user experience
- Conversion attribution: Connect video engagement to important business outcomes
- Enhanced segmentation: Create audience segments based on video engagement
Here’s a quick overview of how our Vimeo tracking works
- A detection script that checks if Vimeo videos are present on the page
- A Vimeo event listener that monitors video interactions and sends data to the data layer
- GTM configuration (variables, triggers, and tags) that processes this data and sends it to GA4
Step 1: Create a Variable to Detect Vimeo Videos
First, we’ll create a Custom JavaScript variable that checks if any Vimeo videos are present on the page.

- In GTM, go to Variables → User-Defined Variables → New
- Select Custom JavaScript as the variable type
- Name it “js – vimeoVideosPresent”
- Add the following code:
function() {
// Check for existing iframes with vimeo sources
var iframes = document.getElementsByTagName('iframe');
for (var i = 0; i < iframes.length; i++) {
if (iframes[i].src && iframes[i].src.indexOf('vimeo.com') !== -1) {
return true;
}
}
// Also check for other elements that might indicate a Vimeo embed
var possibleContainers = document.querySelectorAll('[class*="vimeo"], [id*="vimeo"]');
if (possibleContainers.length > 0) {
return true;
}
return false;
}
- Save the variable
This variable will return true if Vimeo videos are found on the page, allowing us to only load our tracking script when necessary.
Step 2: Add the Vimeo Tracking Script
Next, we’ll create a Custom HTML tag that contains our tracking script:
- Go to Tags → New → Custom HTML
- Name it “Vimeo Video Tracking”
- Add the following code:
<script>
(function() {
if (window.isiVimeoTrackerLoaded) {
return;
}
window.isiVimeoTrackerLoaded = true;
window.vimeoGa4Config = window.vimeoGa4Config || {
progressMarkers: [10, 25, 50, 75, 90, 100],
progressThrottle: 2,
trackEngagement: true,
trackQuality: true,
debug: false
};
if (!window.Vimeo) {
var tag = document.createElement('script');
tag.src = "https://player.vimeo.com/api/player.js";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
tag.onload = initializeTracking;
} else {
initializeTracking();
}
var observer;
if (window.MutationObserver) {
observer = new MutationObserver(function(mutations) {
for (var i = 0; i < mutations.length; i++) {
var mutation = mutations[i];
if (mutation.addedNodes && mutation.addedNodes.length) {
for (var j = 0; j < mutation.addedNodes.length; j++) {
var node = mutation.addedNodes[j];
if (node.nodeName === 'IFRAME' && node.src && node.src.indexOf('vimeo.com') !== -1) {
setupPlayerTracking(node);
} else if (node.querySelectorAll) {
var vimeoIframes = node.querySelectorAll('iframe[src*="vimeo.com"]');
for (var k = 0; k < vimeoIframes.length; k++) {
setupPlayerTracking(vimeoIframes[k]);
}
}
}
}
}
});
}
function initializeTracking() {
var vimeoIframes = document.querySelectorAll('iframe[src*="vimeo.com"]');
for (var i = 0; i < vimeoIframes.length; i++) {
setupPlayerTracking(vimeoIframes[i]);
}
if (observer) {
observer.observe(document.body, {
childList: true,
subtree: true
});
}
console.log('Vimeo tracking initialized');
}
function setupPlayerTracking(iframe) {
if (iframe.getAttribute('data-vimeo-tracked')) return;
iframe.setAttribute('data-vimeo-tracked', 'true');
var videoId = extractVideoId(iframe.src);
var videoTitle = getVideoTitle(iframe);
var videoData = {
video_id: videoId,
video_title: videoTitle,
video_url: iframe.src,
video_provider: 'vimeo',
page_title: document.title,
page_url: window.location.href,
video_category: getVideoCategory(iframe, videoTitle),
video_duration: null
};
try {
var player = new Vimeo.Player(iframe);
var trackedProgress = {};
var lastProgressTime = 0;
var playbackStarted = false;
player.getVideoTitle().then(function(title) {
if (title && title !== videoData.video_title) {
videoData.video_title = title;
}
}).catch(function(err) {
console.warn('Could not get video title:', err);
});
player.getDuration().then(function(duration) {
videoData.video_duration = duration;
}).catch(function(err) {
console.warn('Could not get video duration:', err);
});
player.on('play', function(data) {
if (!playbackStarted) {
var startData = {
video_event: 'video_start',
video_current_time: data.seconds,
video_percent: 0,
video_id: videoData.video_id,
video_title: videoData.video_title,
video_url: videoData.video_url,
video_provider: videoData.video_provider,
video_category: videoData.video_category
};
pushToDatalayer(startData);
playbackStarted = true;
} else {
var playData = {
video_event: 'video_play',
video_current_time: data.seconds,
video_percent: Math.round(data.percent * 100),
video_id: videoData.video_id,
video_title: videoData.video_title,
video_url: videoData.video_url,
video_provider: videoData.video_provider,
video_category: videoData.video_category
};
pushToDatalayer(playData);
}
});
player.on('pause', function(data) {
if (data.percent >= 0.99) return;
var pauseData = {
video_event: 'video_pause',
video_current_time: data.seconds,
video_percent: Math.round(data.percent * 100),
video_id: videoData.video_id,
video_title: videoData.video_title,
video_url: videoData.video_url,
video_provider: videoData.video_provider,
video_category: videoData.video_category
};
pushToDatalayer(pauseData);
});
player.on('ended', function(data) {
var completeData = {
video_event: 'video_complete',
video_current_time: data.seconds,
video_percent: 100,
video_id: videoData.video_id,
video_title: videoData.video_title,
video_url: videoData.video_url,
video_provider: videoData.video_provider,
video_category: videoData.video_category
};
pushToDatalayer(completeData);
playbackStarted = false;
for (var key in trackedProgress) {
if (trackedProgress.hasOwnProperty(key)) {
trackedProgress[key] = false;
}
}
});
player.on('timeupdate', function(data) {
var now = Date.now();
var percent = Math.round(data.percent * 100);
if (now - lastProgressTime < (window.vimeoGa4Config.progressThrottle * 1000)) {
return;
}
for (var i = 0; i < window.vimeoGa4Config.progressMarkers.length; i++) {
var marker = window.vimeoGa4Config.progressMarkers[i];
if (marker === 100) continue;
if (percent >= marker && !trackedProgress[marker]) {
trackedProgress[marker] = true;
lastProgressTime = now;
var progressData = {
video_event: 'video_progress',
video_current_time: data.seconds,
video_percent: marker,
video_milestone: marker,
video_id: videoData.video_id,
video_title: videoData.video_title,
video_url: videoData.video_url,
video_provider: videoData.video_provider,
video_category: videoData.video_category
};
pushToDatalayer(progressData);
}
}
});
if (window.vimeoGa4Config.trackEngagement) {
player.on('seeked', function(data) {
var seekData = {
video_event: 'video_seek',
video_current_time: data.seconds,
video_percent: Math.round(data.percent * 100),
video_id: videoData.video_id,
video_title: videoData.video_title,
video_url: videoData.video_url,
video_provider: videoData.video_provider,
video_category: videoData.video_category
};
pushToDatalayer(seekData);
});
player.on('volumechange', function(data) {
if (data.volume === 0 && data.previousVolume > 0) {
var muteData = {
video_event: 'video_mute',
video_id: videoData.video_id,
video_title: videoData.video_title,
video_url: videoData.video_url,
video_provider: videoData.video_provider,
video_category: videoData.video_category
};
pushToDatalayer(muteData);
} else if (data.volume > 0 && data.previousVolume === 0) {
var unmuteData = {
video_event: 'video_unmute',
video_id: videoData.video_id,
video_title: videoData.video_title,
video_url: videoData.video_url,
video_provider: videoData.video_provider,
video_category: videoData.video_category
};
pushToDatalayer(unmuteData);
}
});
player.on('fullscreenchange', function(fullscreen) {
var fsData = {
video_event: fullscreen ? 'video_fullscreen_enter' : 'video_fullscreen_exit',
video_id: videoData.video_id,
video_title: videoData.video_title,
video_url: videoData.video_url,
video_provider: videoData.video_provider,
video_category: videoData.video_category
};
pushToDatalayer(fsData);
});
}
if (window.vimeoGa4Config.trackQuality) {
player.on('qualitychange', function(quality) {
var qualityData = {
video_event: 'video_quality_change',
video_quality: quality,
video_id: videoData.video_id,
video_title: videoData.video_title,
video_url: videoData.video_url,
video_provider: videoData.video_provider,
video_category: videoData.video_category
};
pushToDatalayer(qualityData);
});
}
console.log('Vimeo tracking initialized for:', videoTitle, '(ID:', videoId, ')');
} catch (error) {
console.error('Error initializing Vimeo tracking:', error);
}
}
function extractVideoId(src) {
var regex = /(?:vimeo\.com\/(?:video\/)?|player\.vimeo\.com\/video\/)(\d+)/;
var match = src.match(regex);
return match ? match[1] : 'unknown';
}
function getVideoTitle(iframe) {
return iframe.getAttribute('data-video-title') || iframe.getAttribute('title') || iframe.getAttribute('alt') || iframe.getAttribute('name') || 'Vimeo Video ' + extractVideoId(iframe.src);
}
function getVideoCategory(iframe, title) {
var categories = {
'Product': ['product', 'feature', 'demo', 'tutorial', 'how to'],
'Marketing': ['promo', 'ad', 'commercial', 'marketing'],
'Educational': ['learn', 'course', 'educational', 'training', 'webinar'],
'Entertainment': ['entertainment', 'creative', 'story', 'music']
};
var dataCategory = iframe.getAttribute('data-video-category');
if (dataCategory) return dataCategory;
var titleLower = title.toLowerCase();
var urlPath = iframe.src.split('/').pop().toLowerCase();
for (var category in categories) {
if (categories.hasOwnProperty(category)) {
var keywords = categories[category];
for (var i = 0; i < keywords.length; i++) {
var keyword = keywords[i].toLowerCase();
if (titleLower.indexOf(keyword) !== -1 || urlPath.indexOf(keyword) !== -1) {
return category;
}
}
}
}
return 'Other Video';
}
function pushToDatalayer(data) {
window.dataLayer = window.dataLayer || [];
var eventData = {
event: 'Video Engagement',
video: data
};
eventData.timestamp = new Date().toISOString();
window.dataLayer.push(eventData);
if (window.vimeoGa4Config.debug) {
console.log('📊 Vimeo Event:', data.video_event, data);
}
}
})();
</script>
- For Firing Triggers, create and select a new trigger named “Vimeo Videos Present” with the following settings:
- Trigger Type: Page View
- This trigger fires on: Some Page Views
- Fire this trigger when: {{js – vimeoVideosPresent}} equals true
- Save the tag
Step 3: Configure Data Layer Variables

Next, set up data layer variables to capture the video event data:
- Go to Variables → User-Defined Variables → New
- Select Data Layer Variable
- Create the following Data Layer Variables:
Name | Data Layer Variable Name | Variable Description |
---|---|---|
dlv video.video_event | video.video_event | The type of video event (start, pause, etc.) |
dlv video.video_title | video.video_title | The title of the video |
dlv video.video_id | video.video_id | The unique Vimeo video ID |
dlv video.video_url | video.video_url | URL of the video |
dlv video.video_provider | video.video_provider | Will be “vimeo” |
dlv video.video_percent | video.video_percent | Percentage of video watched |
dlv video.video_current_time | video.video_current_time | Current playback position in seconds |
dlv video.video_category | video.video_category | Category of the video content |
Step 4: Create Custom Event Triggers
Now, let’s create triggers for each video event type:
- Go to Triggers → New
- Create the following triggers:
video_start Trigger
- Name: “video_start”
- Trigger Type: Custom Event
- Event name: Video Engagement
- This trigger fires on: Some Custom Events
- Fire this trigger when: {{dlv video.video_event}} equals video_start

video_play Trigger
- Name: “video_play”
- Trigger Type: Custom Event
- Event name: Video Engagement
- This trigger fires on: Some Custom Events
- Fire this trigger when: {{dlv video.video_event}} equals video_play
video_pause Trigger
- Name: “video_pause”
- Trigger Type: Custom Event
- Event name: Video Engagement
- This trigger fires on: Some Custom Events
- Fire this trigger when: {{dlv video.video_event}} equals video_pause
video_progress Trigger
- Name: “video_progress”
- Trigger Type: Custom Event
- Event name: Video Engagement
- This trigger fires on: Some Custom Events
- Fire this trigger when: {{dlv video.video_event}} equals video_progress
video_complete Trigger
- Name: “video_complete”
- Trigger Type: Custom Event
- Event name: Video Engagement
- This trigger fires on: Some Custom Events
- Fire this trigger when: {{dlv video.video_event}} equals video_complete
Step 5: Create GA4 Event Tags
Now, create GA4 tags to send the video events to Google Analytics:
Option 1: Individual Event Tags (We recommend this for detailed analysis)
Create separate GA4 event tags for each event type:
Example: video_start event tag for GA4

- Tag Name: “GA4 video_start”
- Tag Type: Google Analytics: GA4 Event
- Configuration Tag: [Your GA4 Configuration Tag]
- Event Name: video_start
- Event Parameters:
- video_provider: {{dlv video.video_provider}}
- video_title: {{dlv video.video_title}}
- video_url: {{dlv video.video_url}}
- video_percent: {{dlv video.video_percent}}
- video_current_time: {{dlv video.video_current_time}}
- Triggering: video_start trigger
Create similar tags for video_play, video_pause, video_progress, and video_complete, changing the Event Name and Triggering accordingly.
Option 2: Combined Event Tag (Good for simple tracking)
Alternatively, you can create a generic video engagement tag that captures all events:
- Name: “GA4 video_engagement”
- Tag Type: Google Analytics: GA4 Event
- Configuration Tag: [Your GA4 Configuration Tag]
- Event Name: video_engagement
- Event Parameters:
- video_provider: {{dlv video.video_provider}}
- video_title: {{dlv video.video_title}}
- video_url: {{dlv video.video_url}}
- video_percent: {{dlv video.video_percent}}
- video_current_time: {{dlv video.video_current_time}}
- video_event: {{dlv video.video_event}}
- Triggering: “Video Engagement” custom event trigger
- Exceptions: Add the “video_progress” trigger as an exception if you want to track progress separately
Step 6: Test Your Implementation
- Enable Preview Mode in GTM
- Navigate to a page on your website with Vimeo videos
- Interact with the videos (play, pause, watch to different progress points)
- Verify that the events are being captured in the GTM Preview pane
- Check that the data is being sent to GA4 properly
Step 7: Analyze Video Performance in GA4
Once your tracking is in place and collecting data, you can analyze video performance in GA4. The best way to see the data is by creating custom explorations:
- Go to Explore → Blank to create a new exploration
- Add dimensions like “video_title”, “video_category”, and “page_title”
- Add metrics like “Event count” and “Total users”
- Apply filters to focus on specific video events
Customization Options
Adding Custom Dimensions
If you want to categorize your videos more precisely, you can modify the getVideoCategory function in the tracking script to match your organization’s content taxonomy. For example:
function getVideoCategory(iframe, title) {
  // Customize these categories based on your content strategy
  var categories = {
    'Product Demos': ['demo', 'product tour', 'walkthrough'],
    'Tutorials': ['tutorial', 'how to', 'guide', 'learn'],
    'Customer Stories': ['testimonial', 'case study', 'customer'],
    'Company News': ['announcement', 'update', 'news']
  };
  // Rest of the function remains the same...
}
Adding Data Attributes to Your Embeds
For more precise categorization, add data attributes to your Vimeo embeds:
<iframe src="https://player.vimeo.com/video/123456789"Â
  data-video-title="Custom Video Title" data-video-category="Product Demos"
 width="640" height="360" frameborder="0" allowfullscreen>
</iframe>
Modifying Progress Markers
If you want to track different progress points, you can modify the progressMarkers in the configuration:
window.vimeoGa4Config = {
  progressMarkers: [15, 30, 45, 60, 75, 90],
  // Other settings...
};
If you made it this far, congratulations!
With this implementation, you now have comprehensive tracking of Vimeo video engagement that integrates directly with your GA4 property. You can use this data to:
- Understand which videos are most engaging
- Identify drop-off points within videos
- Correlate video engagement with conversion metrics
- Build remarketing audiences based on video viewing behavior
Video is the hardest media type to create, so understanding how engaging it is, what videos users watch, which they skip over, and other data is invaluable.