Overview
A data layer is a JavaScript object that stores information about the page, user interactions, and transaction data. While LinkedIn's Insight Tag doesn't have native data layer integration like Google Analytics, implementing a proper data layer structure enables dynamic conversion tracking, flexible tag management, and scalable analytics implementation.
Why Use a Data Layer?
Benefits
1. Separation of Concerns
- Marketing data separate from application code
- Developers populate data layer
- Marketers configure tags independently
2. Flexibility
- Easy to change tracking without code changes
- Support multiple platforms from one source
- A/B test tracking implementations
3. Dynamic Values
- Pass transaction amounts
- Track product details
- Capture user attributes
- Send custom parameters
4. Consistency
- Standardized data structure
- Reduces implementation errors
- Easier to maintain and debug
Data Layer Fundamentals
Basic Structure
A data layer is typically a JavaScript array or object:
// Array-based (Google Tag Manager style)
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'purchase',
'transactionId': 'ORDER_12345',
'transactionTotal': 99.99,
'transactionCurrency': 'USD'
});
// Object-based (simple approach)
window.digitalData = {
page: {
pageInfo: {
pageName: 'Order Confirmation'
}
},
transaction: {
transactionID: 'ORDER_12345',
total: {
basePrice: 99.99,
currency: 'USD'
}
}
};
LinkedIn-Specific Data Layer
Recommended Structure
For LinkedIn Ads tracking:
window.linkedInData = {
page: {
type: '', // 'home', 'product', 'checkout', 'confirmation'
category: ''
},
user: {
// Don't include PII
isLoggedIn: false,
accountType: '' // 'free', 'paid', 'enterprise'
},
conversion: {
id: null,
value: null,
currency: 'USD',
type: '' // 'lead', 'purchase', 'signup'
},
product: {
id: '',
name: '',
category: '',
price: null
}
};
Implementation on Different Pages
Homepage:
window.linkedInData = {
page: {
type: 'home',
category: 'landing'
}
};
Product Page:
window.linkedInData = {
page: {
type: 'product',
category: 'enterprise-software'
},
product: {
id: 'PROD-001',
name: 'Enterprise Plan',
category: 'subscription',
price: 99.99
}
};
Conversion Page:
window.linkedInData = {
page: {
type: 'confirmation',
category: 'purchase'
},
conversion: {
id: 1234567, // LinkedIn Conversion ID
value: 249.99,
currency: 'USD',
type: 'purchase'
}
};
Integration with Google Tag Manager
Setup Data Layer Variables
Step 1: Create Variables
In GTM, create Data Layer Variables:
- Navigate to Variables > New
- Variable Type: Data Layer Variable
- Configure:
Variable Name: DL - Transaction Total Data Layer Variable Name: transaction.total
Variable Name: DL - Transaction Currency Data Layer Variable Name: transaction.currency
Variable Name: DL - Transaction ID Data Layer Variable Name: transaction.id
Step 2: Push Data on Conversion
On confirmation page, before GTM container loads or via dataLayer.push:
<script>
// Initialize data layer before GTM
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'purchase',
'transaction': {
'id': 'ORDER_12345',
'total': 249.99,
'currency': 'USD'
}
});
</script>
<!-- Google Tag Manager container code here -->
Step 3: Create LinkedIn Conversion Tag
Tag Configuration:
- Tag Type: Custom HTML
- Code:
<script>
window.lintrk('track', {
conversion_id: 1234567,
value: {{DL - Transaction Total}},
currency: '{{DL - Transaction Currency}}'
});
</script>
Trigger:
- Trigger Type: Custom Event
- Event Name: purchase
Step 4: Test
- GTM Preview mode
- Complete purchase
- Verify data layer populated
- Check LinkedIn tag fires with correct values
E-commerce Data Layer
Enhanced E-commerce Structure
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'purchase',
'ecommerce': {
'purchase': {
'actionField': {
'id': 'ORDER_12345',
'revenue': '249.99',
'tax': '20.00',
'shipping': '10.00',
'coupon': 'SUMMER20'
},
'products': [{
'name': 'Enterprise Plan',
'id': 'ENT-001',
'price': '219.99',
'brand': 'YourBrand',
'category': 'Software/SaaS',
'quantity': 1
}]
}
}
});
LinkedIn Tag Using E-commerce Data
GTM Variables:
- Variable Name: DL - Ecommerce Revenue
- Data Layer Variable Name: ecommerce.purchase.actionField.revenue
Tag Code:
<script>
window.lintrk('track', {
conversion_id: 1234567,
value: parseFloat({{DL - Ecommerce Revenue}}),
currency: 'USD'
});
</script>
Lead Generation Data Layer
Lead Form Submission
// On successful form submission
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'formSubmit',
'formType': 'demo_request',
'formName': 'Product Demo Form',
'leadValue': 50.00, // Average lead value
'industry': 'Technology', // Non-PII categorization
'companySize': '100-500' // Range, not specific
});
LinkedIn Conversion Tag
GTM Variable:
- DL - Lead Value
- Data Layer Variable: leadValue
Tag:
<script>
window.lintrk('track', {
conversion_id: 2222222,
value: {{DL - Lead Value}},
currency: 'USD'
});
</script>
Trigger:
- Event Name: formSubmit
- Form Type equals: demo_request
Dynamic Conversion Selection
Multiple Conversion Types
// Define conversion mappings
var linkedInConversions = {
'demo_request': 1111111,
'trial_signup': 2222222,
'purchase': 3333333,
'download': 4444444
};
// On conversion event
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'conversion',
'conversionType': 'trial_signup', // Dynamic
'conversionValue': 25.00
});
GTM Implementation
Custom JavaScript Variable: Get Conversion ID
function() {
var conversions = {
'demo_request': 1111111,
'trial_signup': 2222222,
'purchase': 3333333,
'download': 4444444
};
var type = {{DL - Conversion Type}};
return conversions[type] || 0;
}
Tag:
<script>
var conversionId = {{CJS - LinkedIn Conversion ID}};
var conversionValue = {{DL - Conversion Value}};
if (conversionId > 0) {
window.lintrk('track', {
conversion_id: conversionId,
value: conversionValue,
currency: 'USD'
});
}
</script>
Server-Side Data Layer Population
PHP Example
<?php
// Order confirmation page
$order = getOrderDetails(); // Your function
// Calculate totals
$subtotal = $order['subtotal'];
$tax = $order['tax'];
$shipping = $order['shipping'];
$total = $subtotal + $tax + $shipping;
?>
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'purchase',
'transaction': {
'id': '<?php echo $order['id']; ?>',
'total': <?php echo $total; ?>,
'subtotal': <?php echo $subtotal; ?>,
'tax': <?php echo $tax; ?>,
'shipping': <?php echo $shipping; ?>,
'currency': 'USD'
}
});
</script>
Node.js/Express Example
// Confirmation route
app.get('/order/confirmation/:orderId', async (req, res) => {
const order = await getOrder(req.params.orderId);
res.render('confirmation', {
order: order,
dataLayer: {
event: 'purchase',
transaction: {
id: order.id,
total: order.total,
currency: 'USD'
}
}
});
});
Template (EJS):
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push(<%- JSON.stringify(dataLayer) %>);
</script>
Python/Django Example
# views.py
def order_confirmation(request, order_id):
order = Order.objects.get(id=order_id)
context = {
'order': order,
'data_layer': {
'event': 'purchase',
'transaction': {
'id': str(order.id),
'total': float(order.total),
'currency': 'USD'
}
}
}
return render(request, 'confirmation.html', context)
Template:
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({{ data_layer|safe }});
</script>
Single Page Application (SPA) Data Layer
React Example
import { useEffect } from 'react';
function OrderConfirmation({ order }) {
useEffect(() => {
// Push to data layer when component mounts
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'purchase',
transaction: {
id: order.id,
total: order.total,
currency: 'USD'
}
});
// Track LinkedIn conversion
if (typeof window.lintrk === 'function') {
window.lintrk('track', {
conversion_id: 1234567,
value: order.total,
currency: 'USD'
});
}
}, [order]);
return <div>Thank you for your order!</div>;
}
Vue.js Example
export default {
name: 'OrderConfirmation',
props: ['order'],
mounted() {
// Push to data layer
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'purchase',
transaction: {
id: this.order.id,
total: this.order.total,
currency: 'USD'
}
});
// Track conversion
if (typeof window.lintrk === 'function') {
window.lintrk('track', {
conversion_id: 1234567,
value: this.order.total,
currency: 'USD'
});
}
}
}
Angular Example
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-order-confirmation',
templateUrl: './order-confirmation.component.html'
})
export class OrderConfirmationComponent implements OnInit {
order: any;
ngOnInit() {
// Push to data layer
(window as any).dataLayer = (window as any).dataLayer || [];
(window as any).dataLayer.push({
event: 'purchase',
transaction: {
id: this.order.id,
total: this.order.total,
currency: 'USD'
}
});
// Track conversion
if (typeof (window as any).lintrk === 'function') {
(window as any).lintrk('track', {
conversion_id: 1234567,
value: this.order.total,
currency: 'USD'
});
}
}
}
Privacy and Compliance
What NOT to Include in Data Layer
Never include Personally Identifiable Information (PII):
- Email addresses
- Phone numbers
- Full names
- Street addresses
- Social Security Numbers
- Credit card information
What's Safe:
- ✓ Transaction amounts
- ✓ Product categories
- ✓ User types (categorized)
- ✓ Order IDs (anonymized)
- ✓ Timestamps
- ✓ Session IDs
Hashing Sensitive Data
If you must reference user data:
// Hash email before including
async function hashEmail(email) {
const encoder = new TextEncoder();
const data = encoder.encode(email.toLowerCase());
const hash = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// Usage
const hashedEmail = await hashEmail('user@example.com');
window.dataLayer.push({
'event': 'conversion',
'userHash': hashedEmail // Hashed, not raw
});
Debugging Data Layer
Console Inspection
// View entire data layer
console.log('Data Layer:', window.dataLayer);
// View specific values
console.log('Transaction Total:', window.dataLayer[0].transaction.total);
// Monitor pushes
var originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
console.log('Data Layer Push:', arguments[0]);
return originalPush.apply(window.dataLayer, arguments);
};
GTM Preview Mode
- Enable GTM Preview
- Navigate to page
- Click on event in debugger
- View "Data Layer" tab
- Inspect variables and values
Browser Extension
Google Tag Assistant:
- Shows data layer state
- Tracks pushes
- Validates structure
Best Practices
Structure
- Initialize Early - Create data layer before tag managers
- Consistent Naming - Use camelCase or snake_case consistently
- Logical Hierarchy - Group related data
- Document Structure - Maintain data layer specification
- Version Control - Track changes to data layer schema
Implementation
- Server-Side When Possible - More reliable than client-side
- Validate Data - Ensure correct types (numbers not strings)
- Handle Missing Data - Provide defaults or nulls
- Test Thoroughly - Verify all conversion scenarios
- Monitor Errors - Log data layer issues
Performance
- Push Asynchronously - Don't block page rendering
- Minimize Size - Only include necessary data
- Avoid Loops - Don't repeatedly push same data
- Clean Up - Remove temporary data after use
Privacy
- No PII - Never include personally identifiable information
- Hash When Necessary - Use one-way hashing for references
- Respect Consent - Only push data with user permission
- Regular Audits - Review what data is collected
- Document Privacy - Maintain data flow documentation
Troubleshooting
Data Layer Not Defined
Issue: Cannot read property 'push' of undefined
Solution:
// Always initialize
window.dataLayer = window.dataLayer || [];
dataLayer.push({...});
Values Not Passing to GTM
Check:
- Variable name matches data layer key exactly (case-sensitive)
- Data layer push occurs before tag fires
- Variable version set to "Version 2"
- Data layer reset between events
Type Errors
Issue: Value passed as string instead of number
Solution:
// Ensure numeric types
dataLayer.push({
'transaction': {
'total': parseFloat(orderTotal), // Convert to number
'currency': 'USD' // String is OK
}
});
Advanced Patterns
State Management
// Maintain state across page
window.appState = window.appState || {
user: {},
session: {},
cart: {}
};
// Update and push
window.appState.cart.total = 99.99;
window.dataLayer.push({
'event': 'cartUpdate',
'cart': window.appState.cart
});
Event Queuing
// Queue events until consent
window.eventQueue = window.eventQueue || [];
function trackEvent(event) {
if (userHasConsent()) {
window.dataLayer.push(event);
} else {
window.eventQueue.push(event);
}
}
// Process queue after consent
function processEventQueue() {
window.eventQueue.forEach(event => {
window.dataLayer.push(event);
});
window.eventQueue = [];
}
Next Steps
After implementing data layer:
- Test All Conversion Paths - Verify data populates correctly
- Document Schema - Maintain data layer specification
- Train Team - Educate developers and marketers
- Monitor Quality - Regular data validation
- Iterate and Improve - Refine based on needs
Additional Resources
- Event Tracking - LinkedIn conversion implementation
- GTM Data Layer Documentation
- Cross-Domain Tracking - Multi-site data layer
- Server-Side Implementation - Alternative approaches