posthog-migration-deep-dive

v2026.09.24

Plan and execute a controlled historical migration into PostHog with identity mapping, timestamp validation, dual-write evidence, and rollback boundaries. Use when moving from another analytics platform or PostHog region. Trigger with "migrate to PostHog", "PostHog historical import", or "PostHog dual write".

GitHub
Install command
npx skhub add jeremylongshore/posthog-migration-deep-dive
Markdown
SKILL.md

PostHog Migration Deep Dive

Overview

Migrate from Google Analytics, Mixpanel, Amplitude, or Segment to PostHog using a dual-write strategy (send events to both old and new platforms) followed by gradual traffic shifting. PostHog's capture API accepts events in a format similar to Segment's track/identify calls, making migration straightforward.

Prerequisites

  • A paid product analytics plan and a dedicated target project are confirmed for historical imports.
  • The event taxonomy, distinct-ID map, timestamp policy, and rollback owner are approved.
  • A representative sample can be validated before full ingestion.

Authentication

  • Use the target project's public project token only for event capture. It is an ingestion identifier, not a secret or a credential for private APIs.
  • Use a least-privilege personal API key or OAuth token for private project APIs, keep it in a secret manager, and never place it in browser code, logs, examples, or committed files.
  • If server-side local feature-flag evaluation is required, use a feature flags secure API key through the SDK's personalApiKey option; do not reuse a broadly scoped personal API key.
  • Select the matching US or EU host for the target project before any sample or bulk write.

Migration Types

SourcePrimary discovery riskEvidence required before import
Google Analytics (GA4)Event and identity models differApproved taxonomy and identity mapping
MixpanelSimilar names can hide property or identity driftSample export reconciliation
AmplitudeCohort and user-property semantics can differProperty and timestamp comparison
SegmentDestination behavior can differ from direct SDK captureDual-write sample and delivery logs
Custom analyticsSource semantics are implementation-specificSource contract, resumable export, and representative sample

Instructions

Tool discipline

Use Read to inspect the relevant configuration and implementation before proposing changes. Use Write only for a new, explicitly requested artifact inside the target project. Use Edit for minimal changes to existing project files after the evidence pass.

Step 1: Event Name Mapping

Create a reviewed mapping table with source event, target event, source property, target property, source identity, target distinct ID, timestamp conversion, consent class, and owner. Reject unknown identities, empty event names, invalid timestamps, and unreviewed PII instead of silently coercing them.

Step 2: Dual-Write Adapter

Wrap the existing analytics boundary once, apply the frozen mapping before either destination, and attach a migration-run identifier. Track delivery results separately for the legacy and PostHog paths. The application path must remain successful when either analytics destination fails, and the adapter must expose independent kill switches plus a bounded flush on shutdown.

Step 3: Historical Data Import

import { PostHog } from 'posthog-node';

const importer = new PostHog(process.env.POSTHOG_PROJECT_TOKEN!, {
  host: process.env.POSTHOG_PUBLIC_HOST!,
  historicalMigration: true,
});

Historical imports require a paid product analytics plan even though the import itself is free. Use the Python or Node SDK, or the public batch endpoint, only with events dated at least 48 hours before import. Export to durable storage first, checkpoint every batch, preserve ISO 8601 timestamps and stable distinct IDs, and stop on the first reconciliation breach.

Step 4: Batch Import via HTTP API

{
  "api_key": "project-token-from-secret-source",
  "historical_migration": true,
  "batch": [
    {
      "event": "mapped_event_name",
      "properties": {"distinct_id": "stable-user-id", "migration_run": "approved-run-id"},
      "timestamp": "approved-iso-8601-timestamp"
    }
  ]
}

POST this shape to the selected regional /batch/ endpoint. Keep each request below the documented body-size limit, record its checkpoint and response, and never paste a real project token into a generated file or transcript.

Step 5: Feature Flag Controlled Cutover

Use explicit legacy, dual-write, and posthog-only states with legacy as the fail-closed default. If PostHog evaluates the cutover flag server-side, pass the feature flags secure API key through the SDK's personalApiKey option. Advance only after the approved observation window passes identity, count, property, timestamp, and business-metric reconciliation; roll back on any breach.

Step 6: Validation

set -euo pipefail
# Compare event counts between old platform and PostHog
echo "=== PostHog Event Counts (last 7 days) ==="
curl "https://us.posthog.com/api/projects/$POSTHOG_PROJECT_ID/query/" \
  -H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "kind": "HogQLQuery",
      "query": "SELECT event, count() AS total FROM events WHERE timestamp > now() - interval 7 day AND properties.migration_source = '"'"'dual-write'"'"' GROUP BY event ORDER BY total DESC LIMIT 20"
    }
  }' | jq '.results[] | {event: .[0], count: .[1]}'

Error Handling

IssueCauseSolution
Event counts don't matchSampling, identity, or timing differencesStop and compare against the migration's approved reconciliation tolerance
Historical import slowSingle-threadedUse batch endpoint, increase flushAt
Identity mismatchDifferent user ID formatsNormalize IDs in event map
Duplicate eventsDual-write without dedupUse migration_source property to filter

Output

  • Event name and property mapping from source platform
  • Dual-write adapter for gradual migration
  • Historical data import script
  • Feature flag controlled cutover plan
  • Validation queries comparing event counts

Examples

For a Mixpanel migration, freeze the event and identity map, run a tiny historical sample with historical_migration enabled, compare counts and properties, then expand in bounded batches. Keep live dual-write and historical import evidence separate, and stop on identity or timestamp drift.

Resources

See official PostHog references for current authority and verification boundaries.

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/.curated/posthog-migration-deep-dive

Default branch

main

Latest commit

e5a6c3b

Tree SHA

c2dc8e8