Overview
The data layer is a JavaScript object that stores information about user interactions, page data, and application state. When using Google Tag Manager (GTM), the data layer becomes the bridge between your website and Hotjar, allowing you to trigger events and pass user attributes without hardcoding tracking logic.
Benefits of using a data layer with Hotjar:
- Centralized data management
- No code deployments for tracking changes
- Easier testing and debugging
- Consistent data structure across tools
- Better collaboration between dev and marketing teams
Data Layer Fundamentals
What is the Data Layer?
The data layer is a global JavaScript array (dataLayer) that holds data about the page, user, and events:
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'page_view',
'page_type': 'homepage',
'user_status': 'logged_in'
});
GTM listens to the data layer and fires tags (including Hotjar events) based on what gets pushed.
Data Layer vs Direct Hotjar Calls
Without Data Layer (Direct):
// Hardcoded in your site
hj('event', 'signup_completed');
With Data Layer (GTM):
// Push to data layer
dataLayer.push({
'event': 'signup_completed'
});
// GTM handles calling Hotjar
// No need to touch hj() directly
The GTM approach is more flexible and maintainable.
Initial Data Layer Setup
Prerequisites
- Google Tag Manager installed on your site
- Hotjar tracking configured in GTM (see Install Guide)
Basic Structure
Initialize the data layer before GTM loads:
<!DOCTYPE html>
<html>
<head>
<title>Your Page</title>
<!-- Initialize Data Layer -->
<script>
window.dataLayer = window.dataLayer || [];
// Push page-level data
dataLayer.push({
'page_type': 'homepage',
'content_category': 'marketing',
'user_login_status': 'logged_out'
});
</script>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->
</head>
<body>
<!-- Your content -->
</body>
</html>
Important: Initialize dataLayer before the GTM script loads.
Page-Level Data
Push data about the page when it loads:
Homepage Example
dataLayer.push({
'page_type': 'homepage',
'page_category': 'marketing',
'content_group': 'top_funnel',
'logged_in': false
});
Product Page Example
dataLayer.push({
'page_type': 'product',
'product_id': 'SKU-12345',
'product_name': 'Blue Widget',
'product_price': 29.99,
'product_category': 'Widgets',
'in_stock': true
});
Checkout Page Example
dataLayer.push({
'page_type': 'checkout',
'checkout_step': 'payment',
'cart_value': 149.97,
'cart_items': 3
});
Event Tracking via Data Layer
Basic Event Push
To track an event, push an object with an event key:
dataLayer.push({
'event': 'button_click',
'button_name': 'signup_cta',
'button_location': 'homepage_hero'
});
Form Events
Form Start:
dataLayer.push({
'event': 'form_start',
'form_name': 'contact_us',
'form_location': 'footer'
});
Form Submit:
dataLayer.push({
'event': 'form_submit',
'form_name': 'contact_us',
'form_fields': 5,
'form_completion_time': 45 // seconds
});
Form Error:
dataLayer.push({
'event': 'form_error',
'form_name': 'contact_us',
'error_field': 'email',
'error_type': 'invalid_format'
});
E-commerce Events
Add to Cart:
dataLayer.push({
'event': 'add_to_cart',
'product_id': 'SKU-12345',
'product_name': 'Blue Widget',
'product_price': 29.99,
'quantity': 1
});
Checkout Started:
dataLayer.push({
'event': 'begin_checkout',
'cart_value': 149.97,
'cart_items': 3
});
Purchase Completed:
dataLayer.push({
'event': 'purchase',
'transaction_id': 'TXN-789',
'revenue': 149.97,
'tax': 12.00,
'shipping': 5.99
});
User Interaction Events
Video Play:
dataLayer.push({
'event': 'video_play',
'video_title': 'Product Demo 2024',
'video_duration': 120,
'video_provider': 'youtube'
});
File Download:
dataLayer.push({
'event': 'file_download',
'file_name': 'pricing-guide.pdf',
'file_type': 'pdf',
'file_size': '2.4MB'
});
CTA Click:
dataLayer.push({
'event': 'cta_click',
'cta_text': 'Start Free Trial',
'cta_location': 'pricing_page',
'cta_type': 'button'
});
User Identification via Data Layer
Logged-In User
When a user logs in, push their attributes to the data layer:
// On successful login
dataLayer.push({
'event': 'user_login',
'user_id': 'user_12345',
'user_email': 'user@example.com',
'user_plan': 'premium',
'user_signup_date': '2024-01-15',
'user_role': 'admin'
});
User Attributes
For ongoing pages (after login), include user data in the initial page load:
dataLayer.push({
'user_id': 'user_12345',
'user_plan': 'premium',
'user_account_age_days': 120,
'user_lifetime_value': 599.99,
'user_segment': 'power_user'
});
Single Page Applications (SPAs)
For SPAs, push user data after each route change:
// On route change
dataLayer.push({
'event': 'virtual_pageview',
'page_path': '/dashboard',
'page_title': 'User Dashboard',
'user_id': currentUser.id,
'user_plan': currentUser.plan
});
Configuring GTM to Trigger Hotjar Events
Now that data is flowing into the data layer, configure GTM to send it to Hotjar.
Step 1: Create Data Layer Variables
In GTM, create variables to capture data layer values:
- Go to Variables > New
- Click Variable Configuration
- Choose Data Layer Variable
- Set Data Layer Variable Name (e.g.,
event,button_name,user_id) - Save
Example Variables:
- Variable Name:
DLV - Event| Data Layer Variable Name:event - Variable Name:
DLV - Button Name| Data Layer Variable Name:button_name - Variable Name:
DLV - User ID| Data Layer Variable Name:user_id
Step 2: Create Custom Event Trigger
- Go to Triggers > New
- Choose Trigger Type: Custom Event
- Set Event name to match your data layer event (e.g.,
button_click) - Save
Step 3: Create Hotjar Event Tag
- Go to Tags > New
- Choose Tag Type: Custom HTML
- Add the code:
<script>
if (window.hj) {
hj('event', {{DLV - Event}});
}
</script>
- Set Triggering to your custom event trigger (e.g.,
button_click) - Save
Step 4: Test in Preview Mode
- Click Preview in GTM
- Navigate to your site
- Trigger the event (e.g., click a button)
- In GTM Debug, verify:
- Data layer push appears
- Trigger fires
- Hotjar tag executes
- Check browser console:
localStorage.setItem('hjDebug', 'true')to see Hotjar events
Step 5: Publish
Once validated, publish your GTM container.
Advanced Data Layer Patterns
Dynamic Event Names
If you want Hotjar event names to be dynamic:
// Data layer push
dataLayer.push({
'event': 'user_action',
'action_name': 'signup_completed'
});
GTM Tag:
<script>
if (window.hj && {{DLV - Action Name}}) {
hj('event', {{DLV - Action Name}});
}
</script>
Conditional Event Firing
Only fire Hotjar events for specific user segments:
Data Layer:
dataLayer.push({
'event': 'feature_used',
'feature_name': 'export_data',
'user_plan': 'premium'
});
GTM Trigger:
- Trigger Type: Custom Event
- Event name:
feature_used - This trigger fires on: Some Custom Events
- Fire when:
{{DLV - User Plan}}equalspremium
User Identification via GTM
Send user attributes to Hotjar's Identify API:
Data Layer Push:
dataLayer.push({
'event': 'user_identified',
'user_id': 'user_12345',
'user_email': 'user@example.com',
'user_plan': 'premium',
'user_signup_date': '2024-01-15'
});
GTM Tag (Custom HTML):
<script>
if (window.hj && {{DLV - User ID}}) {
hj('identify', {{DLV - User ID}}, {
'email': {{DLV - User Email}},
'plan': {{DLV - User Plan}},
'signup_date': {{DLV - User Signup Date}}
});
}
</script>
Trigger: Custom Event user_identified
Single Page Application (SPA) Data Layer
Virtual Pageviews
For SPAs, push a virtual pageview on route changes:
// React Router example
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function useDataLayerPageview() {
const location = useLocation();
useEffect(() => {
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'virtual_pageview',
'page_path': location.pathname,
'page_title': document.title
});
}, [location]);
}
GTM Configuration:
- Trigger: Custom Event
virtual_pageview - Tag: Hotjar state change
<script>
if (window.hj) {
hj('stateChange', {{DLV - Page Path}});
}
</script>
SPA Event Tracking
// Vue example
methods: {
trackButtonClick(buttonName) {
dataLayer.push({
'event': 'button_click',
'button_name': buttonName,
'page_path': this.$route.path
});
}
}
Data Layer Best Practices
Naming Conventions
Use consistent naming:
snake_casefor event names:form_submit,video_play- Descriptive keys:
product_namenotpn - Prefixes for clarity:
user_plan,page_type
Avoid:
- Inconsistent casing:
formSubmitvsform_submit - Ambiguous keys:
type,name,data - Special characters:
form-name,user.id
Data Structure
Keep it flat:
dataLayer.push({
'event': 'purchase',
'transaction_id': 'TXN-123',
'revenue': 99.99
});
Avoid nested objects (harder to access in GTM):
dataLayer.push({
'event': 'purchase',
'transaction': {
'id': 'TXN-123',
'revenue': 99.99
}
});
Timing
Push before GTM loads (for page-level data):
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({...});
</script>
<!-- GTM script here -->
Push after user action (for events):
button.addEventListener('click', () => {
dataLayer.push({...});
});
Privacy & Compliance
Do:
- Use user IDs, not emails in event names
- Hash or encrypt PII before pushing
- Respect user consent before tracking
- Document what data you collect
Don't:
- Push credit card numbers, passwords
- Include raw email addresses in events
- Track without user consent
- Push unnecessary sensitive data
Debugging the Data Layer
Chrome Extension: dataLayer Checker
Install dataLayer Inspector Chrome extension to:
- View data layer pushes in real-time
- Inspect variable values
- Debug GTM triggers and tags
Console Commands
// View entire data layer
console.table(dataLayer);
// View latest push
console.log(dataLayer[dataLayer.length - 1]);
// Monitor pushes
const originalPush = dataLayer.push;
dataLayer.push = function() {
console.log('Data layer push:', arguments[0]);
return originalPush.apply(this, arguments);
};
GTM Preview Mode
- Enable Preview in GTM
- Navigate your site
- In Debug panel:
- Summary: See which tags fired
- Variables: Check data layer variable values
- Data Layer: View all pushes in chronological order
Common Issues
Event not firing in Hotjar:
- Check data layer push appears in GTM Debug
- Verify trigger conditions met
- Confirm Hotjar tag fires
- Enable
hjDebugand check console
Variable undefined:
- Ensure data layer key matches variable name exactly
- Check timing (variable might not exist yet)
- Verify data layer push happened before tag fires
Example Implementation
Complete E-commerce Flow
Product Page:
dataLayer.push({
'page_type': 'product',
'product_id': 'SKU-12345',
'product_name': 'Blue Widget',
'product_price': 29.99,
'product_category': 'Widgets',
'user_id': currentUser ? currentUser.id : null,
'user_plan': currentUser ? currentUser.plan : 'guest'
});
Add to Cart Click:
addToCartButton.addEventListener('click', () => {
dataLayer.push({
'event': 'add_to_cart',
'product_id': 'SKU-12345',
'product_name': 'Blue Widget',
'product_price': 29.99,
'quantity': 1
});
});
Checkout Initiated:
checkoutButton.addEventListener('click', () => {
dataLayer.push({
'event': 'begin_checkout',
'cart_value': 149.97,
'cart_items': 3,
'user_id': currentUser.id
});
});
Purchase Completed:
// On thank you page
dataLayer.push({
'event': 'purchase',
'transaction_id': 'TXN-789',
'revenue': 149.97,
'tax': 12.00,
'shipping': 5.99,
'user_id': currentUser.id
});
GTM Setup:
Create triggers and Hotjar event tags for:
add_to_cart→hj('event', 'add_to_cart')begin_checkout→hj('event', 'checkout_started')purchase→hj('event', 'purchase_completed')
Next Steps:
Additional Resources: