Overview
The data layer is a JavaScript object that holds information about your store, products, and customer interactions. GTM reads this data to fire tags with the correct information.
Benefits:
- Centralized data management
- Consistent tracking across platforms
- Easier debugging
- No hard-coded values in tags
Data Layer Structure
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'eventName', // Trigger identifier
'key': 'value', // Data parameters
'nestedObject': { // Structured data
'property': 'value'
}
});
Base Data Layer Implementation
Initialize the data layer before GTM loads.
File: catalog/includes/header.php
Add BEFORE GTM code:
<?php
// Determine page type
$page_script = basename($_SERVER['PHP_SELF'], '.php');
// Map OSCommerce pages to page types
$page_type_map = array(
'index' => 'home',
'product_info' => 'product',
'shopping_cart' => 'cart',
'checkout_shipping' => 'checkout',
'checkout_payment' => 'checkout',
'checkout_confirmation' => 'checkout',
'checkout_success' => 'purchase',
'advanced_search_result' => 'search'
);
$page_type = isset($page_type_map[$page_script]) ? $page_type_map[$page_script] : 'other';
// Get user status
$user_status = tep_session_is_registered('customer_id') ? 'logged_in' : 'guest';
$user_id = tep_session_is_registered('customer_id') ? $customer_id : null;
// Get currency
$currency = isset($_SESSION['currency']) ? $_SESSION['currency'] : DEFAULT_CURRENCY;
?>
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'pageType': '<?php echo $page_type; ?>',
'pageName': '<?php echo addslashes($page_script); ?>',
'userStatus': '<?php echo $user_status; ?>',
'currency': '<?php echo $currency; ?>'
<?php if ($user_id): ?>
,'userId': '<?php echo $user_id; ?>'
,'customerId': '<?php echo $user_id; ?>'
<?php endif; ?>
});
</script>
<!-- GTM code follows here -->
Product Page Data Layer
File: catalog/product_info.php
Add after product information is loaded:
<?php
// After $product_info is retrieved
if (tep_not_null($product_info)) {
// Get category
$category_query = tep_db_query("
SELECT cd.categories_name, cd.categories_id
FROM " . TABLE_CATEGORIES_DESCRIPTION . " cd
INNER JOIN " . TABLE_PRODUCTS_TO_CATEGORIES . " p2c ON cd.categories_id = p2c.categories_id
WHERE p2c.products_id = '" . (int)$product_info['products_id'] . "'
AND cd.language_id = '" . (int)$languages_id . "'
LIMIT 1
");
$category = tep_db_fetch_array($category_query);
// Get manufacturer/brand
$manufacturer_query = tep_db_query("
SELECT manufacturers_name
FROM " . TABLE_MANUFACTURERS . " m
INNER JOIN " . TABLE_PRODUCTS . " p ON m.manufacturers_id = p.manufacturers_id
WHERE p.products_id = '" . (int)$product_info['products_id'] . "'
");
$manufacturer = tep_db_fetch_array($manufacturer_query);
// Calculate price
$special_price = tep_get_products_special_price($product_info['products_id']);
$final_price = $special_price ? $special_price : $product_info['products_price'];
?>
<script>
dataLayer.push({
'event': 'productView',
'ecommerce': {
'detail': {
'products': [{
'id': '<?php echo $product_info['products_id']; ?>',
'name': '<?php echo addslashes($product_info['products_name']); ?>',
'price': <?php echo $final_price; ?>,
'brand': '<?php echo $manufacturer ? addslashes($manufacturer['manufacturers_name']) : ''; ?>',
'category': '<?php echo $category ? addslashes($category['categories_name']) : ''; ?>',
'variant': ''
<?php if ($special_price): ?>
,'discount': <?php echo $product_info['products_price'] - $special_price; ?>
<?php endif; ?>
}]
}
}
});
</script>
<?php
}
?>
Add to Cart Data Layer
File: catalog/product_info.php
Add JavaScript to track add to cart action:
<script>
// Add to cart tracking
document.querySelector('form[name="cart_quantity"]').addEventListener('submit', function(e) {
var productId = '<?php echo $product_info['products_id']; ?>';
var productName = '<?php echo addslashes($product_info['products_name']); ?>';
var productPrice = <?php echo $final_price; ?>;
var quantity = parseInt(document.querySelector('input[name="cart_quantity"]').value) || 1;
dataLayer.push({
'event': 'addToCart',
'ecommerce': {
'currencyCode': '<?php echo $currency; ?>',
'add': {
'products': [{
'id': productId,
'name': productName,
'price': productPrice,
'brand': '<?php echo $manufacturer ? addslashes($manufacturer['manufacturers_name']) : ''; ?>',
'category': '<?php echo $category ? addslashes($category['categories_name']) : ''; ?>',
'quantity': quantity
}]
}
}
});
});
</script>
Remove from Cart Data Layer
File: catalog/shopping_cart.php
Track cart removals:
<?php
// Detect removal action
if (isset($_GET['action']) && $_GET['action'] == 'remove_product') {
$products_id = tep_get_prid($_GET['products_id']);
// Get product info before removal
if (isset($_SESSION['cart']->contents[$products_id])) {
$product_query = tep_db_query("
SELECT pd.products_name, p.products_price
FROM " . TABLE_PRODUCTS . " p
INNER JOIN " . TABLE_PRODUCTS_DESCRIPTION . " pd ON p.products_id = pd.products_id
WHERE p.products_id = '" . (int)$products_id . "'
AND pd.language_id = '" . (int)$languages_id . "'
");
$product = tep_db_fetch_array($product_query);
$quantity = $_SESSION['cart']->contents[$products_id]['qty'];
?>
<script>
dataLayer.push({
'event': 'removeFromCart',
'ecommerce': {
'remove': {
'products': [{
'id': '<?php echo $products_id; ?>',
'name': '<?php echo addslashes($product['products_name']); ?>',
'price': <?php echo $product['products_price']; ?>,
'quantity': <?php echo $quantity; ?>
}]
}
}
});
</script>
<?php
}
}
?>
Cart View Data Layer
File: catalog/shopping_cart.php
Push cart contents to data layer:
<?php
// After cart is loaded
if (isset($_SESSION['cart']) && $_SESSION['cart']->count_contents() > 0) {
$products = $_SESSION['cart']->get_products();
$cart_items = array();
foreach ($products as $product) {
$cart_items[] = sprintf(
'{
"id": "%s",
"name": "%s",
"price": %s,
"quantity": %d
}',
$product['id'],
addslashes($product['name']),
$product['final_price'],
$product['quantity']
);
}
?>
<script>
dataLayer.push({
'event': 'cartView',
'ecommerce': {
'currencyCode': '<?php echo $_SESSION['currency']; ?>',
'cart': {
'products': [<?php echo implode(',', $cart_items); ?>]
}
}
});
</script>
<?php
}
?>
Checkout Initiation Data Layer
File: catalog/checkout_shipping.php
<?php
// On first checkout page
if (isset($_SESSION['cart']) && $_SESSION['cart']->count_contents() > 0) {
$products = $_SESSION['cart']->get_products();
$checkout_items = array();
foreach ($products as $product) {
$checkout_items[] = sprintf(
'{
"id": "%s",
"name": "%s",
"price": %s,
"quantity": %d
}',
$product['id'],
addslashes($product['name']),
$product['final_price'],
$product['quantity']
);
}
?>
<script>
dataLayer.push({
'event': 'checkout',
'ecommerce': {
'checkout': {
'actionField': {
'step': 1,
'option': 'Shipping Information'
},
'products': [<?php echo implode(',', $checkout_items); ?>]
}
}
});
</script>
<?php
}
?>
Checkout Step Tracking
File: catalog/checkout_payment.php
<script>
dataLayer.push({
'event': 'checkoutStep',
'ecommerce': {
'checkout': {
'actionField': {
'step': 2,
'option': 'Payment Information'
}
}
}
});
</script>
File: catalog/checkout_confirmation.php
<script>
dataLayer.push({
'event': 'checkoutStep',
'ecommerce': {
'checkout': {
'actionField': {
'step': 3,
'option': 'Order Confirmation'
}
}
}
});
</script>
Purchase Data Layer
File: catalog/checkout_process.php
Store transaction in session after order creation:
<?php
// After order is inserted ($insert_id contains order_id)
if ($insert_id) {
// Get order details
$order_query = tep_db_query("SELECT * FROM " . TABLE_ORDERS . " WHERE orders_id = '" . (int)$insert_id . "'");
$order = tep_db_fetch_array($order_query);
// Get order products
$products_query = tep_db_query("SELECT * FROM " . TABLE_ORDERS_PRODUCTS . " WHERE orders_id = '" . (int)$insert_id . "'");
$order_products = array();
while ($product = tep_db_fetch_array($products_query)) {
$order_products[] = array(
'id' => $product['products_id'],
'name' => $product['products_name'],
'price' => $product['final_price'],
'quantity' => $product['products_quantity']
);
}
// Get order totals
$totals_query = tep_db_query("SELECT * FROM " . TABLE_ORDERS_TOTAL . " WHERE orders_id = '" . (int)$insert_id . "'");
$tax = 0;
$shipping = 0;
$discount = 0;
while ($total = tep_db_fetch_array($totals_query)) {
if ($total['class'] == 'ot_tax') $tax = $total['value'];
if ($total['class'] == 'ot_shipping') $shipping = $total['value'];
if ($total['class'] == 'ot_coupon') $discount = abs($total['value']);
}
// Store in session for success page
$_SESSION['gtm_purchase_data'] = array(
'transactionId' => $insert_id,
'transactionTotal' => $order['order_total'],
'transactionTax' => $tax,
'transactionShipping' => $shipping,
'transactionDiscount' => $discount,
'currency' => $order['currency'],
'products' => $order_products
);
}
?>
File: catalog/checkout_success.php
Push to data layer on success page:
<?php
// Retrieve purchase data from session
if (isset($_SESSION['gtm_purchase_data'])) {
$purchase = $_SESSION['gtm_purchase_data'];
$products_json = json_encode($purchase['products']);
?>
<script>
dataLayer.push({
'event': 'purchase',
'ecommerce': {
'purchase': {
'actionField': {
'id': '<?php echo $purchase['transactionId']; ?>',
'revenue': <?php echo $purchase['transactionTotal']; ?>,
'tax': <?php echo $purchase['transactionTax']; ?>,
'shipping': <?php echo $purchase['transactionShipping']; ?>
<?php if ($purchase['transactionDiscount'] > 0): ?>
,'coupon': 'DISCOUNT'
<?php endif; ?>
},
'products': <?php echo $products_json; ?>
}
}
});
</script>
<?php
// Clear to prevent duplicate tracking
unset($_SESSION['gtm_purchase_data']);
}
?>
Product List Impressions
File: catalog/index.php or category pages
<?php
// After product listing is loaded
if (isset($listing) && is_array($listing)) {
$impressions = array();
foreach ($listing as $index => $product) {
$impressions[] = sprintf(
'{
"id": "%s",
"name": "%s",
"price": %s,
"list": "Category - %s",
"position": %d
}',
$product['products_id'],
addslashes($product['products_name']),
$product['products_price'],
addslashes($category_name),
$index + 1
);
}
?>
<script>
dataLayer.push({
'event': 'productImpressions',
'ecommerce': {
'impressions': [<?php echo implode(',', $impressions); ?>]
}
});
</script>
<?php
}
?>
Product Click Tracking
Add click tracking to product links:
<a href="product_info.php?products_id=<?php echo $product['products_id']; ?>" echo $product['products_id']; ?>', '<?php echo addslashes($product['products_name']); ?>', '<?php echo $category_name; ?>', <?php echo $index; ?>)">
<?php echo $product['products_name']; ?>
</a>
<script>
function trackProductClick(id, name, category, position) {
dataLayer.push({
'event': 'productClick',
'ecommerce': {
'click': {
'actionField': {'list': 'Category - ' + category},
'products': [{
'id': id,
'name': name,
'position': position
}]
}
}
});
}
</script>
Custom Events
Newsletter Signup
File: catalog/newsletter.php
<?php
if ($subscription_successful) {
?>
<script>
dataLayer.push({
'event': 'newsletterSignup',
'eventCategory': 'Email',
'eventAction': 'Newsletter Signup',
'eventLabel': 'Footer Form'
});
</script>
<?php
}
?>
Search Events
File: catalog/advanced_search_result.php
<?php
if (isset($_GET['keywords'])) {
$search_term = tep_output_string($_GET['keywords']);
$result_count = tep_db_num_rows($listing_query);
?>
<script>
dataLayer.push({
'event': 'search',
'searchTerm': '<?php echo addslashes($search_term); ?>',
'searchResults': <?php echo $result_count; ?>
});
</script>
<?php
}
?>
User Login
File: catalog/login.php
<?php
if (tep_session_is_registered('customer_id')) {
?>
<script>
dataLayer.push({
'event': 'login',
'userId': '<?php echo $customer_id; ?>',
'loginMethod': 'Email'
});
</script>
<?php
}
?>
GTM Configuration for Data Layer Events
Create Triggers in GTM
Trigger 1: Add to Cart
- Triggers > New
- Custom Event
- Event name:
addToCart - This trigger fires on: All Custom Events
- Save
Trigger 2: Purchase
- Triggers > New
- Custom Event
- Event name:
purchase - Save
Create Variables in GTM
Variable 1: Transaction ID
- Variables > New
- Data Layer Variable
- Data Layer Variable Name:
ecommerce.purchase.actionField.id - Name:
DLV - Transaction ID - Save
Variable 2: Transaction Revenue
- Variables > New
- Data Layer Variable
- Data Layer Variable Name:
ecommerce.purchase.actionField.revenue - Name:
DLV - Transaction Revenue - Save
Create Tags Using Data Layer
GA4 Purchase Event Tag
- Tags > New
- Google Analytics: GA4 Event
- Configuration Tag: Your GA4 Config
- Event Name:
purchase - Event Parameters:
transaction_id:\{\{DLV - Transaction ID\}\}value:\{\{DLV - Transaction Revenue\}\}currency:\{\{DLV - Currency\}\}
- Triggering:
purchaseevent - Save
Debug Data Layer
Console Monitoring
<script>
// Monitor all dataLayer pushes
(function() {
var originalPush = dataLayer.push;
dataLayer.push = function() {
console.log('dataLayer.push:', arguments[0]);
return originalPush.apply(dataLayer, arguments);
};
})();
</script>
View Current Data Layer
// In browser console
console.table(dataLayer);
// View specific event
dataLayer.filter(item => item.event === 'purchase');
Best Practices
1. Initialize Before GTM
Always initialize dataLayer before GTM container loads.
2. Use Consistent Event Names
// GOOD - Consistent naming
'event': 'addToCart'
'event': 'removeFromCart'
'event': 'productView'
// BAD - Inconsistent
'event': 'add_to_cart'
'event': 'removeCart'
'event': 'ViewProduct'
3. Clear Ecommerce Object
Clear previous ecommerce data before pushing new:
dataLayer.push({
'ecommerce': null // Clear previous ecommerce
});
dataLayer.push({
'event': 'addToCart',
'ecommerce': { /* new data */ }
});
4. Validate Data Types
// Ensure correct types
'id': '123', // String
'price': 29.99, // Number
'quantity': 1, // Number
'revenue': 149.95 // Number
Testing Checklist
- dataLayer initializes before GTM
- Product views push correct data
- Add to cart events fire
- Remove from cart tracked
- Checkout steps tracked in sequence
- Purchase data complete and accurate
- User ID populated for logged-in users
- Currency code is correct
- No JavaScript errors
- GTM Preview shows events