Wix Data Layer for Google Tag Manager | Blue Frog Docs

Wix Data Layer for Google Tag Manager

Build a custom data layer for GTM on Wix using Velo, Wix Events, and platform APIs.

Wix Data Layer for Google Tag Manager

Wix does not provide a native data layer like Shopify or WordPress. This guide shows how to build your own using Wix Velo, access Wix platform data, and structure it for GTM consumption.

Understanding the Challenge

What Wix Doesn't Provide

  • No automatic data layer population (unlike Shopify, WordPress + plugins)
  • No server-side data layer for page metadata
  • No standardized ecommerce object pushed to GTM
  • Limited access to Wix's internal site structure

What You Can Build

Using Wix Velo, you can:

  • Create custom data layer objects
  • Push page/product/user data to GTM
  • Track user interactions and state changes
  • Access Wix Stores, Bookings, and Members data

Prerequisites

Basic Data Layer Structure

Initializing the Data Layer

Add this to your Site Code (runs on all pages):

// File: public/dataLayer.js (Wix Studio)
// Or Site Code → Global

// Initialize dataLayer if it doesn't exist
window.dataLayer = window.dataLayer || [];

// Helper function to push data
export function pushToDataLayer(data) {
  window.dataLayer.push(data);
}

// Initial page load data
$w.onReady(function () {
  pushToDataLayer({
    'event': 'wix.page.loaded',
    'pageType': getPageType(),
    'pageTitle': document.title,
    'pageUrl': window.location.href,
    'pagePath': window.location.pathname
  });
});

// Get page type helper
function getPageType() {
  const path = window.location.pathname;

  if (path === '/' || path === '/home') return 'home';
  if (path.includes('/product-page/')) return 'product';
  if (path.includes('/shop')) return 'collection';
  if (path.includes('/cart')) return 'cart';
  if (path.includes('/checkout')) return 'checkout';
  if (path.includes('/thank-you')) return 'purchase';
  if (path.includes('/blog')) return 'blog';

  return 'other';
}

Site-Wide Variables

Push global site data:

import wixWindow from 'wix-window';

$w.onReady(function () {
  window.dataLayer = window.dataLayer || [];

  window.dataLayer.push({
    'siteInfo': {
      'siteId': wixWindow.viewMode === 'Site' ? 'production' : 'preview',
      'viewMode': wixWindow.viewMode,
      'formFactor': wixWindow.formFactor, // 'Desktop', 'Tablet', 'Mobile'
      'language': document.documentElement.lang
    }
  });
});

User Data Layer

Anonymous vs Logged-In Users

Track user authentication state:

import wixUsers from 'wix-users';
import { pushToDataLayer } from 'public/dataLayer';

$w.onReady(async function () {
  const isLoggedIn = wixUsers.currentUser.loggedIn;

  let userData = {
    'userStatus': isLoggedIn ? 'logged_in' : 'anonymous'
  };

  if (isLoggedIn) {
    const user = wixUsers.currentUser;

    userData = {
      ...userData,
      'userId': user.id, // Consider hashing for privacy
      'userRole': await getUserRole(user)
    };
  }

  pushToDataLayer({
    'event': 'user.identified',
    'user': userData
  });
});

async function getUserRole(user) {
  // Determine if user is member, subscriber, etc.
  const role = await user.getRole();
  return role ? role.name : 'member';
}

⚠️ Privacy Consideration: Never send PII (email, name, phone) to GTM unless required and compliant with privacy regulations.

User Login/Logout Events

import wixUsers from 'wix-users';

$w.onReady(function () {
  // Login event
  wixUsers.onLogin((user) => {
    window.dataLayer.push({
      'event': 'user.login',
      'userId': user.id,
      'loginMethod': 'wix_members'
    });
  });

  // Logout event (if supported)
  // Note: Wix doesn't expose onLogout natively
  // Implement on logout button click
});

Ecommerce Data Layer

