Mixpanel Event Tracking | OpsBlu Docs

Mixpanel Event Tracking

How to implement custom event tracking in Mixpanel. Covers event naming conventions, required and optional parameters, ecommerce events, debugging with.

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 Interaction event with type property
  • Inconsistent properties: Sometimes has price, sometimes cost
  • Missing critical context

Core Event Catalog

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 Started not Checkout
  • 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

  1. Open Mixpanel and navigate to Events > Live View
  2. Filter by your distinct_id or test user
  3. Trigger events in your application
  4. Verify events appear in real-time with correct properties
  5. 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

  1. Be specific: Product Added is better than Button Clicked
  2. Use consistent naming: Decide on Title Case or snake_case and stick to it
  3. Include context: Add properties that answer "what, where, when, why"
  4. Validate data types: Numbers for metrics, strings for dimensions
  5. Deduplicate critical events: Use $insert_id for purchases, signups
  6. Respect privacy: Hash or exclude PII, respect consent preferences
  7. Track revenue: Always include $amount on revenue-generating events
  8. Version your events: Track schema changes in event properties
  9. Test thoroughly: Validate in Live View before production deployment
  10. Document events: Maintain an event catalog with business context
  11. Monitor event volume: Set up alerts for unexpected drops or spikes
  12. Clean up deprecated events: Remove unused events from Lexicon
  13. Use super properties wisely: Only for truly global context
  14. Leverage Lexicon: Define expected properties and data types
  15. Plan for analysis: Design events around the questions you need to answer