Webflow Data Layer for Google Tag Manager | Blue Frog Docs

Webflow Data Layer for Google Tag Manager

Implement a comprehensive data layer for Webflow to track ecommerce events, CMS data, and custom attributes with Google Tag Manager.

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

  1. Select an element in Webflow Designer
  2. Go to Settings (gear icon) > Custom Attributes
  3. 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

  1. In GTM, go to Variables > New
  2. Click Variable Configuration
  3. Choose Data Layer Variable
  4. Enter the Data Layer Variable Name (e.g., form_name, ecommerce.value)
  5. Set Data Layer Version to "Version 2"
  6. Name the variable (e.g., "DL - Form Name")
  7. 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

  1. Go to Tags > New
  2. Tag Configuration: Google Analytics: GA4 Event
  3. Event Name: form_submission
  4. Add Event Parameter:
    • Parameter Name: form_name
    • Value: \{\{DL - Form Name\}\}
  5. Trigger: Custom Event = form_success
  6. 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>
<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

  1. In GTM, click Preview
  2. Enter your Webflow site URL
  3. Click Connect
  4. Trigger events on your site
  5. View data layer values in the Data Layer tab

Method 3: Google Tag Assistant

  1. Install Tag Assistant
  2. Visit your site
  3. Trigger events
  4. View data layer in Tag Assistant

Common Issues

Data Layer Variable Returns Undefined

Problem: GTM variable shows "undefined" instead of expected value.

Solutions:

  1. Check variable name: Ensure exact match (case-sensitive)
  2. Verify data layer push: Check browser console for dataLayer
  3. Set default value: In GTM variable settings, set a default value
  4. Check timing: Ensure data is pushed before tag fires

Ecommerce Data Not Available

Problem: window.Webflow.commerce returns undefined.

Solutions:

  1. Wrap in Webflow.push(): Use window.Webflow.push(function() {})
  2. Check published site: Ecommerce data only on published sites
  3. Verify Ecommerce plan: Ensure you have Webflow Ecommerce plan

Duplicate Events

Problem: Same event fires multiple times.

Solutions:

  1. Check multiple listeners: Remove duplicate event listeners
  2. Use event flags: Track if event already fired
  3. Deduplicate in GTM: Use firing triggers carefully

Next Steps

// SYS.FOOTER