Product Page Data

Push product details to GTM:

// File: Product Page Code
import wixStoresFrontend from 'wix-stores-frontend';
import { pushToDataLayer } from 'public/dataLayer';

$w.onReady(async function () {
  try {
    const product = await wixStoresFrontend.product.getProduct();

    if (product) {
      pushToDataLayer({
        'event': 'productDetail',
        'ecommerce': {
          'detail': {
            'products': [{
              'id': product.sku || product.id,
              'name': product.name,
              'price': product.price,
              'currency': product.currency || 'USD',
              'brand': product.brand || '',
              'category': product.productType || '',
              'variant': '', // Will be populated on variant selection
              'stock': product.inStock ? 'in stock' : 'out of stock'
            }]
          }
        },
        'product': {
          'id': product.id,
          'sku': product.sku,
          'name': product.name,
          'price': product.price,
          'currency': product.currency,
          'inStock': product.inStock
        }
      });
    }
  } catch (error) {
    console.error('Product data layer error:', error);
  }
});

Product Variant Selection

Track when users select variants (size, color, etc.):

import wixStoresFrontend from 'wix-stores-frontend';

$w.onReady(function () {
  // Listen for option selections
  wixStoresFrontend.product.onOptionsSelectionChanged(async (selections) => {
    const product = await wixStoresFrontend.product.getProduct();
    const selectedVariant = selections.map(s => s.selection).join(' / ');

    window.dataLayer.push({
      'event': 'product.variantSelected',
      'productVariant': selectedVariant,
      'productId': product.sku || product.id
    });
  });
});

Collection/Category Page Data

Track product impressions:

// File: Collection Page Code
import wixStoresFrontend from 'wix-stores-frontend';
import { pushToDataLayer } from 'public/dataLayer';

$w.onReady(async function () {
  try {
    const category = await wixStoresFrontend.category.getCategory();
    const products = await wixStoresFrontend.products.getProducts();

    if (products.items && products.items.length > 0) {
      const impressions = products.items.map((product, index) => ({
        'id': product.sku || product.id,
        'name': product.name,
        'price': product.price,
        'currency': product.currency || 'USD',
        'category': category?.name || 'All Products',
        'list': category?.name || 'Product Grid',
        'position': index + 1
      }));

      pushToDataLayer({
        'event': 'productImpressions',
        'ecommerce': {
          'impressions': impressions
        }
      });
    }
  } catch (error) {
    console.error('Collection data layer error:', error);
  }
});

Add to Cart Event

import wixStoresFrontend from 'wix-stores-frontend';

$w('#addToCartButton').onClick(async () => {
  const product = await wixStoresFrontend.product.getProduct();
  const quantity = Number($w('#quantityInput').value) || 1;
  const selectedOptions = await wixStoresFrontend.product.getOptionsSelections();

  window.dataLayer.push({
    'event': 'addToCart',
    'ecommerce': {
      'add': {
        'products': [{
          'id': product.sku || product.id,
          'name': product.name,
          'price': product.price,
          'currency': product.currency || 'USD',
          'quantity': quantity,
          'variant': selectedOptions.map(opt => opt.selection).join(' / ') || ''
        }]
      }
    }
  });
});

Cart Data Layer

Push cart contents to data layer on cart page:

// File: Cart Page Code
import wixStoresFrontend from 'wix-stores-frontend';
import { pushToDataLayer } from 'public/dataLayer';

$w.onReady(async function () {
  try {
    const cart = await wixStoresFrontend.cart.getCurrentCart();

    if (cart && cart.lineItems) {
      const cartProducts = cart.lineItems.map((item, index) => ({
        'id': item.sku || item.productId,
        'name': item.name,
        'price': item.price,
        'currency': cart.currency || 'USD',
        'quantity': item.quantity,
        'variant': item.options?.map(opt => opt.selection).join(' / ') || '',
        'position': index + 1
      }));

      pushToDataLayer({
        'event': 'cartView',
        'ecommerce': {
          'cart': {
            'products': cartProducts,
            'value': cart.subtotal,
            'currency': cart.currency || 'USD',
            'itemCount': cart.lineItems.length
          }
        }
      });
    }
  } catch (error) {
    console.error('Cart data layer error:', error);
  }
});

Checkout Initiation

$w('#checkoutButton').onClick(async () => {
  const cart = await wixStoresFrontend.cart.getCurrentCart();

  window.dataLayer.push({
    'event': 'checkout',
    'ecommerce': {
      'checkout': {
        'actionField': {'step': 1},
        'products': cart.lineItems.map(item => ({
          'id': item.sku || item.productId,
          'name': item.name,
          'price': item.price,
          'quantity': item.quantity
        }))
      }
    }
  });
});

Purchase Data Layer

On the thank you page:

// File: Thank You Page Code
import wixLocation from 'wix-location';

$w.onReady(async function () {
  const orderId = wixLocation.query.orderId;

  if (!orderId) return;

  // Check if already tracked (prevent duplicates)
  if (sessionStorage.getItem('purchaseTracked_' + orderId)) {
    return;
  }

  // Fetch order details (requires backend code)
  const order = await getOrderDetails(orderId);

  if (order) {
    window.dataLayer.push({
      'event': 'purchase',
      'ecommerce': {
        'purchase': {
          'actionField': {
            'id': order.number || order._id,
            'revenue': order.totals.total,
            'tax': order.totals.tax,
            'shipping': order.totals.shipping,
            'coupon': order.appliedCoupon?.code || ''
          },
          'products': order.lineItems.map((item, index) => ({
            'id': item.sku || item.productId,
            'name': item.name,
            'price': item.price,
            'quantity': item.quantity,
            'variant': item.options?.map(opt => opt.selection).join(' / ') || '',
            'position': index + 1
          }))
        }
      },
      'transactionId': order.number || order._id,
      'transactionTotal': order.totals.total,
      'transactionCurrency': order.currency || 'USD'
    });

    // Mark as tracked
    sessionStorage.setItem('purchaseTracked_' + orderId, 'true');
  }
});

async function getOrderDetails(orderId) {
  // Import and call backend function
  const { fetch } = await import('wix-fetch');
  const response = await fetch(`/api/getOrder?orderId=${orderId}`);
  return response.json();
}

Form Interaction Data Layer

Form View

$w.onReady(function () {
  window.dataLayer.push({
    'event': 'formView',
    'formId': 'contact_form',
    'formName': 'Contact Us',
    'formLocation': window.location.pathname
  });
});

Form Submission

$w('#contactForm').onWixFormSubmitted((event) => {
  window.dataLayer.push({
    'event': 'formSubmit',
    'formId': 'contact_form',
    'formName': 'Contact Us',
    'formLocation': window.location.pathname,
    'submissionId': event.submissionId
  });
});

Form Field Interactions

$w('#emailInput').onFocus(() => {
  window.dataLayer.push({
    'event': 'formFieldFocus',
    'fieldName': 'email',
    'formId': 'contact_form'
  });
});
$w('#mainMenu').onClick((event) => {
  window.dataLayer.push({
    'event': 'navigation.click',
    'linkText': event.target.label || event.target.text,
    'linkUrl': event.target.link,
    'menuLocation': 'main_menu'
  });
});

Single-Page Application Navigation

Track route changes:

import wixLocation from 'wix-location';

let lastPath = wixLocation.path.join('/');

wixLocation.onChange(() => {
  const currentPath = wixLocation.path.join('/');

  if (currentPath !== lastPath) {
    lastPath = currentPath;

    window.dataLayer.push({
      'event': 'virtualPageview',
      'virtualPagePath': currentPath,
      'virtualPageTitle': document.title,
      'virtualPageUrl': wixLocation.url,
      'pageType': getPageType()
    });
  }
});

