Webflow-Specific GA4 Event Tracking
This guide covers tracking Webflow-specific interactions with Google Analytics 4 (GA4), including forms, CMS content, interactions, and custom events unique to Webflow sites.
Prerequisites
- GA4 installed on your Webflow site via custom code method
- Basic JavaScript knowledge for implementing custom event tracking
- Published Webflow site (events only fire on published sites)
Webflow Forms
Webflow's native form component generates a unique form submission event that you can track.
Basic Form Submission Tracking
Add this code to Project Settings > Custom Code > Footer Code:
<script>
// Track Webflow form submissions
document.addEventListener('DOMContentLoaded', function() {
// Find all Webflow forms
const forms = document.querySelectorAll('form[data-name]');
forms.forEach(function(form) {
form.addEventListener('submit', function(e) {
const formName = form.getAttribute('data-name');
// Send event to GA4
gtag('event', 'form_submit', {
'form_name': formName,
'form_location': window.location.pathname
});
});
});
});
</script>
Track Form Submission Success
Webflow shows a success message after form submission. Track successful submissions:
<script>
// Track successful form submissions
document.addEventListener('DOMContentLoaded', function() {
// Create a MutationObserver to watch for success message
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
// Check if success message appeared
if (node.classList && node.classList.contains('w-form-done')) {
const form = node.previousElementSibling;
const formName = form ? form.getAttribute('data-name') : 'unknown';
gtag('event', 'form_success', {
'form_name': formName,
'form_location': window.location.pathname
});
}
});
});
});
// Observe all form containers
const formBlocks = document.querySelectorAll('.w-form');
formBlocks.forEach(function(block) {
observer.observe(block, { childList: true });
});
});
</script>
Track Form Errors
Track when form validation errors occur:
<script>
document.addEventListener('DOMContentLoaded', function() {
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
// Check if error message appeared
if (node.classList && node.classList.contains('w-form-fail')) {
const form = node.previousElementSibling?.previousElementSibling;
const formName = form ? form.getAttribute('data-name') : 'unknown';
gtag('event', 'form_error', {
'form_name': formName,
'error_message': node.textContent.trim(),
'form_location': window.location.pathname
});
}
});
});
});
const formBlocks = document.querySelectorAll('.w-form');
formBlocks.forEach(function(block) {
observer.observe(block, { childList: true });
});
});
</script>
Capture Form Field Data
Track specific form field values (be careful with PII):
<script>
document.addEventListener('DOMContentLoaded', function() {
const forms = document.querySelectorAll('form[data-name]');
forms.forEach(function(form) {
form.addEventListener('submit', function(e) {
const formName = form.getAttribute('data-name');
// Example: Track which product someone inquired about
const productField = form.querySelector('[name="Product"]');
const messageField = form.querySelector('[name="Message"]');
gtag('event', 'contact_form_submit', {
'form_name': formName,
'product_interest': productField ? productField.value : 'not_specified',
'has_message': messageField ? (messageField.value.length > 0) : false
});
});
});
});
</script>
Privacy warning: Do not track email addresses, phone numbers, names, or other PII without proper consent and data protection measures.
Webflow CMS Tracking
Track interactions with Webflow CMS Collection items.
Track CMS Collection Item Views
Add custom code to your CMS Collection Page template:
- Go to your CMS Collection Page in the Designer
- Add an Embed element at the top of the page
- Add this code:
<script>
// Get CMS item data from the page
const itemName = document.querySelector('h1')?.textContent || 'Unknown';
const itemSlug = window.location.pathname.split('/').pop();
// Send CMS item view event
gtag('event', 'view_item', {
'item_name': itemName,
'item_category': 'blog_post', // Adjust based on your collection type
'item_slug': itemSlug,
'content_type': 'cms_collection_item'
});
</script>
Track CMS Item Clicks from Collection List
Track when users click on CMS items in a Collection List:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Find all collection list items
const collectionItems = document.querySelectorAll('.w-dyn-item a');
collectionItems.forEach(function(link) {
link.addEventListener('click', function() {
const itemName = this.textContent.trim();
const itemUrl = this.getAttribute('href');
gtag('event', 'select_content', {
'content_type': 'cms_item',
'item_name': itemName,
'item_url': itemUrl
});
});
});
});
</script>
Pass CMS Field Data to GA4
Use Webflow's Embed element to pass CMS field data to analytics. In your CMS Collection Page template:
- Add an Embed element
- Click Insert field to insert CMS field values
- Add tracking code:
<script>
// CMS field data (inserted via Webflow CMS)
const cmsData = {
title: "INSERT_CMS_FIELD_NAME", // Use Webflow's insert field feature
category: "INSERT_CATEGORY_FIELD",
author: "INSERT_AUTHOR_FIELD",
publishDate: "INSERT_DATE_FIELD"
};
// Send enriched page view
gtag('event', 'page_view', {
'page_title': cmsData.title,
'content_category': cmsData.category,
'content_author': cmsData.author,
'publish_date': cmsData.publishDate,
'content_type': 'blog_post'
});
</script>
In Webflow: Click the purple "Insert field" button in the Embed editor to dynamically insert CMS values.
Webflow Interactions Tracking
Track Webflow's built-in interactions and animations.
Track Tab Clicks
<script>
document.addEventListener('DOMContentLoaded', function() {
const tabs = document.querySelectorAll('.w-tab-link');
tabs.forEach(function(tab) {
tab.addEventListener('click', function() {
const tabName = this.textContent.trim();
const tabsContainer = this.closest('[data-name]');
const containerName = tabsContainer ? tabsContainer.getAttribute('data-name') : 'unknown';
gtag('event', 'tab_click', {
'tab_name': tabName,
'tabs_container': containerName,
'page_location': window.location.pathname
});
});
});
});
</script>
Track Accordion/Dropdown Clicks
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track dropdown toggles
const dropdowns = document.querySelectorAll('.w-dropdown-toggle');
dropdowns.forEach(function(dropdown) {
dropdown.addEventListener('click', function() {
const dropdownName = this.textContent.trim();
gtag('event', 'dropdown_toggle', {
'dropdown_name': dropdownName,
'page_location': window.location.pathname
});
});
});
});
</script>
Track Lightbox Opens
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track lightbox triggers
const lightboxLinks = document.querySelectorAll('[data-wf-lightbox]');
lightboxLinks.forEach(function(link) {
link.addEventListener('click', function() {
const lightboxType = this.getAttribute('data-wf-lightbox');
const mediaUrl = this.getAttribute('href');
gtag('event', 'lightbox_open', {
'lightbox_type': lightboxType,
'media_url': mediaUrl,
'page_location': window.location.pathname
});
});
});
});
</script>
Track Slider/Carousel Interactions
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track slider arrow clicks
const sliderArrows = document.querySelectorAll('.w-slider-arrow-left, .w-slider-arrow-right');
sliderArrows.forEach(function(arrow) {
arrow.addEventListener('click', function() {
const direction = this.classList.contains('w-slider-arrow-left') ? 'previous' : 'next';
const slider = this.closest('.w-slider');
const sliderName = slider ? slider.getAttribute('data-name') : 'unknown';
gtag('event', 'slider_interaction', {
'slider_name': sliderName,
'direction': direction,
'page_location': window.location.pathname
});
});
});
});
</script>
Custom Button and Link Tracking
Track Specific Button Clicks
Add a custom attribute to buttons you want to track:
- Select the button in Webflow Designer
- Go to Settings (gear icon) > Custom Attributes
- Add attribute:
data-track-event="button_name_here"
Then add this tracking code:
<script>
document.addEventListener('DOMContentLoaded', function() {
const trackableButtons = document.querySelectorAll('[data-track-event]');
trackableButtons.forEach(function(button) {
button.addEventListener('click', function() {
const eventName = this.getAttribute('data-track-event');
const buttonText = this.textContent.trim();
gtag('event', 'button_click', {
'button_name': eventName,
'button_text': buttonText,
'page_location': window.location.pathname
});
});
});
});
</script>
Track Outbound Links
Automatically track external link clicks:
<script>
document.addEventListener('DOMContentLoaded', function() {
const links = document.querySelectorAll('a');
links.forEach(function(link) {
link.addEventListener('click', function() {
const href = this.getAttribute('href');
// Check if link is external
if (href && (href.startsWith('http') || href.startsWith('https')) && !href.includes(window.location.hostname)) {
gtag('event', 'click', {
'event_category': 'outbound',
'event_label': href,
'transport_type': 'beacon'
});
}
});
});
});
</script>
Track File Downloads
Track PDF and file downloads:
<script>
document.addEventListener('DOMContentLoaded', function() {
const fileLinks = document.querySelectorAll('a[href$=".pdf"], a[href$=".zip"], a[href$=".doc"], a[href$=".docx"], a[href$=".xls"], a[href$=".xlsx"]');
fileLinks.forEach(function(link) {
link.addEventListener('click', function() {
const fileUrl = this.getAttribute('href');
const fileName = fileUrl.split('/').pop();
const fileExtension = fileName.split('.').pop();
gtag('event', 'file_download', {
'file_name': fileName,
'file_extension': fileExtension,
'file_url': fileUrl,
'page_location': window.location.pathname
});
});
});
});
</script>
Scroll Depth Tracking
Track how far users scroll on your pages:
<script>
(function() {
let scrollMarks = [25, 50, 75, 90, 100];
let trackedMarks = [];
function checkScrollDepth() {
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const scrollPercent = Math.round((scrollTop / (documentHeight - windowHeight)) * 100);
scrollMarks.forEach(function(mark) {
if (scrollPercent >= mark && !trackedMarks.includes(mark)) {
trackedMarks.push(mark);
gtag('event', 'scroll', {
'percent_scrolled': mark,
'page_location': window.location.pathname
});
}
});
}
// Throttle scroll events
let scrollTimeout;
window.addEventListener('scroll', function() {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(checkScrollDepth, 100);
});
})();
</script>
Video Tracking
Track Embedded YouTube Videos
Webflow's video embed component uses YouTube's API. Track video interactions:
<script>
// Load YouTube IFrame API
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// Track video events
function onYouTubeIframeAPIReady() {
var videos = document.querySelectorAll('iframe[src*="youtube.com"]');
videos.forEach(function(video) {
var player = new YT.Player(video, {
events: {
'onStateChange': function(event) {
var videoUrl = event.target.getVideoUrl();
var videoTitle = event.target.getVideoData().title;
if (event.data == YT.PlayerState.PLAYING) {
gtag('event', 'video_start', {
'video_title': videoTitle,
'video_url': videoUrl
});
} else if (event.data == YT.PlayerState.ENDED) {
gtag('event', 'video_complete', {
'video_title': videoTitle,
'video_url': videoUrl
});
}
}
}
});
});
}
</script>
Track Native HTML5 Video
For HTML5 video elements:
<script>
document.addEventListener('DOMContentLoaded', function() {
const videos = document.querySelectorAll('video');
videos.forEach(function(video) {
const videoName = video.getAttribute('data-name') || video.src;
video.addEventListener('play', function() {
gtag('event', 'video_start', {
'video_name': videoName,
'video_provider': 'html5'
});
});
video.addEventListener('ended', function() {
gtag('event', 'video_complete', {
'video_name': videoName,
'video_provider': 'html5'
});
});
video.addEventListener('pause', function() {
if (video.currentTime < video.duration) {
gtag('event', 'video_pause', {
'video_name': videoName,
'video_provider': 'html5',
'video_percent': Math.round((video.currentTime / video.duration) * 100)
});
}
});
});
});
</script>
Search Tracking
If you've built a custom search on your Webflow site:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Assuming search form has data-name="Search"
const searchForm = document.querySelector('form[data-name="Search"]');
if (searchForm) {
searchForm.addEventListener('submit', function(e) {
const searchInput = this.querySelector('input[type="search"], input[name="query"]');
const searchTerm = searchInput ? searchInput.value : '';
gtag('event', 'search', {
'search_term': searchTerm,
'page_location': window.location.pathname
});
});
}
});
</script>
Webflow Ecommerce Events
For Webflow Ecommerce tracking, see the dedicated guide: Webflow Ecommerce + GA4 Tracking
Custom Dimensions and Parameters
Setting Up Custom Dimensions in GA4
- Go to Admin > Custom Definitions in GA4
- Click Create custom dimension
- Add dimensions like:
page_template- Webflow page template namecms_collection- CMS collection namemembership_status- Memberstack statususer_role- User role/type
Sending Custom Dimensions
<script>
// Set custom dimensions for all events
gtag('set', {
'page_template': 'blog-post',
'cms_collection': 'articles',
'site_section': 'blog'
});
// Or send with specific events
gtag('event', 'page_view', {
'page_template': 'blog-post',
'cms_collection': 'articles'
});
</script>
Event Naming Best Practices
Follow GA4 Recommended Events
Use GA4's recommended event names when possible:
login- User loginsign_up- User registrationsearch- Site searchselect_content- Content selectionshare- Social sharingview_item- Item/product viewgenerate_lead- Lead form submission
Custom Event Naming Conventions
For custom Webflow events:
- Use lowercase with underscores:
form_submitnotformSubmitorForm Submit - Be descriptive:
contact_form_submitnotform1 - Use consistent prefixes:
webflow_form_,cms_,interaction_ - Keep names under 40 characters
Testing Events
Use GA4 DebugView
- Add debug mode to your GA4 config:
<script>
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
</script>
- Go to Admin > DebugView in GA4
- Trigger events on your site
- See events appear in real-time with full parameters
Browser Console Testing
Test events directly in the browser console:
// Test a form submission event
gtag('event', 'form_submit', {
'form_name': 'Contact',
'form_location': '/contact'
});
// Check if gtag is defined
console.log(typeof gtag); // Should output "function"
// Check dataLayer
console.log(window.dataLayer); // Should show array of events
Google Tag Assistant
- Install Tag Assistant
- Visit your site
- Trigger events
- Verify events in Tag Assistant
Common Issues
Events Not Firing
Problem: Custom events don't appear in GA4.
Solutions:
- Verify GA4 is loaded: Check
typeof gtagin console - Check event syntax: Ensure proper
gtag('event', 'name', {})format - Wait for page load: Wrap code in
DOMContentLoadedlistener - Test on published site: Events don't fire in Webflow Designer
- Enable debug mode: Use DebugView to troubleshoot
Events Fire Multiple Times
Problem: Events trigger multiple times per action.
Solutions:
- Remove duplicate listeners: Check for multiple script instances
- Use
once: trueoption:
element.addEventListener('click', handler, { once: true });
- Add event flags:
let formSubmitted = false;
form.addEventListener('submit', function() {
if (!formSubmitted) {
formSubmitted = true;
gtag('event', 'form_submit', {...});
}
});
CMS Field Data Not Passing
Problem: CMS field values don't appear in events.
Solutions:
- Use Embed element: CMS fields only work in Embed elements
- Click "Insert field": Use the purple button in Embed editor
- Check field name: Ensure field exists in CMS collection
- Verify on published site: CMS data only available after publishing
Next Steps
- Set Up Ecommerce Tracking for Webflow Ecommerce
- Install Google Tag Manager for easier event management
- Troubleshoot Events Not Firing
- Configure Data Layer for GTM implementation