How to migrate from Mixpanel to PostHog

TL;DR

You can migrate from Mixpanel to PostHog without losing your historical event data or breaking your current dashboards. The full process takes roughly one to two weeks if you run a parallel tracking period, which you should. You need a PostHog Cloud account (free tier works up to 1 million events per month), your Mixpanel project credentials, and basic access to your product’s codebase or tag manager.

What You Need Before You Start

  • Mixpanel account access with at least Admin role so you can export raw data
  • PostHog Cloud account at posthog.com (free tier covers up to 1M events/month in 2026)
  • Your Mixpanel project token and service account credentials (found under Settings > Service Accounts)
  • Access to your product codebase or a tag manager like Google Tag Manager or Segment
  • PostHog project API key (generated after you create a project in PostHog)
  • Python 3.9+ or Node 18+ if you plan to run the historical import script
  • A spreadsheet (Google Sheets or Excel) to map your Mixpanel event names to PostHog equivalents
  • Optional: Segment as a middleware layer if your team does not want to touch the codebase directly

Step 1: Audit Your Mixpanel Events Before You Touch Anything

Before writing a single line of code, spend one hour inside Mixpanel’s Lexicon (Settings > Data Management > Lexicon). Pull up every event your team tracks and note which ones are actively used in reports, funnels, or cohorts. You will almost certainly find dozens of dead events from old A/B tests and features that shipped two product cycles ago.

Export the full event list as a CSV by clicking the download icon in the top-right of Lexicon. Open that CSV in your spreadsheet tool and add two columns: “Keep?” and “PostHog Name”. Mark each event yes or no. For the ones you keep, decide whether to rename them using PostHog’s naming convention (snake_case is standard, for example user_signed_up instead of User Signed Up).

This audit prevents you from carrying technical debt into your new platform. Migrating bad data is worse than starting fresh.

You should now see: a spreadsheet with every Mixpanel event, a keep/discard flag, and a mapped PostHog event name for everything you are keeping.


Step 2: Export Your Historical Mixpanel Data

Mixpanel lets you export raw event data via the Data Export API. You will need a service account (not your personal login) to authenticate. Go to Settings > Service Accounts, create one, and copy the username and secret.

Run this Python script to pull the last 90 days of data. Adjust the from_date and to_date as needed:

import requests
import json
from datetime import datetime, timedelta

USERNAME = "your-service-account-username"
SECRET = "your-service-account-secret"
PROJECT_ID = "your-project-id"

end = datetime.today()
start = end - timedelta(days=90)

url = "https://data.mixpanel.com/api/2.0/export"
params = {
    "from_date": start.strftime("%Y-%m-%d"),
    "to_date": end.strftime("%Y-%m-%d"),
    "project_id": PROJECT_ID,
}

response = requests.get(url, params=params, auth=(USERNAME, SECRET), stream=True)

with open("mixpanel_export.jsonl", "w") as f:
    for line in response.iter_lines():
        if line:
            f.write(line.decode("utf-8") + "\n")

print("Export complete.")

Mixpanel returns newline-delimited JSON (one event object per line). For large projects with millions of events, break the export into 30-day chunks to avoid timeouts.

You should now see: a mixpanel_export.jsonl file on disk with raw event records you can inspect with any JSON viewer.


Step 3: Create Your PostHog Project and Grab the API Key

Log into PostHog Cloud at app.posthog.com. Create a new project under your organization by clicking “New Project” in the left sidebar. Name it to match your product. PostHog will generate a project API key that starts with phc_.

Copy that key and store it somewhere safe. You need it for both the SDK installation and the historical import.

While you are here, go to Project Settings and set your data retention period. On the free plan you get 1 year of retention. If your team needs longer, check the paid plans. Also turn on “Group Analytics” now if you track B2B accounts as entities, because enabling it later can affect how historical data is queried.

You should now see: a fresh PostHog project with a phc_ API key in your clipboard and your data retention setting saved.


Step 4: Install the PostHog SDK Alongside Mixpanel

Do not remove Mixpanel yet. Install PostHog in parallel so both tools fire events simultaneously during your validation window. This parallel period protects you from gaps if something goes wrong.

For a JavaScript web app:

npm install posthog-js

Then initialize PostHog right next to your existing Mixpanel init, typically in your app’s entry file:

import posthog from 'posthog-js'

posthog.init('phc_YOUR_PROJECT_KEY', {
  api_host: 'https://app.posthog.com',
  capture_pageview: true,
  persistence: 'localStorage',
})

For React Native, use posthog-react-native. For a Python backend, use posthog-python. For mobile (iOS/Android), PostHog publishes official SDKs for both.