Wix Events API Integration

Access platform-level events:

import { events } from 'wix-events-frontend';

// Product quick view
events.onProductQuickView((event) => {
  window.dataLayer.push({
    'event': 'product.quickView',
    'productId': event.product.id,
    'productName': event.product.name
  });
});

// Search performed
events.onSearch((event) => {
  window.dataLayer.push({
    'event': 'search',
    'searchTerm': event.searchQuery,
    'searchResults': event.resultCount
  });
});

Note: Available events depend on installed Wix apps and plan level.

GTM Variable Configuration

Create Data Layer Variables in GTM

For each data layer key, create a corresponding GTM variable:

Variable Type: Data Layer Variable

Examples:

Variable Name Data Layer Variable Name Default Value
DL - Page Type pageType other
DL - User Status user.userStatus anonymous
DL - Product ID product.id (not set)
DL - Product Name product.name (not set)
DL - Product Price product.price 0
DL - Transaction ID transactionId (not set)
DL - Form ID formId (not set)

Using Data Layer Variables in Tags

Reference in GA4 event tags:

Event Parameters:

  • page_type: \{\{DL - Page Type\}\}
  • user_status: \{\{DL - User Status\}\}
  • product_id: \{\{DL - Product ID\}\}

Debugging the Data Layer

Console Inspection

// View entire data layer
console.table(window.dataLayer);

// View last pushed object
console.log(window.dataLayer[window.dataLayer.length - 1]);

// Monitor pushes
const originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
  console.log('Data layer push:', arguments[0]);
  return originalPush.apply(window.dataLayer, arguments);
};

GTM Preview Mode

  1. Open GTM Preview
  2. Connect to your Wix site
  3. Click Data Layer tab
  4. Expand data layer events
  5. Verify your custom objects appear

Chrome Extension: dataLayer Inspector+

Install dataLayer Inspector+ to monitor data layer in real-time.

Common Data Layer Patterns

Error Tracking

window.addEventListener('error', (event) => {
  window.dataLayer.push({
    'event': 'jsError',
    'errorMessage': event.message,
    'errorUrl': event.filename,
    'errorLine': event.lineno,
    'errorColumn': event.colno
  });
});

Scroll Depth

let scrollDepths = [25, 50, 75, 90];
let tracked = [];

window.addEventListener('scroll', () => {
  const percent = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;

  scrollDepths.forEach(depth => {
    if (percent >= depth && !tracked.includes(depth)) {
      tracked.push(depth);
      window.dataLayer.push({
        'event': 'scrollDepth',
        'scrollPercent': depth
      });
    }
  });
});

Video Interactions

// For Wix Video elements
$w('#videoPlayer').onPlay(() => {
  window.dataLayer.push({
    'event': 'videoPlay',
    'videoTitle': $w('#videoPlayer').title,
    'videoUrl': $w('#videoPlayer').src
  });
});

$w('#videoPlayer').onEnded(() => {
  window.dataLayer.push({
    'event': 'videoComplete',
    'videoTitle': $w('#videoPlayer').title
  });
});

Data Layer Best Practices

  1. Push before the event: Data should be in the layer before the event triggers
  2. Clear ecommerce object: Reset ecommerce data between events
  3. Consistent naming: Use camelCase or snake_case consistently
  4. Document your schema: Maintain a data layer specification document
  5. Test thoroughly: Verify all data layer pushes in GTM Preview
  6. Avoid PII: Never push personal information without consent and necessity
  7. Version control: Document data layer changes in GTM version notes

Wix Limitations

  1. No server-side rendering - All data layer logic is client-side
  2. Limited backend access - Some data requires backend API calls
  3. No native Wix Events for all interactions
  4. Velo required for most advanced implementations
  5. Performance impact - Complex data layer logic can slow page load

Next Steps

Additional Resources

// SYS.FOOTER