Webflow Data Layer for Google Tag Manager
A well-structured data layer is essential for robust tracking with Google Tag Manager (GTM). This guide shows you how to implement a comprehensive data layer for Webflow sites, covering ecommerce events, CMS content, forms, and custom attributes.
Prerequisites
- Google Tag Manager installed on your Webflow site - GTM Setup Guide
- Basic JavaScript knowledge for implementing data layer pushes
- Published Webflow site (data layer only works on published sites)
- Webflow Ecommerce plan (for ecommerce tracking)
Data Layer Fundamentals
What is a Data Layer?
A data layer is a JavaScript object that stores information about your website and user interactions. GTM reads this data to fire tags with relevant context.
Webflow's Default Data Layer
When you install GTM, a basic data layer is created automatically:
window.dataLayer = window.dataLayer || [];
Data Layer Structure
Data is pushed to the data layer as objects:
dataLayer.push({
'event': 'event_name',
'parameter1': 'value1',
'parameter2': 'value2'
});
Base Data Layer Implementation
Add this foundational data layer to Project Settings > Custom Code > Head Code (BEFORE the GTM container code):
<script>
window.dataLayer = window.dataLayer || [];
// Push page load data
dataLayer.push({
'event': 'page_load',
'page_type': getPageType(),
'page_category': getPageCategory(),
'page_template': getPageTemplate(),
'user_type': getUserType()
});
// Helper: Determine page type
function getPageType() {
if (document.querySelector('.w-commerce-commerceproducttemplate')) return 'product';
if (document.querySelector('.w-commerce-commercecartcontainer')) return 'cart';
if (document.querySelector('.w-commerce-commercecheckoutform')) return 'checkout';
if (document.querySelector('.w-commerce-commerceorderconfirmationcontainer')) return 'order_confirmation';
if (document.querySelector('.w-dyn-item')) return 'cms_item';
return 'standard';
}
// Helper: Get page category from URL
function getPageCategory() {
var path = window.location.pathname;
if (path.includes('/products')) return 'products';
if (path.includes('/blog')) return 'blog';
if (path.includes('/about')) return 'about';
if (path.includes('/contact')) return 'contact';
return 'other';
}
// Helper: Get page template type
function getPageTemplate() {
if (document.querySelector('[data-wf-page]')) {
return document.querySelector('[data-wf-page]').getAttribute('data-wf-page') || 'unknown';
}
return 'unknown';
}
// Helper: Determine user type
function getUserType() {
// Check if Memberstack is installed
if (window.$memberstackDom) {
return 'member'; // Will be updated after Memberstack loads
}
return 'visitor';
}
</script>
Webflow Forms Data Layer
Track Webflow form submissions with detailed data.
Form Submission Tracking
Add this code to Project Settings > Custom Code > Footer Code:
<script>
document.addEventListener('DOMContentLoaded', function() {
var forms = document.querySelectorAll('form[data-name]');
forms.forEach(function(form) {
form.addEventListener('submit', function(e) {
var formName = this.getAttribute('data-name');
var formId = this.getAttribute('id') || 'no-id';
// Get form field data (excluding sensitive fields)
var formData = {};
var formElements = this.elements;
for (var i = 0; i < formElements.length; i++) {
var field = formElements[i];
var fieldName = field.name;
// Skip sensitive fields
if (fieldName && !isSensitiveField(fieldName)) {
formData[fieldName] = field.value;
}
}
// Push to data layer
dataLayer.push({
'event': 'form_submit',
'form_name': formName,
'form_id': formId,
'form_location': window.location.pathname,
'form_data': formData
});
});
});
// Helper: Identify sensitive fields
function isSensitiveField(fieldName) {
var sensitiveFields = ['email', 'phone', 'password', 'ssn', 'credit-card', 'cvv'];
fieldName = fieldName.toLowerCase();
return sensitiveFields.some(function(sensitive) {
return fieldName.includes(sensitive);
});
}
});
</script>
Form Success Tracking
Track successful form submissions (when success message appears):
<script>
document.addEventListener('DOMContentLoaded', function() {
// Watch for success message
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.classList && node.classList.contains('w-form-done')) {
var form = node.previousElementSibling;
var formName = form ? form.getAttribute('data-name') : 'unknown';
dataLayer.push({
'event': 'form_success',
'form_name': formName,
'form_location': window.location.pathname
});
}
});
});
});
// Observe all form blocks
var formBlocks = document.querySelectorAll('.w-form');
formBlocks.forEach(function(block) {
observer.observe(block, { childList: true });
});
});
</script>
Form Error Tracking
Track form validation errors:
<script>
document.addEventListener('DOMContentLoaded', function() {
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.classList && node.classList.contains('w-form-fail')) {
var form = node.previousElementSibling?.previousElementSibling;
var formName = form ? form.getAttribute('data-name') : 'unknown';
var errorMessage = node.textContent.trim();
dataLayer.push({
'event': 'form_error',
'form_name': formName,
'error_message': errorMessage,
'form_location': window.location.pathname
});
}
});
});
});
var formBlocks = document.querySelectorAll('.w-form');
formBlocks.forEach(function(block) {
observer.observe(block, { childList: true });
});
});
</script>
Webflow Ecommerce Data Layer
Implement comprehensive ecommerce tracking with a data layer.
Product Page Data Layer
Add this to your Product Template Page as an Embed element:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get product data from page
var productName = document.querySelector('.product-name')?.textContent.trim() || 'Unknown';
var productPrice = document.querySelector('.product-price')?.textContent.replace(/[^0-9.]/g, '') || '0';
var productSku = document.querySelector('.product-sku')?.textContent.trim() || '';
var productCategory = document.querySelector('.product-category')?.textContent.trim() || 'Uncategorized';
var productId = window.location.pathname.split('/').pop();
// Push product data to data layer
dataLayer.push({
'event': 'view_item',
'ecommerce': {
'currency': 'USD',
'value': parseFloat(productPrice),
'items': [{
'item_id': productSku || productId,
'item_name': productName,
'item_category': productCategory,
'price': parseFloat(productPrice),
'quantity': 1
}]
}
});
});
</script>
Enhanced Product Data with CMS Fields
For better data quality, use Webflow CMS fields:
<script>
// Use Webflow's "Insert field" button to add CMS values
var productData = {
id: 'INSERT_SKU_FIELD',
name: 'INSERT_NAME_FIELD',
price: 'INSERT_PRICE_FIELD',
category: 'INSERT_CATEGORY_FIELD',
brand: 'INSERT_BRAND_FIELD',
variant: 'INSERT_VARIANT_FIELD'
};
dataLayer.push({
'event': 'view_item',
'ecommerce': {
'currency': 'USD',
'value': parseFloat(productData.price),
'items': [{
'item_id': productData.id,
'item_name': productData.name,
'item_category': productData.category,
'item_brand': productData.brand,
'item_variant': productData.variant,
'price': parseFloat(productData.price),
'quantity': 1
}]
}
});
</script>
In Webflow: Click the purple "Insert field" button in the Embed element to insert actual CMS field values.
Add to Cart Data Layer
Add this to Project Settings > Custom Code > Footer Code:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track add to cart button clicks
document.addEventListener('click', function(e) {
var addToCartBtn = e.target.closest('.w-commerce-commerceaddtocartbutton');
if (addToCartBtn && !addToCartBtn.disabled) {
// Get product data from the page
var productName = document.querySelector('.product-name')?.textContent.trim() || 'Unknown';
var productPrice = document.querySelector('.product-price')?.textContent.replace(/[^0-9.]/g, '') || '0';
var productId = window.location.pathname.split('/').pop();
var productSku = document.querySelector('.product-sku')?.textContent.trim() || productId;
// Get quantity if available
var qtyInput = document.querySelector('.w-commerce-commerceaddtocartquantityinput');
var quantity = qtyInput ? parseInt(qtyInput.value) : 1;
// Push to data layer
dataLayer.push({
'event': 'add_to_cart',
'ecommerce': {
'currency': 'USD',
'value': parseFloat(productPrice) * quantity,
'items': [{
'item_id': productSku,
'item_name': productName,
'price': parseFloat(productPrice),
'quantity': quantity
}]
}
});
}
});
});
</script>
Cart Page Data Layer
Add this to the Cart Page (/checkout) or Project Settings > Footer Code:
<script>
document.addEventListener('DOMContentLoaded', function() {
if (window.location.pathname === '/checkout' && !window.location.search) {
// Wait for Webflow commerce to load
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
var cart = window.Webflow?.commerce?.cart;
if (cart && cart.items && cart.items.length > 0) {
var items = cart.items.map(function(item) {
return {
'item_id': item.sku || item.productId,
'item_name': item.name,
'price': parseFloat(item.price),
'quantity': item.count
};
});
dataLayer.push({
'event': 'view_cart',
'ecommerce': {
'currency': cart.currency || 'USD',
'value': parseFloat(cart.subtotal),
'items': items
}
});
}
});
}
});
</script>
Checkout Data Layer
Track checkout initiation:
<script>
document.addEventListener('DOMContentLoaded', function() {
var checkoutForm = document.querySelector('.w-commerce-commercecheckoutform');
if (checkoutForm) {
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
var cart = window.Webflow?.commerce?.cart;
if (cart && cart.items && cart.items.length > 0) {
var items = cart.items.map(function(item) {
return {
'item_id': item.sku || item.productId,
'item_name': item.name,
'price': parseFloat(item.price),
'quantity': item.count
};
});
dataLayer.push({
'event': 'begin_checkout',
'ecommerce': {
'currency': cart.currency || 'USD',
'value': parseFloat(cart.subtotal),
'items': items
}
});
}
});
}
});
</script>
Purchase Data Layer
Track completed purchases on the order confirmation page:
<script>
document.addEventListener('DOMContentLoaded', function() {
if (window.location.pathname.includes('/order-confirmation')) {
// Prevent duplicate tracking
var trackingKey = 'gtm_tracked_orders';
var trackedOrders = JSON.parse(localStorage.getItem(trackingKey) || '[]');
window.Webflow = window.Webflow || [];
window.Webflow.push(function() {
var order = window.Webflow?.commerce?.order;
if (order && !trackedOrders.includes(order.orderId)) {
var items = order.userItems.map(function(item) {
return {
'item_id': item.sku || item.productId,
'item_name': item.name,
'price': parseFloat(item.price),
'quantity': item.count
};
});
dataLayer.push({
'event': 'purchase',
'ecommerce': {
'transaction_id': order.orderId,
'value': parseFloat(order.total),
'tax': parseFloat(order.tax || 0),
'shipping': parseFloat(order.shipping || 0),
'currency': order.currency || 'USD',
'coupon': order.coupon || '',
'items': items
}
});
// Mark as tracked
trackedOrders.push(order.orderId);
localStorage.setItem(trackingKey, JSON.stringify(trackedOrders.slice(-10)));
}
});
}
});
</script>
CMS Data Layer
Track CMS Collection item data.
CMS Collection Item View
Add this to your CMS Collection Page Template as an Embed element:
<script>
// Get CMS data from page elements
var itemName = document.querySelector('h1')?.textContent.trim() || 'Unknown';
var itemSlug = window.location.pathname.split('/').pop();
var itemCategory = document.querySelector('.category')?.textContent.trim() || 'Uncategorized';
dataLayer.push({
'event': 'view_cms_item',
'cms_item_name': itemName,
'cms_item_slug': itemSlug,
'cms_item_category': itemCategory,
'cms_collection': 'blog', // Update based on your collection
'page_type': 'cms_item'
});
</script>
Enhanced CMS Data with Fields
Use Webflow's CMS field insertion for accurate data:
<script>
// Use "Insert field" button to add CMS values
var cmsData = {
title: 'INSERT_TITLE_FIELD',
category: 'INSERT_CATEGORY_FIELD',
author: 'INSERT_AUTHOR_FIELD',
publishDate: 'INSERT_DATE_FIELD',
tags: 'INSERT_TAGS_FIELD'
};
dataLayer.push({
'event': 'view_cms_item',
'cms_item_title': cmsData.title,
'cms_item_category': cmsData.category,
'cms_item_author': cmsData.author,
'cms_item_date': cmsData.publishDate,
'cms_item_tags': cmsData.tags,
'cms_collection': 'blog_posts',
'content_type': 'blog'
});
</script>
CMS List Item Clicks
Track clicks on CMS items in Collection Lists:
<script>
document.addEventListener('DOMContentLoaded', function() {
var collectionItems = document.querySelectorAll('.w-dyn-item a');
collectionItems.forEach(function(link, index) {
link.addEventListener('click', function() {
var itemName = this.textContent.trim();
var itemUrl = this.getAttribute('href');
dataLayer.push({
'event': 'cms_item_click',
'cms_item_name': itemName,
'cms_item_url': itemUrl,
'cms_item_position': index + 1,
'page_location': window.location.pathname
});
});
});
});
</script>
Custom Attributes Data Layer
Use Webflow's custom attributes to enrich tracking.
Adding Custom Attributes in Webflow
- Select an element in Webflow Designer
- Go to Settings (gear icon) > Custom Attributes
- Add attributes like:
data-track-event="button_name"data-track-category="cta"data-track-value="subscribe"
Tracking Custom Attributes
<script>
document.addEventListener('DOMContentLoaded', function() {
var trackableElements = document.querySelectorAll('[data-track-event]');
trackableElements.forEach(function(element) {
element.addEventListener('click', function() {
var eventName = this.getAttribute('data-track-event');
var eventCategory = this.getAttribute('data-track-category') || 'interaction';
var eventValue = this.getAttribute('data-track-value') || '';
var elementText = this.textContent.trim();
dataLayer.push({
'event': 'custom_interaction',
'interaction_type': eventName,
'interaction_category': eventCategory,
'interaction_value': eventValue,
'element_text': elementText,
'page_location': window.location.pathname
});
});
});
});
</script>
User Authentication Data Layer
Track user authentication with Memberstack.
Memberstack Integration
<script>
// Wait for Memberstack to load
if (window.$memberstackDom) {
window.$memberstackDom.ready.then(function(member) {
if (member.loggedIn) {
dataLayer.push({
'event': 'user_authenticated',
'user_id': member.id,
'user_type': 'member',
'membership_plan': member.planConnections[0]?.planId || 'none',
'member_status': 'logged_in'
});
} else {
dataLayer.push({
'event': 'user_status',
'user_type': 'visitor',
'member_status': 'logged_out'
});
}
});
// Track login
window.$memberstackDom.on('memberLogin', function(member) {
dataLayer.push({
'event': 'login',
'method': 'memberstack',
'user_id': member.id
});
});
// Track signup
window.$memberstackDom.on('memberSignup', function(member) {
dataLayer.push({
'event': 'sign_up',
'method': 'memberstack',
'user_id': member.id
});
});
// Track logout
window.$memberstackDom.on('memberLogout', function() {
dataLayer.push({
'event': 'logout',
'method': 'memberstack'
});
});
}
</script>
Creating GTM Variables from Data Layer
Access data layer values in GTM by creating Data Layer Variables.
Step 1: Create Data Layer Variables
- In GTM, go to Variables > New
- Click Variable Configuration
- Choose Data Layer Variable
- Enter the Data Layer Variable Name (e.g.,
form_name,ecommerce.value) - Set Data Layer Version to "Version 2"
- Name the variable (e.g., "DL - Form Name")
- Click Save
Example Variables to Create
| GTM Variable Name | Data Layer Variable Name | Description |
|---|---|---|
| DL - Form Name | form_name | Name of submitted form |
| DL - Page Type | page_type | Type of page (product, cms, etc.) |
| DL - CMS Item Title | cms_item_title | CMS item title |
| DL - Transaction ID | ecommerce.transaction_id | Order ID |
| DL - Transaction Value | ecommerce.value | Order total |
| DL - User ID | user_id | Memberstack user ID |
Step 2: Use Variables in Tags
Use these variables in your GTM tags:
Example: GA4 Event Tag
- Go to Tags > New
- Tag Configuration: Google Analytics: GA4 Event
- Event Name:
form_submission - Add Event Parameter:
- Parameter Name:
form_name - Value:
\{\{DL - Form Name\}\}
- Parameter Name:
- Trigger: Custom Event =
form_success - Save and publish
Complete Data Layer Implementation
Here's a production-ready, comprehensive data layer for Webflow:
Add to Project Settings > Custom Code > Head Code (BEFORE GTM)
<script>
(function() {
'use strict';
// Initialize data layer
window.dataLayer = window.dataLayer || [];
// Configuration
var config = {
currency: 'USD',
debug: false
};
function log(message, data) {
if (config.debug) {
console.log('[Webflow DataLayer]', message, data);
}
}
// Helper functions
function getPageType() {
if (document.querySelector('.w-commerce-commerceproducttemplate')) return 'product';
if (document.querySelector('.w-commerce-commercecartcontainer')) return 'cart';
if (document.querySelector('.w-commerce-commercecheckoutform')) return 'checkout';
if (document.querySelector('.w-commerce-commerceorderconfirmationcontainer')) return 'order_confirmation';
if (document.querySelector('.w-dyn-item')) return 'cms_item';
return 'standard';
}
function getPageCategory() {
var path = window.location.pathname;
var segments = path.split('/').filter(Boolean);
return segments[0] || 'home';
}
// Push initial page data
dataLayer.push({
'event': 'page_load',
'page_type': getPageType(),
'page_category': getPageCategory(),
'page_path': window.location.pathname,
'page_title': document.title,
'user_type': 'visitor' // Will be updated if Memberstack loads
});
log('Data layer initialized', {
page_type: getPageType(),
page_category: getPageCategory()
});
})();
</script>
Add to Project Settings > Custom Code > Footer Code
<script>
(function() {
'use strict';
var config = {
currency: 'USD',
debug: false
};
function log(message, data) {
if (config.debug) {
console.log('[Webflow DataLayer]', message, data);
}
}
// Form tracking
function initFormTracking() {
var forms = document.querySelectorAll('form[data-name]');
forms.forEach(function(form) {
form.addEventListener('submit', function() {
dataLayer.push({
'event': 'form_submit',
'form_name': this.getAttribute('data-name'),
'form_location': window.location.pathname
});
log('Form submitted', this.getAttribute('data-name'));
});
});
// Watch for success/error messages
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.classList) {
if (node.classList.contains('w-form-done')) {
var form = node.previousElementSibling;
dataLayer.push({
'event': 'form_success',
'form_name': form ? form.getAttribute('data-name') : 'unknown'
});
log('Form success');
} else if (node.classList.contains('w-form-fail')) {
var form = node.previousElementSibling?.previousElementSibling;
dataLayer.push({
'event': 'form_error',
'form_name': form ? form.getAttribute('data-name') : 'unknown'
});
log('Form error');
}
}
});
});
});
document.querySelectorAll('.w-form').forEach(function(block) {
observer.observe(block, { childList: true });
});
}
// Custom attribute tracking
function initCustomTracking() {
document.querySelectorAll('[data-track-event]').forEach(function(el) {
el.addEventListener('click', function() {
dataLayer.push({
'event': 'custom_interaction',
'interaction_type': this.getAttribute('data-track-event'),
'interaction_category': this.getAttribute('data-track-category') || 'interaction',
'element_text': this.textContent.trim()
});
log('Custom interaction', this.getAttribute('data-track-event'));
});
});
}
// Initialize tracking
document.addEventListener('DOMContentLoaded', function() {
initFormTracking();
initCustomTracking();
log('Event tracking initialized');
});
})();
</script>
Testing Your Data Layer
Method 1: Browser Console
// View entire data layer
console.log(window.dataLayer);
// View most recent push
console.log(window.dataLayer[window.dataLayer.length - 1]);
// Monitor data layer in real-time
window.dataLayer.push = function() {
console.log('DataLayer push:', arguments[0]);
return Array.prototype.push.apply(window.dataLayer, arguments);
};
Method 2: GTM Preview Mode
- In GTM, click Preview
- Enter your Webflow site URL
- Click Connect
- Trigger events on your site
- View data layer values in the Data Layer tab
Method 3: Google Tag Assistant
- Install Tag Assistant
- Visit your site
- Trigger events
- View data layer in Tag Assistant
Common Issues
Data Layer Variable Returns Undefined
Problem: GTM variable shows "undefined" instead of expected value.
Solutions:
- Check variable name: Ensure exact match (case-sensitive)
- Verify data layer push: Check browser console for
dataLayer - Set default value: In GTM variable settings, set a default value
- Check timing: Ensure data is pushed before tag fires
Ecommerce Data Not Available
Problem: window.Webflow.commerce returns undefined.
Solutions:
- Wrap in Webflow.push(): Use
window.Webflow.push(function() {}) - Check published site: Ecommerce data only on published sites
- Verify Ecommerce plan: Ensure you have Webflow Ecommerce plan
Duplicate Events
Problem: Same event fires multiple times.
Solutions:
- Check multiple listeners: Remove duplicate event listeners
- Use event flags: Track if event already fired
- Deduplicate in GTM: Use firing triggers carefully