Skip to main content

How to Build a Real-Time Dashboard with the Mixpanel API

·APIScout Team
mixpanelanalyticsdashboardtutorialapi integration

How to Build a Real-Time Dashboard with the Mixpanel API

Mixpanel tracks user behavior and lets you query that data via API. This guide shows how to instrument your app with events, then build a custom dashboard that displays funnels, retention, and engagement metrics in real time.

What You'll Build

  • Event tracking (page views, button clicks, signups, purchases)
  • Custom analytics dashboard with charts
  • Funnel analysis (signup → activation → conversion)
  • Retention cohort visualization
  • Real-time event feed

Prerequisites: Node.js 18+, Mixpanel account (free: 20M events/month).

1. Setup

Install

# Server-side tracking
npm install mixpanel

# Client-side tracking
npm install mixpanel-browser

Initialize Server-Side

// lib/mixpanel.ts
import Mixpanel from 'mixpanel';

export const mixpanel = Mixpanel.init(process.env.MIXPANEL_TOKEN!, {
  host: 'api.mixpanel.com',
});

Initialize Client-Side

// lib/mixpanel-client.ts
import mixpanel from 'mixpanel-browser';

mixpanel.init(process.env.NEXT_PUBLIC_MIXPANEL_TOKEN!, {
  track_pageview: true,
  persistence: 'localStorage',
});

export default mixpanel;

2. Track Events

Server-Side Events

// Track signup
mixpanel.track('Sign Up', {
  distinct_id: user.id,
  email: user.email,
  plan: 'free',
  source: 'organic',
});

// Track purchase
mixpanel.track('Purchase', {
  distinct_id: user.id,
  amount: 29.00,
  plan: 'pro',
  currency: 'USD',
});

// Set user profile
mixpanel.people.set(user.id, {
  $email: user.email,
  $name: user.name,
  plan: 'pro',
  signup_date: new Date().toISOString(),
});

Client-Side Events

// components/TrackingProvider.tsx
'use client';
import { useEffect } from 'react';
import { usePathname } from 'next/navigation';
import mixpanel from '@/lib/mixpanel-client';

export function TrackingProvider({ children, userId }: {
  children: React.ReactNode;
  userId?: string;
}) {
  const pathname = usePathname();

  useEffect(() => {
    if (userId) {
      mixpanel.identify(userId);
    }
  }, [userId]);

  useEffect(() => {
    mixpanel.track('Page View', { path: pathname });
  }, [pathname]);

  return <>{children}</>;
}

// Track button clicks
export function trackClick(name: string, props?: Record<string, any>) {
  mixpanel.track('Button Click', { button_name: name, ...props });
}

Event Naming Best Practices

EventPropertiesWhen
Sign Upplan, source, methodUser creates account
Loginmethod (email, google, github)User logs in
Page Viewpath, referrerEvery page load
Feature Usedfeature_name, contextUser engages with feature
Purchaseamount, plan, currencyPayment completed
Subscription Changedfrom_plan, to_planUpgrade/downgrade
Churnreason, days_activeUser cancels

3. Query Data via API

Authentication

Mixpanel uses service account authentication for the Data Export API:

// lib/mixpanel-query.ts
const MIXPANEL_PROJECT_ID = process.env.MIXPANEL_PROJECT_ID!;
const MIXPANEL_SERVICE_ACCOUNT = process.env.MIXPANEL_SERVICE_ACCOUNT!;
const MIXPANEL_SERVICE_SECRET = process.env.MIXPANEL_SERVICE_SECRET!;

const auth = Buffer.from(
  `${MIXPANEL_SERVICE_ACCOUNT}:${MIXPANEL_SERVICE_SECRET}`
).toString('base64');

export async function queryMixpanel(endpoint: string, params: Record<string, string>) {
  const url = new URL(`https://mixpanel.com/api/query${endpoint}`);
  url.searchParams.set('project_id', MIXPANEL_PROJECT_ID);
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));

  const res = await fetch(url.toString(), {
    headers: { Authorization: `Basic ${auth}` },
  });

  return res.json();
}

Query Event Counts