If your team routes events through Segment, add PostHog as a destination in the Segment dashboard instead. Go to Connections > Destinations > Add Destination, search “PostHog”, and paste your API key. No code changes needed.

You should now see: PostHog events appearing in the Live Events feed at app.posthog.com within a few minutes of loading your product.


Step 5: Map and Remap Your Events in Code

With both tools firing, update every mixpanel.track() call to also call posthog.capture(). Use the event name mapping you built in Step 1.

A practical pattern is to wrap both calls in a single analytics utility function so you only change one place later when you remove Mixpanel:

// analytics.js
import mixpanel from 'mixpanel-browser'
import posthog from 'posthog-js'

export function trackEvent(eventName, properties = {}) {
  // PostHog name: snake_case
  posthog.capture(eventName, properties)
  // Mixpanel legacy name: keep old format during transition
  const mixpanelName = eventName.replace(/_/g, ' ')
    .replace(/\b\w/g, c => c.toUpperCase())
  mixpanel.track(mixpanelName, properties)
}

Do the same for user identification. Replace direct mixpanel.identify() calls with both:

posthog.identify(userId, { email, name, plan })
mixpanel.identify(userId)
mixpanel.people.set({ $email: email, $name: name })

You should now see: both Mixpanel and PostHog receiving the same events and user identities whenever a user triggers an action in your product.


Step 6: Import Historical Data Into PostHog

PostHog accepts historical event ingestion via its Batch API. Each event needs an explicit timestamp field so PostHog places it correctly on your charts. Use the JSONL export from Step 2 and transform each Mixpanel record:

import json
import requests

POSTHOG_KEY = "phc_YOUR_PROJECT_KEY"
POSTHOG_HOST = "https://app.posthog.com"

EVENT_MAP = {
    "User Signed Up": "user_signed_up",
    "Purchase Completed": "purchase_completed",
    # add all your mappings here
}

batch = []

with open("mixpanel_export.jsonl") as f:
    for line in f:
        record = json.loads(line)
        mp_name = record.get("event")
        ph_name = EVENT_MAP.get(mp_name)
        if not ph_name:
            continue  # skip events you marked "discard"

        batch.append({
            "event": ph_name,
            "distinct_id": record["properties"].get("distinct_id", "anonymous"),
            "timestamp": record["properties"].get("time"),
            "properties": record["properties"],
        })

        if len(batch) == 500:
            requests.post(f"{POSTHOG_HOST}/batch/", json={
                "api_key": POSTHOG_KEY,
                "batch": batch,
            })
            batch = []

# flush remaining
if batch:
    requests.post(f"{POSTHOG_HOST}/batch/", json={
        "api_key": POSTHOG_KEY,
        "batch": batch,
    })

print("Import complete.")

Send batches of 500 events at a time. PostHog’s free tier has no historical import limit, but rate limiting applies at around 1,000 requests per minute.

You should now see: historical events populating PostHog charts with correct timestamps from before your SDK install date.


Step 7: Recreate Your Core Dashboards and Funnels

With data flowing in PostHog, rebuild the three to five dashboards your team checks every week. Start with funnels since those are the highest-stakes reports for most product teams.

Go to Insights > New Insight > Funnels. Add each step using your new PostHog event names. PostHog’s funnel UI is close to Mixpanel’s but uses “steps” instead of “events” in its label. Set your conversion window to match what you had in Mixpanel (14 days is a common default).

For retention analysis, use Insights > Retention. Pick your cohort-defining event (usually user_signed_up) and your retention event (usually the core action your product is built around). PostHog shows retention in a triangle heatmap that is very close to Mixpanel’s format.

Tag each dashboard as a “Team Dashboard” and pin it so your PMs and engineers can find it without hunting.

You should now see: your core funnels and retention reports in PostHog matching the shape of your Mixpanel equivalents within a reasonable margin.


Step 8: Run a Two-Week Parallel Validation Period

For two weeks, keep both tools running. Every Monday, open both Mixpanel and PostHog and compare the numbers for your top five events. You are looking for event counts that are within five percent of each other. Larger gaps usually mean a missed trackEvent call somewhere in the codebase, a bot filtering difference, or a user identity mismatch.

Build a simple comparison table in Sheets:

Event Mixpanel (7d) PostHog (7d) Delta %
user_signed_up 1,240 1,218 -1.8%
purchase_completed 340 322 -5.3%

A delta above 10 percent warrants investigation. Check the Live Events feed in PostHog to confirm the event is firing with the right properties. Check your analytics utility function for any conditional logic that might suppress the PostHog call.

You should now see: a comparison table where every key event is within acceptable variance after resolving any gaps you find during the first week.


