Data Layer Overview
A data layer is a JavaScript object that stores structured data about page content, user interactions, and ecommerce transactions. It serves as a single source of truth for tracking implementations.
Benefits of Data Layer
| Benefit | Description |
|---|---|
| Separation of concerns | Business logic separate from tracking |
| Maintainability | Update tracking without changing app code |
| Tag manager friendly | Easy GTM integration |
| Consistency | Same data structure across platforms |
| Testing | Easier to validate data |
Basic Data Layer Structure
Global Data Layer
Initialize on all pages:
// Initialize before TikTok Pixel loads
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'pageType': 'homepage',
'userStatus': 'logged_in',
'userId': 'USER_12345',
'userEmail': 'user@example.com', // Will be hashed
'userPhone': '+15551234567' // Will be hashed
});
Page-Specific Data
Add page context:
// Product page
dataLayer.push({
'pageType': 'product',
'product': {
'id': 'SKU_123',
'name': 'Blue Widget',
'category': 'Widgets',
'price': 99.99,
'currency': 'USD',
'availability': 'in_stock'
}
});
// Category page
dataLayer.push({
'pageType': 'category',
'category': {
'name': 'Widgets',
'id': 'CAT_001',
'productCount': 24
}
});
// Cart page
dataLayer.push({
'pageType': 'cart',
'cart': {
'itemCount': 3,
'totalValue': 249.97,
'currency': 'USD',
'items': [
{
'id': 'SKU_123',
'name': 'Blue Widget',
'quantity': 2,
'price': 99.99
},
{
'id': 'SKU_456',
'name': 'Red Widget',
'quantity': 1,
'price': 49.99
}
]
}
});
Ecommerce Data Layer
Enhanced Ecommerce Format
Follow Google's Enhanced Ecommerce structure for compatibility:
dataLayer.push({
'event': 'ecommerce',
'ecommerce': {
'currencyCode': 'USD',
'impressions': [
{
'id': 'SKU_123',
'name': 'Blue Widget',
'category': 'Widgets',
'price': '99.99',
'position': 1
}
]
}
});
Product Detail View
// When user views product
dataLayer.push({
'event': 'productDetail',
'ecommerce': {
'currencyCode': 'USD',
'detail': {
'actionField': {'list': 'Search Results'},
'products': [{
'id': 'SKU_123',
'name': 'Blue Widget',
'category': 'Widgets',
'variant': 'Blue',
'price': '99.99',
'brand': 'WidgetCo'
}]
}
}
});
Add to Cart
// When user adds to cart
dataLayer.push({
'event': 'addToCart',
'ecommerce': {
'currencyCode': 'USD',
'add': {
'products': [{
'id': 'SKU_123',
'name': 'Blue Widget',
'category': 'Widgets',
'price': '99.99',
'quantity': 2
}]
}
}
});
Checkout Steps
// Checkout initiated
dataLayer.push({
'event': 'checkout',
'ecommerce': {
'checkout': {
'actionField': {'step': 1, 'option': 'Visa'},
'products': [{
'id': 'SKU_123',
'name': 'Blue Widget',
'price': '99.99',
'quantity': 2
}]
}
}
});
// Checkout step 2 (shipping)
dataLayer.push({
'event': 'checkoutOption',
'ecommerce': {
'checkout_option': {
'actionField': {'step': 2, 'option': 'Standard Shipping'}
}
}
});
Purchase
// On order confirmation page
dataLayer.push({
'event': 'purchase',
'ecommerce': {
'purchase': {
'actionField': {
'id': 'ORDER_12345',
'revenue': '199.98',
'tax': '16.00',
'shipping': '10.00',
'coupon': 'SUMMER10'
},
'products': [
{
'id': 'SKU_123',
'name': 'Blue Widget',
'category': 'Widgets',
'price': '99.99',
'quantity': 2
}
]
}
}
});
TikTok Pixel Integration with Data Layer
Reading from Data Layer
// Wait for data layer, then fire TikTok event
function getDataLayer(key) {
for (var i = 0; i < window.dataLayer.length; i++) {
if (window.dataLayer[i][key] !== undefined) {
return window.dataLayer[i][key];
}
}
return null;
}
// Fire ViewContent based on data layer
var product = getDataLayer('product');
if (product) {
ttq.track('ViewContent', {
content_type: 'product',
content_id: product.id,
content_name: product.name,
content_category: product.category,
value: product.price,
currency: product.currency
});
}
Event Listener Pattern
// Listen for data layer events
window.dataLayer = window.dataLayer || [];
// Store original push method
var originalPush = window.dataLayer.push;
// Override push to intercept events
window.dataLayer.push = function() {
// Call original push
originalPush.apply(window.dataLayer, arguments);
// Check for ecommerce events
var lastEvent = arguments[0];
if (lastEvent.event === 'addToCart' && lastEvent.ecommerce) {
var product = lastEvent.ecommerce.add.products[0];
ttq.track('AddToCart', {
content_type: 'product',
content_id: product.id,
content_name: product.name,
quantity: product.quantity,
price: parseFloat(product.price),
value: parseFloat(product.price) * product.quantity,
currency: lastEvent.ecommerce.currencyCode
});
}
if (lastEvent.event === 'purchase' && lastEvent.ecommerce) {
var purchase = lastEvent.ecommerce.purchase;
ttq.track('CompletePayment', {
value: parseFloat(purchase.actionField.revenue),
currency: lastEvent.ecommerce.currencyCode,
order_id: purchase.actionField.id,
contents: purchase.products.map(function(p) {
return {
content_id: p.id,
content_name: p.name,
quantity: p.quantity,
price: parseFloat(p.price)
};
})
});
}
};
Platform-Specific Data Layers
Shopify Data Layer
<!-- Shopify Liquid template -->
<script>
window.dataLayer = window.dataLayer || [];
{% if template.name == 'product' %}
dataLayer.push({
'pageType': 'product',
'product': {
'id': '{{ product.id }}',
'sku': '{{ product.selected_or_first_available_variant.sku }}',
'name': '{{ product.title | escape }}',
'category': '{{ product.type }}',
'price': {{ product.price | money_without_currency }},
'currency': '{{ shop.currency }}',
'vendor': '{{ product.vendor }}',
'available': {{ product.available }}
}
});
{% endif %}
{% if template.name == 'cart' %}
dataLayer.push({
'pageType': 'cart',
'cart': {
'totalValue': {{ cart.total_price | money_without_currency }},
'currency': '{{ shop.currency }}',
'itemCount': {{ cart.item_count }},
'items': [
{% for item in cart.items %}
{
'id': '{{ item.product_id }}',
'sku': '{{ item.sku }}',
'name': '{{ item.product.title | escape }}',
'quantity': {{ item.quantity }},
'price': {{ item.price | money_without_currency }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
});
{% endif %}
</script>
WooCommerce Data Layer
// functions.php
function add_woocommerce_datalayer() {
global $product, $wp_query;
$data = array();
// Product page
if (is_product()) {
$data['pageType'] = 'product';
$data['product'] = array(
'id' => $product->get_id(),
'sku' => $product->get_sku(),
'name' => $product->get_name(),
'category' => strip_tags($product->get_categories()),
'price' => floatval($product->get_price()),
'currency' => get_woocommerce_currency()
);
}
// Cart page
if (is_cart() && !WC()->cart->is_empty()) {
$cart_items = array();
foreach (WC()->cart->get_cart() as $cart_item) {
$cart_product = $cart_item['data'];
$cart_items[] = array(
'id' => $cart_product->get_id(),
'sku' => $cart_product->get_sku(),
'name' => $cart_product->get_name(),
'quantity' => $cart_item['quantity'],
'price' => floatval($cart_product->get_price())
);
}
$data['pageType'] = 'cart';
$data['cart'] = array(
'totalValue' => floatval(WC()->cart->get_total('edit')),
'currency' => get_woocommerce_currency(),
'itemCount' => WC()->cart->get_cart_contents_count(),
'items' => $cart_items
);
}
// Output data layer
if (!empty($data)) {
?>
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push(<?php echo json_encode($data); ?>);
</script>
<?php
}
}
add_action('wp_head', 'add_woocommerce_datalayer');
Magento Data Layer
<!-- Magento template -->
<script>
window.dataLayer = window.dataLayer || [];
<?php if ($product = $block->getProduct()): ?>
dataLayer.push({
'pageType': 'product',
'product': {
'id': '<?php echo $product->getId(); ?>',
'sku': '<?php echo $product->getSku(); ?>',
'name': '<?php echo $this->escapeHtml($product->getName()); ?>',
'price': <?php echo $product->getFinalPrice(); ?>,
'currency': '<?php echo $this->getCurrency(); ?>'
}
});
<?php endif; ?>
</script>
Single Page Application (SPA) Data Layer
React Example
// DataLayerContext.js
import React, { createContext, useContext, useState } from 'react';
const DataLayerContext = createContext();
export function DataLayerProvider({ children }) {
const [dataLayer, setDataLayer] = useState([]);
const pushToDataLayer = (data) => {
setDataLayer(prev => [...prev, data]);
window.dataLayer = window.dataLayer || [];
window.dataLayer.push(data);
};
return (
<DataLayerContext.Provider value={{ dataLayer, pushToDataLayer }}>
{children}
</DataLayerContext.Provider>
);
}
export const useDataLayer = () => useContext(DataLayerContext);
// ProductPage.js
import { useEffect } from 'react';
import { useDataLayer } from './DataLayerContext';
function ProductPage({ product }) {
const { pushToDataLayer } = useDataLayer();
useEffect(() => {
// Push product data to data layer
pushToDataLayer({
'event': 'productDetail',
'pageType': 'product',
'product': {
'id': product.sku,
'name': product.name,
'price': product.price,
'currency': 'USD'
}
});
// Fire TikTok event
if (typeof window.ttq !== 'undefined') {
window.ttq.track('ViewContent', {
content_type: 'product',
content_id: product.sku,
content_name: product.name,
value: product.price,
currency: 'USD'
});
}
}, [product, pushToDataLayer]);
return <div>{/* Product UI */}</div>;
}
Vue.js Example
// store/datalayer.js
export default {
state: {
events: []
},
mutations: {
PUSH_EVENT(state, event) {
state.events.push(event);
window.dataLayer = window.dataLayer || [];
window.dataLayer.push(event);
}
},
actions: {
pushToDataLayer({ commit }, event) {
commit('PUSH_EVENT', event);
}
}
};
// ProductView.vue
export default {
mounted() {
this.$store.dispatch('pushToDataLayer', {
event: 'productDetail',
pageType: 'product',
product: {
id: this.product.sku,
name: this.product.name,
price: this.product.price,
currency: 'USD'
}
});
ttq.track('ViewContent', {
content_type: 'product',
content_id: this.product.sku,
content_name: this.product.name,
value: this.product.price,
currency: 'USD'
});
}
};
User Data in Data Layer
Hashing PII
Always hash personally identifiable information:
// Hash function (use crypto-js or similar library)
function hashSHA256(data) {
return CryptoJS.SHA256(data.toLowerCase().trim()).toString();
}
// Push user data to data layer (hashed)
dataLayer.push({
'user': {
'id': 'USER_12345', // Internal ID (not PII)
'emailHash': hashSHA256('user@example.com'),
'phoneHash': hashSHA256('+15551234567'),
'status': 'logged_in',
'lifetime_value': 2500.00,
'first_purchase_date': '2024-01-15'
}
});
// Use in TikTok advanced matching
ttq.identify({
email: dataLayer.find(d => d.user)?.user.emailHash,
phone_number: dataLayer.find(d => d.user)?.user.phoneHash,
external_id: dataLayer.find(d => d.user)?.user.id
});
GTM Integration
Reading Data Layer in GTM
Create Data Layer Variables:
- Variable Type: Data Layer Variable
- Variable Name:
Product ID - Data Layer Variable Name:
product.id
<script>
ttq.track('ViewContent', {
content_type: 'product',
content_id: '{{Product ID}}',
content_name: '{{Product Name}}',
value: {{Product Price}},
currency: '{{Currency}}'
});
</script>
Event-Driven Tags
Trigger Configuration:
- Trigger Type: Custom Event
- Event Name:
addToCart - This trigger fires on: All Custom Events
Tag Fires When:
// Website code
dataLayer.push({
'event': 'addToCart',
'product': {
'id': 'SKU_123',
'name': 'Blue Widget',
'price': 99.99
}
});
// GTM tag automatically fires
Data Layer Validation
Validation Function
function validateDataLayer(data) {
const errors = [];
// Check required fields
if (!data.pageType) {
errors.push('Missing pageType');
}
// Validate product data
if (data.pageType === 'product' && data.product) {
if (!data.product.id) errors.push('Missing product.id');
if (!data.product.name) errors.push('Missing product.name');
if (!data.product.price) errors.push('Missing product.price');
if (!data.product.currency) errors.push('Missing product.currency');
}
// Validate purchase data
if (data.event === 'purchase' && data.ecommerce?.purchase) {
const purchase = data.ecommerce.purchase;
if (!purchase.actionField?.id) {
errors.push('Missing transaction ID');
}
if (!purchase.actionField?.revenue) {
errors.push('Missing transaction revenue');
}
if (!purchase.products || purchase.products.length === 0) {
errors.push('Missing products array');
}
}
if (errors.length > 0) {
console.error('Data Layer Validation Errors:', errors);
return false;
}
return true;
}
// Use before pushing to data layer
const productData = {
pageType: 'product',
product: {
id: 'SKU_123',
name: 'Blue Widget',
price: 99.99,
currency: 'USD'
}
};
if (validateDataLayer(productData)) {
dataLayer.push(productData);
}
Browser Console Inspector
// Inspect current data layer
console.table(window.dataLayer);
// Find specific data
window.dataLayer.filter(d => d.event === 'purchase');
// Get latest product data
window.dataLayer.filter(d => d.product).pop();
Best Practices
Structure
- Initialize early - Before tracking scripts load
- Use consistent naming - camelCase or snake_case
- Shallow objects - Avoid deep nesting
- Push events - Don't overwrite data layer
- Document structure - Maintain data layer schema
Performance
- Batch updates - Push multiple values at once
- Avoid redundancy - Don't push duplicate data
- Clean old data - Remove unnecessary historical events
- Lazy load - Don't populate unused data
Security
- Hash PII - Email, phone, address
- Sanitize input - Escape special characters
- Validate data - Check types and formats
- Limit exposure - Only include necessary data
Testing
- Console logging - View data layer in browser
- GTM Preview mode - Validate data layer variables
- Automated tests - Unit test data layer logic
- QA checklist - Verify on all page types
Data Layer Schema Documentation
## Data Layer Schema
### Global Variables
| Field | Type | Description | Required |
|-------|------|-------------|----------|
| `pageType` | String | Page category | Yes |
| `user.id` | String | User identifier | No |
| `user.emailHash` | String | SHA256 hashed email | No |
| `user.status` | String | logged_in/guest | Yes |
### Product Object
| Field | Type | Description | Required |
|-------|------|-------------|----------|
| `product.id` | String | Product SKU | Yes |
| `product.name` | String | Product name | Yes |
| `product.price` | Number | Product price | Yes |
| `product.currency` | String | ISO currency code | Yes |
| `product.category` | String | Product category | No |
### Cart Object
| Field | Type | Description | Required |
|-------|------|-------------|----------|
| `cart.totalValue` | Number | Total cart value | Yes |
| `cart.currency` | String | ISO currency code | Yes |
| `cart.itemCount` | Number | Number of items | Yes |
| `cart.items` | Array | Array of products | Yes |