Overview
Events are the foundation of Mixpanel's analytics model. Unlike traditional analytics platforms that focus on page views and sessions, Mixpanel centers on tracking specific user actions with rich contextual properties. Every interaction, from button clicks to purchases, is captured as a discrete event with associated metadata.
This approach enables powerful behavioral analysis, funnel optimization, retention tracking, and user cohort segmentation based on actual product usage rather than simple page navigation.
Mixpanel Event Model
Event Anatomy
Every Mixpanel event consists of:
mixpanel.track('Event Name', {
// Event properties (what happened, context)
'Property Name': 'Property Value',
'Another Property': 123,
// Super properties (automatically added to every event)
// These are registered separately with mixpanel.register()
// Default properties (automatically captured by Mixpanel)
// $browser, $os, $city, $region, $country, etc.
});
Event Components
| Component | Description | Example |
|---|---|---|
| Event Name | Action the user performed | Product Viewed, Sign Up Completed |
| Event Properties | Context-specific metadata | Product ID, Category, Price |
| Super Properties | Properties sent with every event | User Plan, App Version |
| Default Properties | Auto-captured by Mixpanel SDK | $browser, $os, $city |
| User Identity | distinct_id linking event to user | user_12345 or anonymous ID |
| Timestamp | When the event occurred | 2024-03-20T14:30:00Z |
Event Philosophy
Good event design:
- Specific, explicit action:
Purchase Completed,Video Played - Clear, unambiguous name
- Focused purpose with relevant properties
- Consistent property structure across similar events
Poor event design:
- Generic names:
Button Clicked,Action Performed - Overloaded events: One
Interactionevent with type property - Inconsistent properties: Sometimes has
price, sometimescost - Missing critical context
Core Event Catalog
Navigation Events
Track page and screen views for journey analysis:
// Web page view
mixpanel.track('Page Viewed', {
'Page Name': 'Product Detail - Wireless Headphones',
'Page Type': 'product',
'URL': window.location.href,
'Referrer': document.referrer,
'Category': 'Electronics',
'Subcategory': 'Audio'
});
// Mobile screen view
mixpanel.track('Screen Viewed', {
'Screen Name': 'ProductDetail',
'Product ID': 'SKU-12345',
'Previous Screen': 'ProductList'
});
// SPA navigation
mixpanel.track('Page Viewed', {
'Page Name': router.currentRoute.name,
'Page Path': router.currentRoute.path,
'Route Params': JSON.stringify(router.currentRoute.params)
});
Ecommerce Events
Product Discovery
// Product list viewed
mixpanel.track('Product List Viewed', {
'Category': 'Electronics',
'Subcategory': 'Headphones',
'Filter Applied': 'price_low_to_high',
'Search Term': '',
'Product Count': 24
});
// Product viewed
mixpanel.track('Product Viewed', {
'Product ID': 'SKU-12345',
'Product Name': 'Wireless Headphones',
'Category': 'Electronics',
'Brand': 'AudioTech',
'Price': 79.99,
'Sale Price': 69.99,
'Currency': 'USD',
'In Stock': true,
'Position': 3, // Position in list
'List Name': 'Search Results'
});
Cart Management
// Add to cart
mixpanel.track('Product Added', {
'Product ID': 'SKU-12345',
'Product Name': 'Wireless Headphones',
'Category': 'Electronics',
'Brand': 'AudioTech',
'Price': 69.99,
'Quantity': 1,
'Currency': 'USD',
'Cart Total': 229.97,
'Cart Item Count': 3
});
// Remove from cart
mixpanel.track('Product Removed', {
'Product ID': 'SKU-12345',
'Product Name': 'Wireless Headphones',
'Quantity': 1,
'Reason': 'user_action', // or 'out_of_stock', 'price_change'
'Cart Total': 160.00,
'Cart Item Count': 2
});
// Cart viewed
mixpanel.track('Cart Viewed', {
'Cart Total': 229.97,
'Item Count': 3,
'Currency': 'USD',
'Has Coupon': true,
'Coupon Code': 'SAVE10'
});
Checkout Flow
// Checkout started
mixpanel.track('Checkout Started', {
'Cart Total': 229.97,
'Item Count': 3,
'Currency': 'USD',
'Products': ['SKU-12345', 'SKU-67890'],
'Coupon Code': 'SAVE10',
'Discount Amount': 10.00
});
// Shipping info added
mixpanel.track('Shipping Info Added', {
'Shipping Method': 'ground',
'Shipping Cost': 5.99,
'Estimated Delivery': '2024-03-25',
'Country': 'US',
'State': 'CA'
});
// Payment info added
mixpanel.track('Payment Info Added', {
'Payment Method': 'credit_card',
'Card Type': 'visa',
'Billing Country': 'US'
});
// Purchase completed
mixpanel.track('Purchase Completed', {
'$amount': 235.96, // Special Mixpanel revenue property
'Order ID': 'ORD-2024-98765',
'Revenue': 235.96,
'Tax': 18.00,
'Shipping': 5.99,
'Discount': 10.00,
'Currency': 'USD',
'Item Count': 3,
'Products': ['SKU-12345', 'SKU-67890', 'SKU-11111'],
'Payment Method': 'credit_card',
'Coupon Code': 'SAVE10',
'Is First Purchase': false
});
User Lifecycle Events
// Sign up flow
mixpanel.track('Signup Started', {
'Source': 'homepage_cta',
'Signup Method': 'email' // or 'google', 'facebook', 'apple'
});
mixpanel.track('Signup Completed', {
'Signup Method': 'email',
'Source': 'homepage_cta',
'Plan Selected': 'free',
'Account Type': 'personal'
});
// Login
mixpanel.track('Login', {
'Method': 'email',
'Platform': 'web',
'Remember Me': true
});
// Subscription events
mixpanel.track('Trial Started', {
'Plan': 'premium',
'Trial Duration': 14,
'Source': 'pricing_page'
});
mixpanel.track('Subscription Upgraded', {
'Previous Plan': 'basic',
'New Plan': 'premium',
'Billing Cycle': 'monthly',
'Price': 29.99,
'Currency': 'USD'
});
mixpanel.track('Subscription Cancelled', {
'Plan': 'premium',
'Cancellation Reason': 'too_expensive',
'Days Active': 127,
'Lifetime Value': 380.00
});
Feature Engagement Events
// Feature usage
mixpanel.track('Feature Used', {
'Feature Name': 'advanced_search',
'Feature Category': 'search',
'Session Duration': 45, // seconds
'Success': true
});
// Search
mixpanel.track('Search Performed', {
'Search Term': 'wireless headphones',
'Search Type': 'product',
'Results Count': 24,
'Filter Applied': false,
'Time to First Result': 234 // milliseconds
});
// Content engagement
mixpanel.track('Article Read', {
'Article ID': 'blog-123',
'Article Title': 'Top 10 Headphones',
'Category': 'buying_guide',
'Read Duration': 180, // seconds
'Scroll Depth': 85, // percentage
'Completed': true
});
// Video engagement
mixpanel.track('Video Played', {
'Video ID': 'vid-456',
'Video Title': 'Product Demo',
'Video Duration': 120,
'Position': 0
});
mixpanel.track('Video Progress', {
'Video ID': 'vid-456',
'Video Title': 'Product Demo',
'Percent Complete': 50,
'Current Time': 60
});
mixpanel.track('Video Completed', {
'Video ID': 'vid-456',
'Video Title': 'Product Demo',
'Watch Duration': 120,
'Completion Rate': 100
});
Social and Sharing Events
// Share
mixpanel.track('Content Shared', {
'Content Type': 'product',
'Content ID': 'SKU-12345',
'Share Method': 'facebook',
'Share Source': 'product_detail_page'
});
// Review/Rating
mixpanel.track('Review Submitted', {
'Product ID': 'SKU-12345',
'Rating': 5,
'Has Written Review': true,
'Review Length': 234,
'Verified Purchase': true
});
Error and Exception Events
// Form errors
mixpanel.track('Form Error', {
'Form Name': 'checkout',
'Field Name': 'credit_card_number',
'Error Type': 'invalid_format',
'Error Message': 'Invalid card number'
});
// Application errors
mixpanel.track('Error Occurred', {
'Error Type': 'api_failure',
'Error Code': 500,
'Error Message': 'Failed to load product details',
'Page': window.location.pathname,
'Severity': 'high'
});
Event Properties Best Practices
Property Naming Conventions
Use consistent casing:
// GOOD - Use Title Case for multi-word properties
mixpanel.track('Purchase Completed', {
'Product ID': 'SKU-12345',
'Product Name': 'Wireless Headphones',
'Order Total': 99.99
});
// ACCEPTABLE - Use snake_case if consistent across all events
mixpanel.track('purchase_completed', {
'product_id': 'SKU-12345',
'product_name': 'Wireless Headphones',
'order_total': 99.99
});
// AVOID - Inconsistent casing
mixpanel.track('Purchase Completed', {
'productID': 'SKU-12345', // camelCase
'Product_Name': 'Wireless Headphones', // Mixed
'order-total': 99.99 // kebab-case
});
Event Name Guidelines:
- Use Title Case for event names:
Purchase Completed,Video Played - Past tense for completed actions:
Product Added,Form Submitted - Present tense for ongoing states:
Video Playing,Page Viewing(if tracking states) - Be specific:
Checkout StartednotCheckout - Maximum 255 characters (practical limit: 40-50 for readability)
Property Data Types
Mixpanel supports specific data types - ensure consistency:
mixpanel.track('Purchase Completed', {
// String
'Product Name': 'Wireless Headphones',
'Category': 'Electronics',
// Number (for aggregations, comparisons)
'Price': 79.99,
'Quantity': 2,
'Item Count': 3,
// Boolean
'In Stock': true,
'Is First Purchase': false,
'Has Coupon': true,
// Date (ISO 8601 format)
'Purchase Date': new Date().toISOString(),
'Trial End Date': '2024-04-20T23:59:59Z',
// List (array of strings or numbers)
'Product IDs': ['SKU-12345', 'SKU-67890'],
'Categories': ['Electronics', 'Audio'],
// Object (use sparingly, flatten when possible)
'Custom Data': {
'key': 'value'
}
});
Special Mixpanel Properties
Mixpanel reserves certain property names with special handling:
// Revenue tracking
mixpanel.track('Purchase Completed', {
'$amount': 99.99 // Used in revenue reports
});
// Insert ID for deduplication
mixpanel.track('Purchase Completed', {
'$insert_id': 'ORD-2024-98765', // Prevents duplicate event processing
'Order ID': 'ORD-2024-98765'
});
// Time override (use server time)
mixpanel.track('Purchase Completed', {
'$time': Date.now() // Milliseconds since epoch
});
// User properties (use with people.set)
mixpanel.people.set({
'$email': 'user@example.com',
'$first_name': 'John',
'$last_name': 'Doe',
'$phone': '+1-555-0100',
'$created': '2024-01-15T10:30:00Z'
});
Super Properties
Properties that apply to all events:
// Register super properties (persist in localStorage)
mixpanel.register({
'App Version': '2.1.0',
'Environment': 'production',
'User Plan': 'premium',
'A/B Test Variant': 'variant_b',
'Platform': 'web'
});
// Register once (only set if not already set)
mixpanel.register_once({
'Initial Referrer': document.referrer,
'Initial Landing Page': window.location.href,
'Signup Date': new Date().toISOString()
});
// Unregister a super property
mixpanel.unregister('A/B Test Variant');
// Clear all super properties
mixpanel.clear_super_properties();
When to Use Super Properties
| Use Case | Super Property? | Why |
|---|---|---|
| User account type | Yes | Applies to all events |
| App version | Yes | Consistent across session |
| A/B test variant | Yes | Segment all behavior |
| Product ID | No | Specific to product events |
| Search term | No | Specific to search events |
| Page name | No | Changes frequently |
| Cart total | No | Dynamic state |
Deduplication with $insert_id
Prevent duplicate event processing:
// Client-side: Use order ID for purchase events
mixpanel.track('Purchase Completed', {
'$insert_id': `order_${orderId}`,
'Order ID': orderId,
'$amount': 99.99
});
// Server-side: Generate unique ID per event
const insertId = `${userId}_${eventName}_${timestamp}`;
mixpanel.track(userId, 'Feature Used', {
'$insert_id': insertId,
'Feature Name': 'advanced_search'
});
// For retries or duplicate submissions
function trackWithRetry(eventName, properties) {
const insertId = `${properties['Order ID']}_${Date.now()}`;
mixpanel.track(eventName, {
...properties,
'$insert_id': insertId
});
}
Event Implementation Patterns
Click Tracking
// Button click
document.getElementById('cta-button').addEventListener('click', function() {
mixpanel.track('CTA Clicked', {
'Button Text': this.textContent,
'Button Location': 'homepage_hero',
'URL': window.location.href
});
});
// Link tracking
document.querySelectorAll('a[data-track]').forEach(link => {
link.addEventListener('click', function(e) {
const eventName = this.dataset.track;
const href = this.href;
mixpanel.track(eventName, {
'Link Text': this.textContent,
'Link URL': href,
'Link Type': this.hostname === window.location.hostname ? 'internal' : 'external'
});
// For external links, ensure event sends before navigation
if (this.hostname !== window.location.hostname) {
e.preventDefault();
setTimeout(() => {
window.location.href = href;
}, 300);
}
});
});
Form Tracking
// Form submission
document.getElementById('signup-form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
mixpanel.track('Signup Form Submitted', {
'Form Name': 'signup',
'Email Domain': formData.get('email').split('@')[1],
'Plan Selected': formData.get('plan'),
'Newsletter Opt In': formData.get('newsletter') === 'on'
});
// Continue with form submission
this.submit();
});
// Form field errors
document.querySelectorAll('input').forEach(input => {
input.addEventListener('blur', function() {
if (!this.validity.valid) {
mixpanel.track('Form Field Error', {
'Form Name': this.form.id,
'Field Name': this.name,
'Error Type': this.validationMessage
});
}
});
});
Scroll Tracking
let maxScroll = 0;
let scrollCheckpoints = [25, 50, 75, 90, 100];
let firedCheckpoints = [];
window.addEventListener('scroll', function() {
const scrollPercent = Math.round(
(window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100
);
if (scrollPercent > maxScroll) {
maxScroll = scrollPercent;
}
scrollCheckpoints.forEach(checkpoint => {
if (scrollPercent >= checkpoint && !firedCheckpoints.includes(checkpoint)) {
mixpanel.track('Page Scrolled', {
'Scroll Depth': checkpoint,
'Page': window.location.pathname,
'Page Type': document.body.dataset.pageType
});
firedCheckpoints.push(checkpoint);
}
});
});
// Send max scroll on page unload
window.addEventListener('beforeunload', function() {
if (maxScroll > 0) {
mixpanel.track('Page Exit', {
'Max Scroll Depth': maxScroll,
'Time on Page': Date.now() - pageLoadTime
});
}
});
Time-Based Tracking
// Session time tracking
let sessionStart = Date.now();
window.addEventListener('beforeunload', function() {
const sessionDuration = Math.round((Date.now() - sessionStart) / 1000);
mixpanel.track('Session Ended', {
'Session Duration': sessionDuration,
'Pages Viewed': pageViewCount,
'Events Tracked': eventCount
});
});
// Feature usage time
let featureStartTime;
function startFeatureTracking(featureName) {
featureStartTime = Date.now();
}
function endFeatureTracking(featureName, success = true) {
const duration = Math.round((Date.now() - featureStartTime) / 1000);
mixpanel.track('Feature Used', {
'Feature Name': featureName,
'Duration': duration,
'Success': success
});
}
Validation and Testing
Development Mode Debugging
// Enable verbose logging
mixpanel.init('YOUR_PROJECT_TOKEN', {
debug: true,
verbose: true
});
// Log all events to console
const originalTrack = mixpanel.track;
mixpanel.track = function(eventName, properties) {
console.group(`[Mixpanel] ${eventName}`);
console.log('Properties:', properties);
console.log('Super Properties:', mixpanel.persistence.props);
console.groupEnd();
return originalTrack.call(this, eventName, properties);
};
Live View Testing
- Open Mixpanel and navigate to Events > Live View
- Filter by your distinct_id or test user
- Trigger events in your application
- Verify events appear in real-time with correct properties
- Check property data types match expectations
Event Validation Checklist
| Check | Expected Result | How to Validate |
|---|---|---|
| Event fires on action | Event appears in Live View | Perform action, check Live View |
| Event name is correct | Matches naming convention | Inspect event in Live View |
| All properties present | No missing required properties | Check property list |
| Property data types correct | Numbers are numeric, not strings | Inspect property values |
| distinct_id consistent | Same ID across events | Filter by distinct_id |
| Super properties attached | Global properties on all events | Check any event's properties |
| $insert_id prevents duplicates | Multiple triggers = one event | Submit twice, check count |
| Timestamps accurate | Time matches actual occurrence | Compare timestamps |
| Revenue property set | $amount on purchase events | Check purchase event |
| No PII in properties | Email/phone hashed or excluded | Review all properties |
Automated Testing
// Jest example
import mixpanel from 'mixpanel-browser';
jest.mock('mixpanel-browser');
describe('Event Tracking', () => {
beforeEach(() => {
mixpanel.track.mockClear();
});
test('tracks purchase event with correct properties', () => {
const order = {
id: 'ORD-123',
total: 99.99,
items: 3
};
trackPurchase(order);
expect(mixpanel.track).toHaveBeenCalledWith('Purchase Completed', {
'Order ID': 'ORD-123',
'$amount': 99.99,
'Item Count': 3,
'$insert_id': 'order_ORD-123'
});
});
test('registers super properties on init', () => {
initializeAnalytics();
expect(mixpanel.register).toHaveBeenCalledWith({
'App Version': expect.any(String),
'Environment': 'production'
});
});
});
Troubleshooting
| Symptom | Likely Cause | Solution |
|---|---|---|
| Events not appearing | Mixpanel not initialized | Check init() called before track() |
| Missing properties | Property undefined when event fires | Verify data availability timing |
| Wrong property data type | String instead of number | Parse values: parseFloat(), parseInt() |
| Duplicate events | No $insert_id | Add unique $insert_id to events |
| Events delayed | Network issues or batching | Check network tab, adjust flush intervals |
| Lost events on navigation | Event not sent before page unload | Use beacon API or add delay |
| Super properties not persisting | localStorage cleared | Use register_once() with fallback |
| distinct_id changing | User not identified | Call identify() after login |
| Events blocked | Ad blocker | Implement first-party proxy |
| Revenue not showing | Missing $amount property | Add $amount to purchase events |
| Property name truncated | Exceeds 255 chars | Shorten property names |
Common Implementation Errors
// WRONG - String instead of number
mixpanel.track('Purchase', {
'Price': '$79.99' // String
});
// CORRECT
mixpanel.track('Purchase', {
'Price': 79.99 // Number
});
// WRONG - Missing $insert_id on critical events
mixpanel.track('Purchase Completed', {
'Order ID': orderId,
'$amount': total
});
// CORRECT
mixpanel.track('Purchase Completed', {
'Order ID': orderId,
'$amount': total,
'$insert_id': `order_${orderId}`
});
// WRONG - Overloading one event
mixpanel.track('Button Clicked', {
'Button Type': 'cta',
'Action': 'signup'
});
// CORRECT - Specific events
mixpanel.track('Signup CTA Clicked', {
'Button Location': 'homepage_hero'
});
Event Tracking Strategy
Decision Matrix: When to Track an Event
| Question | If Yes | If No |
|---|---|---|
| Is this a meaningful user action? | Track it | Don't track |
| Will you analyze this in reports? | Track it | Don't track |
| Does it represent business value? | Track it | Consider skipping |
| Can it inform product decisions? | Track it | Consider skipping |
| Is it a critical conversion step? | Track it | Lower priority |
| Does it happen more than 10x/session? | Consider sampling | Track all |
Event Tracking Priorities
| Priority | Event Type | Examples |
|---|---|---|
| P0 - Critical | Conversion events | Purchase, Signup, Subscription |
| P1 - High | Key engagement | Product View, Add to Cart, Feature Use |
| P2 - Medium | Secondary actions | Search, Share, Review |
| P3 - Low | Minor interactions | Hover, Focus, Tooltip |
Best Practices
- Be specific:
Product Addedis better thanButton Clicked - Use consistent naming: Decide on Title Case or snake_case and stick to it
- Include context: Add properties that answer "what, where, when, why"
- Validate data types: Numbers for metrics, strings for dimensions
- Deduplicate critical events: Use
$insert_idfor purchases, signups - Respect privacy: Hash or exclude PII, respect consent preferences
- Track revenue: Always include
$amounton revenue-generating events - Version your events: Track schema changes in event properties
- Test thoroughly: Validate in Live View before production deployment
- Document events: Maintain an event catalog with business context
- Monitor event volume: Set up alerts for unexpected drops or spikes
- Clean up deprecated events: Remove unused events from Lexicon
- Use super properties wisely: Only for truly global context
- Leverage Lexicon: Define expected properties and data types
- Plan for analysis: Design events around the questions you need to answer