Data Layer Overview
A data layer is a JavaScript object that stores structured information about user interactions, page content, and conversion data. By implementing a clean, consistent data layer, you can dynamically populate Outbrain tracking parameters without hardcoding values in your pixel implementation.
Why Use a Data Layer?
- Dynamic values: Pass order totals, product IDs, and user data without template modifications
- Separation of concerns: Marketing team controls tracking logic without touching application code
- Consistency: Standardize data structure across multiple tracking platforms (Outbrain, GA4, Meta)
- Flexibility: Update tracking parameters via tag manager without code deployments
- Debugging: Easily inspect data layer in browser console to verify values
Data Layer Structure
Page-Level Data Layer
Store persistent information about the current page and user context:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'page': {
'type': 'product', // Page type: home, product, category, checkout, confirmation
'category': 'Electronics', // Content or product category
'language': 'en', // Site language
'country': 'US' // User country
},
'user': {
'id': 'USER-12345', // Internal user ID (if logged in)
'status': 'logged_in', // Authentication status
'customerType': 'returning' // New, returning, VIP, etc.
}
});
Event-Based Data Layer
Push event-specific data when user actions occur:
// When user completes purchase
window.dataLayer.push({
'event': 'purchase',
'ecommerce': {
'transaction_id': 'ORD-2024-001',
'value': 149.99,
'currency': 'USD',
'tax': 10.50,
'shipping': 5.00,
'items': [
{
'item_id': 'PROD-ABC-123',
'item_name': 'Wireless Mouse',
'item_category': 'Electronics',
'price': 29.99,
'quantity': 2
},
{
'item_id': 'PROD-XYZ-789',
'item_name': 'Keyboard',
'item_category': 'Electronics',
'price': 89.99,
'quantity': 1
}
]
}
});
Implementation Patterns
Pattern 1: Server-Side Rendering
For traditional server-rendered pages, generate data layer in template:
PHP Example:
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'page': {
'type': '<?php echo $pageType; ?>',
'category': '<?php echo htmlspecialchars($category); ?>'
},
'ecommerce': {
'transaction_id': '<?php echo $order->id; ?>',
'value': <?php echo $order->total; ?>,
'currency': '<?php echo $order->currency; ?>'
}
});
</script>
Python/Django Example:
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'ecommerce': {
'transaction_id': '{{ order.id }}',
'value': {{ order.total }},
'currency': '{{ order.currency }}'
}
});
</script>
Ruby/Rails Example:
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'ecommerce': {
'transaction_id': '<%= @order.id %>',
'value': <%= @order.total %>,
'currency': '<%= @order.currency %>'
}
});
</script>
Pattern 2: Client-Side JavaScript
For SPAs or dynamic content, populate data layer via JavaScript:
// On page load or route change
function initializeDataLayer(pageData) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'page': {
'type': pageData.type,
'category': pageData.category
},
'user': {
'id': getCurrentUserId(),
'status': isUserLoggedIn() ? 'logged_in' : 'guest'
}
});
}
// On conversion event
function trackPurchase(orderData) {
window.dataLayer.push({
'event': 'purchase',
'ecommerce': {
'transaction_id': orderData.id,
'value': orderData.total,
'currency': orderData.currency
}
});
}
Pattern 3: React / Vue / Angular
React Example:
import { useEffect } from 'react';
function CheckoutConfirmation({ order }) {
useEffect(() => {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'purchase',
'ecommerce': {
'transaction_id': order.id,
'value': order.total,
'currency': order.currency,
'items': order.items.map(item => ({
'item_id': item.sku,
'item_name': item.name,
'price': item.price,
'quantity': item.quantity
}))
}
});
}, [order]);
return <div>Thank you for your order!</div>;
}
Vue Example:
export default {
mounted() {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: this.order.id,
value: this.order.total,
currency: this.order.currency
}
});
}
}
Connecting Data Layer to Outbrain
Direct JavaScript Integration
Read from data layer and pass to Outbrain pixel:
// Wait for data layer to be populated
window.addEventListener('load', function() {
const ecommerce = window.dataLayer.find(item => item.ecommerce)?.ecommerce;
if (ecommerce && ecommerce.transaction_id) {
obApi('track', 'Conversion', {
orderValue: ecommerce.value,
orderId: ecommerce.transaction_id,
currency: ecommerce.currency
});
}
});
Google Tag Manager Integration
Create GTM variables to read data layer values:
Step 1: Create Data Layer Variables in GTM
- Go to Variables > New > Data Layer Variable
- Create variables for each value:
DLV - Transaction ID→ Data Layer Variable Name:ecommerce.transaction_idDLV - Order Value→ Data Layer Variable Name:ecommerce.valueDLV - Currency→ Data Layer Variable Name:ecommerce.currencyDLV - Product ID→ Data Layer Variable Name:ecommerce.items.0.item_id
Step 2: Use Variables in Outbrain Tag
<script type="text/javascript">
obApi('track', 'Conversion', {
orderValue: {{DLV - Order Value}},
orderId: '{{DLV - Transaction ID}}',
currency: '{{DLV - Currency}}',
productId: '{{DLV - Product ID}}'
});
</script>
Step 3: Set Trigger to Fire on Data Layer Event
- Create trigger: Custom Event with event name:
purchase - This trigger fires when
window.dataLayer.push({ event: 'purchase' })executes
Data Layer Schema for Outbrain
Required Fields
{
'event': 'purchase', // Event name (purchase, lead, signup)
'ecommerce': {
'transaction_id': 'ORD-12345', // Unique order ID (string)
'value': 149.99, // Total order value (number, not string)
'currency': 'USD' // ISO 4217 currency code (string)
}
}
Optional Fields
{
'event': 'purchase',
'ecommerce': {
'transaction_id': 'ORD-12345',
'value': 149.99,
'currency': 'USD',
'tax': 10.50, // Tax amount
'shipping': 5.00, // Shipping cost
'discount': 15.00, // Discount applied
'coupon': 'SAVE15', // Coupon code used
'payment_method': 'credit_card', // Payment type
'items': [ // Product details
{
'item_id': 'PROD-123',
'item_name': 'Widget',
'item_category': 'Electronics',
'price': 29.99,
'quantity': 2
}
]
},
'user': {
'id': 'USER-789', // Internal user ID
'email_hash': 'abc123...', // SHA-256 hashed email (for enhanced matching)
'customer_type': 'returning' // Customer segment
}
}
Lead Generation Schema
{
'event': 'lead',
'lead': {
'lead_id': 'LEAD-2024-456', // Unique lead identifier
'value': 50, // Estimated lead value
'currency': 'USD',
'lead_type': 'Contact Form', // Type of lead
'industry': 'Technology', // Lead's industry
'company_size': '50-200' // Company size bracket
}
}
Engagement Event Schema
{
'event': 'signup',
'user': {
'user_id': 'USER-2024-789',
'email_hash': 'hashed_email',
'signup_method': 'email', // email, google, facebook
'account_type': 'free' // free, trial, paid
}
}
Data Validation & Testing
Browser Console Inspection
// View entire data layer
console.log(window.dataLayer);
// Find specific event
const purchaseEvent = window.dataLayer.find(item => item.event === 'purchase');
console.log(purchaseEvent);
// Check specific value
console.log(window.dataLayer.find(item => item.ecommerce)?.ecommerce?.value);
GTM Preview Mode Validation
- Enter GTM Preview mode
- Navigate to conversion page
- In GTM debug panel, select the event (e.g., "purchase")
- Check Data Layer tab to verify all expected values are present
- Check Variables tab to confirm GTM variables read correct values
- Verify Outbrain tag fires with correct parameters
Data Layer Listener for Debugging
// Log all data layer pushes
(function() {
const originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
console.log('Data Layer Push:', arguments);
return originalPush.apply(window.dataLayer, arguments);
};
})();
Common Data Layer Issues
Issue: Data Layer Not Defined
Symptom: Cannot read property 'push' of undefined
Solution: Initialize data layer before any pushes:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ /* data */ });
Issue: Values Are Strings Instead of Numbers
Symptom: GTM or Outbrain receives "149.99" instead of 149.99
Solution: Ensure numeric values are not quoted:
// Wrong
'value': '149.99'
// Correct
'value': 149.99
'value': parseFloat(orderTotal)
Issue: Data Layer Pushed After Tag Fires
Symptom: GTM tag fires but variables are undefined or empty
Solution: Push to data layer before triggering event:
// Push data first
window.dataLayer.push({
'ecommerce': { /* data */ }
});
// Then push event (triggers GTM tags)
window.dataLayer.push({
'event': 'purchase'
});
Issue: Special Characters Breaking JSON
Symptom: JavaScript errors or malformed data
Solution: Escape or sanitize user-input values:
function sanitize(str) {
return String(str).replace(/['"\\]/g, '');
}
window.dataLayer.push({
'ecommerce': {
'coupon': sanitize(userInput)
}
});
Data Layer Best Practices
Naming Conventions
- Use snake_case for consistency with GA4:
transaction_id,order_value - Be descriptive:
item_categorynotcat,customer_typenottype - Namespace custom properties:
custom_lead_score,custom_attribution_source
Data Types
- Strings: Wrap in quotes:
'USD','ORD-12345' - Numbers: No quotes:
149.99,2,0 - Booleans: Use true/false:
is_logged_in: true - Arrays: Use brackets:
items: [...]
Timing
- Initialize base data layer in
<head>before any tracking scripts - Push page-level data on initial page load
- Push event-level data immediately before or during user action
- For SPAs, clear/reset data layer on route changes to avoid stale data
Privacy & Security
- Never include plaintext emails, phone numbers, or PII
- Hash sensitive data with SHA-256 before adding to data layer
- Respect user consent: don't populate data layer if user opted out
- Sanitize user input to prevent XSS attacks via data layer injection
Data Layer Implementation Checklist
- Data layer initialized before any tracking scripts load
- Required fields present: transaction_id, value, currency
- Numeric values are numbers, not strings
- Currency codes are uppercase ISO 4217 (USD, EUR, GBP)
- Unique transaction IDs for deduplication
- Event name matches GTM trigger configuration
- Data layer structure documented for team reference
- Tested in browser console and GTM preview mode
- Privacy compliance: no plaintext PII, consent respected
- Single-page app: data layer updates on route changes
- Server-side rendering: values properly escaped/sanitized
- Fallback values or error handling for missing data