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
- Enable GTM Preview
- Check Data Layer tab in debug panel
- Verify each push contains expected data