Step 9: Remove Mixpanel and Clean Up

Once your validation table shows stable numbers for two consecutive weeks, remove Mixpanel. Delete the npm package, remove the init call, and strip out any mixpanel.* calls from your codebase. If you used the analytics utility wrapper from Step 5, you only have one file to edit.

npm uninstall mixpanel-browser

Then do a codebase-wide search for any remaining mixpanel references:

grep -r "mixpanel" src/ --include="*.js" --include="*.ts"

Cancel your Mixpanel subscription from Settings > Organization > Billing if you are on a paid plan. Keep your Mixpanel account open in read-only mode for 30 days in case a stakeholder needs to reference an old report.

You should now see: zero Mixpanel network calls in your browser’s DevTools Network tab, and your PostHog event counts continuing at their normal rate.


Common Mistakes To Avoid

  • Skipping the event audit. Importing every Mixpanel event without reviewing them first means you carry broken naming conventions and zombie events into PostHog. Do the Lexicon export first.
  • Removing Mixpanel before validating. Two weeks of parallel tracking feels slow but it catches the 15 percent of events that are always wired incorrectly somewhere in the app.
  • Forgetting server-side events. Most teams remember to update the frontend SDK but miss backend event calls in their API (subscription created, payment failed). Grep your server codebase the same way you grep the frontend.
  • Mismatched distinct_id formats. Mixpanel sometimes stores anonymous IDs with a prefix like $device:abc123. If you import those as-is, PostHog treats them as separate users from your authenticated IDs. Normalize them during the import transform in Step 6.
  • Setting the wrong timezone. PostHog and Mixpanel can have different project timezone settings. Set both to the same timezone before comparing numbers or your daily charts will look misaligned even when data is correct.
  • Not enabling Group Analytics before the import. If you enable it after importing historical data, group properties will not backfill onto old events and your company-level funnels will be missing data.

When To Level Up

This migration approach works well for products with up to around 50 million monthly events and teams of one to ten people managing analytics. Beyond that threshold, the manual export-transform-import script starts to show its limits. You will hit Mixpanel API rate limits, the JSONL files get unwieldy at multiple gigabytes, and validating event parity across hundreds of event types by hand becomes a part-time job.

At that scale, consider using a data pipeline tool like Fivetran or Airbyte to move data between platforms continuously, or use a warehouse-first approach where both tools write to BigQuery or Snowflake and you validate parity at the SQL layer. Segment as a customer data platform is another option worth considering if you need to fan out to more than two downstream tools without touching code for every new integration.

For next-step tooling options at higher scale, browse the full comparison library at /category/data-analysis/.


Frequently Asked Questions

Does PostHog have a Mixpanel data importer built in?
PostHog does not offer a one-click Mixpanel import as of mid-2026. You need to use the Batch API approach described in Step 6, or a third-party ETL tool. PostHog’s team has flagged a native importer as a roadmap item but it is not generally available yet.

Will my Mixpanel funnels translate directly to PostHog?
The funnel logic is compatible but you will need to rebuild each funnel manually using your new PostHog event names. PostHog does not import Mixpanel report configurations. Budget about two hours to recreate five to ten core funnels.

Can I keep both tools running permanently instead of migrating?
You can, but you will pay for two analytics platforms and your team will argue about which number is right in every standup. Running them in parallel for two to four weeks is useful for validation. Running them permanently is just expensive confusion.

What happens to my Mixpanel cohorts and user segments?
Cohorts do not migrate automatically. You need to recreate them in PostHog using the Cohorts feature under People. If your cohorts are query-based (for example, “users who completed purchase in the last 30 days”), rebuilding them in PostHog is straightforward because the filter interface is similar.

Is PostHog’s free tier actually enough for a small startup?
One million events per month covers most products under 10,000 monthly active users comfortably. If you send pageview events on every route change and have high traffic, you can hit the limit faster. PostHog’s dashboard shows your usage in real time so you can monitor it and either optimize event volume or upgrade before you hit the cap.


Bottom Line

Migrating from Mixpanel to PostHog is a two-week project, not a two-day one, if you do it properly. You audit your events first, export historical data, install PostHog alongside Mixpanel, run both in parallel to validate parity, then cut over cleanly. The script in Step 6 handles the historical import and the analytics utility wrapper in Step 5 keeps your codebase clean during the transition. Most teams find that rebuilding their dashboards in PostHog is actually faster than expected because the UI is familiar enough. The payoff is a self-hosted-optional, warehouse-native analytics stack that your engineering team can also use for feature flags and session recording without buying another tool. For more on picking the right analytics stack for your stage, check out the full tool comparison guide at /category/data-analysis/.