// Get daily signups for the last 30 days
const signupData = await queryMixpanel('/insights', {
  event: JSON.stringify([{ event: 'Sign Up' }]),
  type: 'general',
  unit: 'day',
  interval: '30',
});

Funnel Query

const funnelData = await queryMixpanel('/funnels', {
  funnel_id: 'your_funnel_id',
  from_date: '2026-02-06',
  to_date: '2026-03-08',
  unit: 'week',
});

4. Build the Dashboard

Dashboard API Route

// app/api/dashboard/route.ts
import { NextResponse } from 'next/server';
import { queryMixpanel } from '@/lib/mixpanel-query';

export async function GET() {
  const [signups, purchases, activeUsers] = await Promise.all([
    queryMixpanel('/insights', {
      event: JSON.stringify([{ event: 'Sign Up' }]),
      type: 'general',
      unit: 'day',
      interval: '30',
    }),
    queryMixpanel('/insights', {
      event: JSON.stringify([{ event: 'Purchase' }]),
      type: 'general',
      unit: 'day',
      interval: '30',
    }),
    queryMixpanel('/insights', {
      event: JSON.stringify([{ event: 'Login' }]),
      type: 'unique',
      unit: 'day',
      interval: '7',
    }),
  ]);

  return NextResponse.json({ signups, purchases, activeUsers });
}

Dashboard Component

// app/dashboard/analytics/page.tsx
'use client';
import { useEffect, useState } from 'react';

interface DashboardData {
  signups: any;
  purchases: any;
  activeUsers: any;
}

export default function AnalyticsDashboard() {
  const [data, setData] = useState<DashboardData | null>(null);

  useEffect(() => {
    fetch('/api/dashboard')
      .then(res => res.json())
      .then(setData);
  }, []);

  if (!data) return <div>Loading analytics...</div>;

  return (
    <div className="grid grid-cols-3 gap-6">
      <MetricCard
        title="Signups (30d)"
        value={sumEvents(data.signups)}
        trend={calculateTrend(data.signups)}
      />
      <MetricCard
        title="Purchases (30d)"
        value={sumEvents(data.purchases)}
        trend={calculateTrend(data.purchases)}
      />
      <MetricCard
        title="Active Users (7d)"
        value={sumEvents(data.activeUsers)}
        trend={calculateTrend(data.activeUsers)}
      />

      <div className="col-span-3">
        <h3>Daily Signups</h3>
        {/* Render chart with data.signups time series */}
      </div>
    </div>
  );
}

function MetricCard({ title, value, trend }: {
  title: string;
  value: number;
  trend: number;
}) {
  return (
    <div className="border rounded-lg p-6">
      <p className="text-sm text-gray-500">{title}</p>
      <p className="text-3xl font-bold">{value.toLocaleString()}</p>
      <p className={trend >= 0 ? 'text-green-500' : 'text-red-500'}>
        {trend >= 0 ? '↑' : '↓'} {Math.abs(trend)}%
      </p>
    </div>
  );
}

5. Key Metrics to Track

MetricMixpanel QueryBusiness Value
DAU / MAUUnique "Login" eventsUser engagement
Signup conversionFunnel: Visit → SignupAcquisition efficiency
Activation rateFunnel: Signup → First Value ActionOnboarding quality
Revenue per user"Purchase" events aggregatedMonetization
Retention (Day 1/7/30)Retention report on "Login"Stickiness
Feature adoption"Feature Used" by feature_nameProduct-market fit
Churn rateUsers inactive for 30+ daysRetention health

Pricing

PlanEvents/MonthPrice
Free20M$0
Growth100M+From $20/month
EnterpriseCustomCustom

Common Mistakes

MistakeImpactFix
Tracking too many eventsNoise, hard to find insightsTrack 10-15 key events max
Not identifying usersCan't track across sessionsCall identify() after login
Events without propertiesUseless dataAlways add context (plan, source, etc.)
Client-only trackingMissed server events (purchases)Use server-side for critical events
No naming convention"signup", "Sign_Up", "user_signup"Pick a convention and enforce it

Choosing analytics? Compare Mixpanel vs PostHog vs Amplitude on APIScout — pricing, features, and which fits your stack.

Comments