Setup Overview
Event tracking in Hotjar requires both client-side implementation and configuration in the Hotjar dashboard. This guide covers the complete setup process from defining your event taxonomy to validating that events are firing correctly.
Implementation Planning
Before writing code, plan your event tracking strategy.
Define Your Event Taxonomy
Create a structured list of events you want to track:
| Event Category | Event Name | Purpose | Trigger |
|---|---|---|---|
| User Actions | signup_button_clicked |
Track signup intent | Button click |
| Forms | checkout_form_started |
Monitor form engagement | First field focus |
| Forms | checkout_form_submitted |
Measure form completion | Form submit |
| E-commerce | add_to_cart |
Track product interest | Add to cart button |
| E-commerce | purchase_completed |
Measure conversions | Thank you page load |
| Content | video_played |
Understand engagement | Video play button |
| Errors | payment_failed |
Identify friction | Error state |
Event Naming Convention
Establish a consistent naming pattern:
Recommended Format:
[category]_[action]_[object]
Examples:
- form_submitted_checkout
- button_clicked_signup
- video_played_product_demo
- error_displayed_payment
Guidelines:
- Use
snake_case - Be descriptive but concise
- Group related events with prefixes
- Avoid dynamic values in names
Implementation Methods
Method 1: Direct JavaScript Implementation
Best for developers with direct access to site code.
Basic Event Tracking
// Wait for Hotjar to load
function trackHotjarEvent(eventName) {
if (typeof hj !== 'undefined') {
hj('event', eventName);
} else {
console.warn('Hotjar not loaded, event not tracked:', eventName);
}
}
// Track button click
document.getElementById('signup-button').addEventListener('click', function() {
trackHotjarEvent('signup_button_clicked');
});
Form Event Tracking
const form = document.querySelector('#checkout-form');
// Track form start (first interaction)
form.addEventListener('focus', function(e) {
if (e.target.matches('input, select, textarea') && !form.dataset.hjStarted) {
form.dataset.hjStarted = 'true';
trackHotjarEvent('checkout_form_started');
}
}, true);
// Track form submission
form.addEventListener('submit', function(e) {
trackHotjarEvent('checkout_form_submitted');
});
// Track form abandonment
let formStarted = false;
form.addEventListener('focus', function(e) {
if (e.target.matches('input, select, textarea')) {
formStarted = true;
}
}, true);
window.addEventListener('beforeunload', function() {
if (formStarted && !form.dataset.submitted) {
trackHotjarEvent('checkout_form_abandoned');
}
});
form.addEventListener('submit', function() {
form.dataset.submitted = 'true';
});
E-commerce Event Tracking
// Add to cart
function addToCart(productId, productName) {
// Your add-to-cart logic here
trackHotjarEvent('add_to_cart');
console.log('Product added:', productName);
}
// Remove from cart
function removeFromCart(productId) {
// Your remove-from-cart logic here
trackHotjarEvent('remove_from_cart');
}
// Checkout started
if (window.location.pathname === '/checkout') {
trackHotjarEvent('checkout_started');
}
// Purchase completed
if (window.location.pathname === '/thank-you') {
trackHotjarEvent('purchase_completed');
}
Video Tracking
const video = document.getElementById('product-video');
// Video play
video.addEventListener('play', function() {
trackHotjarEvent('video_played_product_demo');
});
// Video completion (watched 90%+)
video.addEventListener('timeupdate', function() {
const percentWatched = (video.currentTime / video.duration) * 100;
if (percentWatched >= 90 && !video.dataset.hjCompleted) {
video.dataset.hjCompleted = 'true';
trackHotjarEvent('video_completed_product_demo');
}
});
Error Tracking
// Track JavaScript errors
window.addEventListener('error', function(e) {
trackHotjarEvent('javascript_error_occurred');
console.error('Error tracked:', e.message);
});
// Track form validation errors
function showFormError(fieldName) {
trackHotjarEvent('form_validation_error');
console.log('Validation error on:', fieldName);
}
// Track API errors
fetch('/api/checkout')
.then(response => {
if (!response.ok) {
trackHotjarEvent('api_error_checkout');
}
return response.json();
})
.catch(error => {
trackHotjarEvent('api_error_checkout');
console.error('API error:', error);
});
Method 2: Google Tag Manager Implementation
Best for teams that prefer managing tracking via GTM.
Setup Overview
- Push events to data layer
- Create GTM triggers for each event
- Create Hotjar event tags
- Test and publish
Data Layer Implementation
// Generic event tracking function
function trackEvent(eventName, eventData = {}) {
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': eventName,
...eventData
});
}
// Track button click
document.getElementById('signup-button').addEventListener('click', function() {
trackEvent('signup_button_clicked', {
'button_location': 'homepage_hero',
'button_text': this.textContent
});
});
// Track form submission
document.querySelector('#checkout-form').addEventListener('submit', function() {
trackEvent('checkout_form_submitted', {
'form_fields': this.querySelectorAll('input').length
});
});
// Track add to cart
function addToCart(product) {
trackEvent('add_to_cart', {
'product_id': product.id,
'product_name': product.name,
'product_price': product.price
});
}
GTM Configuration
Step 1: Create Trigger
- In GTM, go to Triggers > New
- Trigger Type: Custom Event
- Event name:
signup_button_clicked(matches data layer event) - Save
Step 2: Create Hotjar Event Tag
- Go to Tags > New
- Tag Type: Custom HTML
- Code:
<script>
if (window.hj) {
hj('event', 'signup_button_clicked');
}
</script>
- Triggering: Select your custom event trigger
- Save
Step 3: Test
- Click Preview in GTM
- Navigate to your site
- Trigger the event (click the button)
- Verify in GTM Debug that tag fires
- Check browser console for Hotjar confirmation
Step 4: Publish
Once validated, publish your GTM container.
Method 3: Segment Integration
Best for teams already using Segment.
Implementation
// Segment handles routing to Hotjar automatically
analytics.track('Signup Button Clicked', {
location: 'homepage',
button_text: 'Start Free Trial'
});
// Translates to:
// hj('event', 'Signup Button Clicked');
Note: Event properties are not passed to Hotjar. Use descriptive event names.
Single Page Application (SPA) Setup
React Implementation
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
// Custom hook for tracking events
export function useHotjarEvent() {
const trackEvent = (eventName) => {
if (window.hj) {
window.hj('event', eventName);
}
};
return trackEvent;
}
// Custom hook for page tracking
export function useHotjarPageTracking() {
const location = useLocation();
useEffect(() => {
if (window.hj) {
window.hj('stateChange', location.pathname);
}
}, [location]);
}
// Usage in component
function SignupButton() {
const trackEvent = useHotjarEvent();
const handleClick = () => {
trackEvent('signup_button_clicked');
// Your signup logic
};
return <button Up</button>;
}
// Usage in App
function App() {
useHotjarPageTracking();
return (
<Router>
{/* Your routes */}
</Router>
);
}
Vue.js Implementation
// In your Vue component
export default {
methods: {
trackEvent(eventName) {
if (window.hj) {
window.hj('event', eventName);
}
},
handleSignupClick() {
this.trackEvent('signup_button_clicked');
// Your signup logic
}
}
};
// In your router
router.afterEach((to) => {
if (window.hj) {
window.hj('stateChange', to.path);
}
});
Angular Implementation
// Event tracking service
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class HotjarService {
trackEvent(eventName: string): void {
if (window['hj']) {
window['hj']('event', eventName);
}
}
trackPageview(path: string): void {
if (window['hj']) {
window['hj']('stateChange', path);
}
}
}
// Usage in component
import { Component } from '@angular/core';
import { HotjarService } from './hotjar.service';
@Component({
selector: 'app-signup',
template: `<button (click)="handleSignup()">Sign Up</button>`
})
export class SignupComponent {
constructor(private hotjar: HotjarService) {}
handleSignup(): void {
this.hotjar.trackEvent('signup_button_clicked');
// Your signup logic
}
}
// Route tracking in app component
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
export class AppComponent {
constructor(
private router: Router,
private hotjar: HotjarService
) {
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe((event: NavigationEnd) => {
this.hotjar.trackPageview(event.urlAfterRedirects);
});
}
}
Event Validation & Testing
Pre-Production Testing
Staging Environment Checklist:
- Event fires on expected user action
- Event name is correct
- No JavaScript errors in console
- Hotjar debug mode shows event
- Event appears in Hotjar dashboard (may take a few minutes)
- Event can be used as recording filter
- Event can trigger surveys
- Works across browsers (Chrome, Firefox, Safari, Edge)
- Works on mobile devices
Browser Console Testing
// Enable Hotjar debug mode
localStorage.setItem('hjDebug', 'true');
// Refresh page
// Trigger your event
hj('event', 'test_event');
// Check console output:
// "Hotjar: event tracked: test_event"
Network Monitoring
- Open DevTools (F12)
- Go to Network tab
- Filter by "hotjar"
- Trigger your event
- Look for request to
insights.hotjar.com/api/v2/events - Inspect payload to verify event name
GTM Preview Mode
- Enable Preview in GTM
- Navigate to your site
- Trigger the event
- In GTM Debug panel:
- Verify data layer push appears
- Check that trigger fires
- Confirm Hotjar tag executes
- View Variables to see event data
Hotjar Dashboard Validation
- Go to Hotjar dashboard
- Navigate to Recordings
- Click Filters
- Add filter: Event > Select your event name
- Wait a few minutes for data to process
- Verify recordings appear with your event
Using Events in Hotjar
Filtering Recordings by Event
- Go to Recordings in Hotjar
- Click Filters button
- Select Event
- Choose the event name(s) you want to filter by
- Apply filter
Now you'll only see recordings where users triggered that specific event.
Triggering Recordings with Events
Configure recordings to only capture sessions where specific events occur:
- Go to Recordings > Create new or edit existing
- Under Advanced filters, select Track only sessions where:
- Choose Event happens
- Enter your event name
- Save
This helps conserve recording quota by only capturing relevant sessions.
Triggering Surveys with Events
Display surveys at key moments using events:
- Go to Surveys > Create new or edit existing
- Under Targeting, select When
- Choose Event happens
- Enter your event name
- Configure survey content and design
- Save and activate
Example use cases:
- Show NPS survey after
purchase_completed - Ask for feedback after
onboarding_finished - Trigger exit survey when
subscription_cancelled
Common Implementation Patterns
Scroll Depth Tracking
let scrollTracked = {
25: false,
50: false,
75: false,
100: false
};
window.addEventListener('scroll', function() {
const scrollPercent = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
Object.keys(scrollTracked).forEach(threshold => {
if (scrollPercent >= parseInt(threshold) && !scrollTracked[threshold]) {
scrollTracked[threshold] = true;
trackHotjarEvent(`scroll_${threshold}_percent`);
}
});
});
Time on Page Tracking
// Track users who spend significant time on page
setTimeout(() => {
trackHotjarEvent('spent_30_seconds_on_page');
}, 30000);
setTimeout(() => {
trackHotjarEvent('spent_60_seconds_on_page');
}, 60000);
Exit Intent Tracking
let exitIntentFired = false;
document.addEventListener('mouseleave', function(e) {
if (e.clientY < 0 && !exitIntentFired) {
exitIntentFired = true;
trackHotjarEvent('exit_intent_detected');
}
});
Rage Click Detection
let clickCount = 0;
let clickTimer;
document.addEventListener('click', function(e) {
clickCount++;
clearTimeout(clickTimer);
if (clickCount >= 3) {
trackHotjarEvent('rage_click_detected');
clickCount = 0;
}
clickTimer = setTimeout(() => {
clickCount = 0;
}, 1000);
});
Best Practices
Event Tracking
Do:
- Track meaningful business actions
- Use consistent naming conventions
- Test events in staging first
- Document your event taxonomy
- Limit total number of unique events (< 50 recommended)
- Use events to trigger recordings and surveys
Don't:
- Track every possible interaction
- Use dynamic values in event names
- Forget to test cross-browser
- Track without user consent
- Create events you'll never analyze
Performance
Do:
- Load Hotjar asynchronously
- Throttle high-frequency events (scroll, mousemove)
- Use event delegation for dynamic elements
- Check if
hjexists before calling
Don't:
- Track events on every scroll/mousemove
- Block user interactions waiting for events
- Load Hotjar synchronously
Privacy
Do:
- Respect user consent
- Avoid PII in event names
- Update privacy policy
- Provide opt-out mechanism
Don't:
Next Steps:
Additional Resources: