GTM Data Layer on Salesforce Commerce Cloud | Blue Frog Docs

GTM Data Layer on Salesforce Commerce Cloud

Complete data layer implementation for GTM on SFCC

GTM Data Layer on Salesforce Commerce Cloud

This guide covers implementing a comprehensive data layer for Google Tag Manager on Salesforce Commerce Cloud.

Data Layer Architecture

SFCC Data Layer Strategy

┌─────────────────────────────────────────────┐
│           Server-Side (SFCC)                │
│  ┌─────────────┐    ┌─────────────────┐    │
│  │ Controllers │ →  │ ISML Templates  │    │
│  └─────────────┘    └─────────────────┘    │
│          ↓                  ↓               │
└─────────────────────────────────────────────┘
           ↓                  ↓
┌─────────────────────────────────────────────┐
│         Client-Side (Browser)               │
│  ┌─────────────────────────────────────┐   │
│  │           window.dataLayer          │   │
│  └─────────────────────────────────────┘   │
│                     ↓                       │
│  ┌─────────────────────────────────────┐   │
│  │      Google Tag Manager (GTM)       │   │
│  └─────────────────────────────────────┘   │
└─────────────────────────────────────────────┘

Base Data Layer Model

Create Data Layer Helper

cartridges/app_custom/cartridge/scripts/helpers/dataLayerHelpers.js

'use strict';

var formatHelpers = require('*/cartridge/scripts/helpers/formatHelpers');

function getBasePageData(req) {
    var Site = require('dw/system/Site');

    return {
        siteName: Site.current.name,
        siteLocale: req.locale.id,
        currency: req.session.currency.currencyCode,
        timestamp: new Date().toISOString()
    };
}

function getUserData(customer) {
    var data = {
        userLoggedIn: customer.authenticated,
        userType: 'guest'
    };

    if (customer.authenticated) {
        data.userType = customer.customerGroups.length > 0 ? 'member' : 'registered';
        // Hash customer ID for privacy
        data.userIdHash = hashValue(customer.ID);
    }

    return data;
}

function getProductData(product, quantity) {
    var ProductMgr = require('dw/catalog/ProductMgr');
    var apiProduct = ProductMgr.getProduct(product.id);

    return {
        item_id: product.id,
        item_name: product.productName,
        item_category: apiProduct.primaryCategory ?
            apiProduct.primaryCategory.displayName : '',
        item_brand: product.brand || '',
        price: product.price.sales ? product.price.sales.value : 0,
        quantity: quantity || 1
    };
}

function getCartData(basket) {
    var items = [];
    var productLineItems = basket.getAllProductLineItems();

    for (var i = 0; i < productLineItems.length; i++) {
        var pli = productLineItems[i];
        items.push({
            item_id: pli.productID,
            item_name: pli.productName,
            price: pli.adjustedPrice.value / pli.quantityValue,
            quantity: pli.quantityValue
        });
    }

    return {
        items: items,
        value: basket.totalGrossPrice.value,
        currency: basket.currencyCode,
        itemCount: basket.productQuantityTotal
    };
}

function getOrderData(order) {
    var items = [];
    var productLineItems = order.getAllProductLineItems();

    for (var i = 0; i < productLineItems.length; i++) {
        var pli = productLineItems[i];
        items.push({
            item_id: pli.productID,
            item_name: pli.productName,
            price: pli.adjustedPrice.value / pli.quantityValue,
            quantity: pli.quantityValue,
            discount: pli.adjustedPrice.value < pli.price.value ?
                (pli.price.value - pli.adjustedPrice.value) : 0
        });
    }

    return {
        transaction_id: order.orderNo,
        value: order.totalGrossPrice.value,
        tax: order.totalTax.value,
        shipping: order.shippingTotalPrice.value,
        currency: order.currencyCode,
        coupon: order.couponLineItems.length > 0 ?
            order.couponLineItems[0].couponCode : '',
        items: items
    };
}

function hashValue(value) {
    var MessageDigest = require('dw/crypto/MessageDigest');
    var Encoding = require('dw/crypto/Encoding');
    var digest = new MessageDigest(MessageDigest.DIGEST_SHA_256);
    return Encoding.toHex(digest.digestBytes(value));
}

module.exports = {
    getBasePageData: getBasePageData,
    getUserData: getUserData,
    getProductData: getProductData,
    getCartData: getCartData,
    getOrderData: getOrderData
};

Page-Specific Data Layers

Product Detail Page

Controller Extension:

// cartridges/app_custom/cartridge/controllers/Product.js
server.append('Show', function (req, res, next) {
    var viewData = res.getViewData();
    var dataLayerHelpers = require('*/cartridge/scripts/helpers/dataLayerHelpers');

    viewData.dataLayer = {
        pageType: 'product',
        ecommerce: {
            detail: {
                products: [dataLayerHelpers.getProductData(viewData.product, 1)]
            }
        }
    };

    res.setViewData(viewData);
    return next();
});

ISML Template:

<isif condition="${pdict.dataLayer}">
<script>
    window.dataLayer = window.dataLayer || [];
    dataLayer.push({
        'event': 'view_item',
        'ecommerce': <isprint value="${JSON.stringify(pdict.dataLayer.ecommerce)}" encoding="off"/>
    });
</script>
</isif>

Category Page

// Controller
server.append('Show', function (req, res, next) {
    var viewData = res.getViewData();

    var impressions = [];
    viewData.productSearch.productIds.forEach(function(pid, index) {
        impressions.push({
            item_id: pid.productID,
            item_list_name: 'Category: ' + viewData.category.name,
            index: index
        });
    });

    viewData.dataLayer = {
        pageType: 'category',
        categoryName: viewData.category.name,
        ecommerce: {
            items: impressions
        }
    };

    res.setViewData(viewData);
    return next();
});

Cart Page

server.append('Show', function (req, res, next) {
    var viewData = res.getViewData();
    var BasketMgr = require('dw/order/BasketMgr');
    var dataLayerHelpers = require('*/cartridge/scripts/helpers/dataLayerHelpers');

    var basket = BasketMgr.getCurrentBasket();

    if (basket) {
        viewData.dataLayer = {
            pageType: 'cart',
            ecommerce: dataLayerHelpers.getCartData(basket)
        };
    }

    res.setViewData(viewData);
    return next();
});

Order Confirmation

server.append('Confirm', function (req, res, next) {
    var viewData = res.getViewData();
    var OrderMgr = require('dw/order/OrderMgr');
    var dataLayerHelpers = require('*/cartridge/scripts/helpers/dataLayerHelpers');

    var order = OrderMgr.getOrder(viewData.order.orderNumber);

    if (order) {
        viewData.dataLayer = {
            pageType: 'purchase',
            ecommerce: dataLayerHelpers.getOrderData(order)
        };
    }

    res.setViewData(viewData);
    return next();
});

GTM Variable Configuration

Data Layer Variables

Create these variables in GTM:

Variable Name Variable Type Data Layer Variable Name
DL - Page Type Data Layer Variable pageType
DL - User Logged In Data Layer Variable userLoggedIn
DL - Ecommerce Items Data Layer Variable ecommerce.items
DL - Transaction ID Data Layer Variable ecommerce.transaction_id
DL - Transaction Value Data Layer Variable ecommerce.value
DL - Currency Data Layer Variable ecommerce.currency

GA4 Ecommerce Tag Configuration

Tag Type: Google Analytics: GA4 Event
Configuration Tag: [Your GA4 Config Tag]
Event Name: {{Event}}

Event Parameters:
- items: {{DL - Ecommerce Items}}
- value: {{DL - Transaction Value}}
- currency: {{DL - Currency}}

Firing Trigger: [Corresponding event trigger]

For purchase events, add additional parameters:

- transaction_id: {{DL - Transaction ID}}
- shipping: {{DL - Shipping}}
- tax: {{DL - Tax}}

Client-Side Event Pushing

Add to Cart Handler

$(document).on('click', '.add-to-cart', function(e) {
    var $form = $(this).closest('form');
    var productData = {
        item_id: $form.find('[name="pid"]').val(),
        item_name: $form.find('.product-name').text(),
        price: parseFloat($form.find('.price .value').attr('content')),
        quantity: parseInt($form.find('[name="quantity"]').val()) || 1
    };

    dataLayer.push({
        'event': 'add_to_cart',
        'ecommerce': {
            'items': [productData],
            'value': productData.price * productData.quantity,
            'currency': window.siteData.currency
        }
    });
});

Remove from Cart Handler

$(document).on('click', '.remove-product', function(e) {
    var $item = $(this).closest('.product-card');
    var productData = $item.data('product');

    dataLayer.push({
        'event': 'remove_from_cart',
        'ecommerce': {
            'items': [{
                item_id: productData.id,
                item_name: productData.name,
                price: productData.price,
                quantity: productData.quantity
            }]
        }
    });
});

Debugging Data Layer

Console Verification

// Check current data layer state
console.table(dataLayer);

// Monitor data layer pushes
(function() {
    var originalPush = dataLayer.push;
    dataLayer.push = function() {
        console.log('dataLayer.push:', arguments);
        return originalPush.apply(this, arguments);
    };
})();

GTM Preview Mode

  1. Enable GTM Preview
  2. Check Data Layer tab in debug panel
  3. Verify each push contains expected data

Next Steps

// SYS.FOOTER