Mixpanel’s Raw Event Export API returns newline-delimited JSON suitable for a staged historical import. Export a small time range, preserve the raw response, and transform the vendor-specific property envelope into an explicit GraphJSON event schema.
Start with the shared migration workflow.
Export raw events#
Follow Mixpanel’s official Raw Event Export API. Use the server hostname for your project’s data residency and authenticate from a trusted migration worker.
Export bounded date intervals and record:
- requested start and end dates
- Mixpanel project and region
- raw file checksum and row count
- earliest and latest source event time
- retry attempts and complete failures
The response is JSON Lines: process it as a stream rather than loading a large export into memory.
Map the fields#
Mixpanel places event metadata and custom properties inside a properties object.
| Mixpanel field | GraphJSON target | Notes |
|---|---|---|
event |
event |
Apply reviewed renames only |
properties.time |
request timestamp |
Verify units requested from the export |
properties.distinct_id |
user_id or anonymous_id |
Decide from your identity model |
properties.$insert_id |
event_id |
Prefix to preserve its source namespace |
| selected custom properties | target event fields | Allowlist and type-check |
| group keys | account_id or explicit group field |
Map the group type intentionally |
Do not blindly copy every $-prefixed field. Many are Mixpanel ingestion, device, campaign, or SDK details rather than durable product concepts.
Transform one record#
function toGraphJSON(source) {
const props = source.properties || {};
const timestamp = Number(props.time);
if (!source.event || !Number.isInteger(timestamp)) {
throw new Error("missing event or integer properties.time");
}
return {
timestamp,
body: {
event: source.event,
event_id: props.$insert_id
? `mixpanel:${props.$insert_id}`
: undefined,
user_id:
typeof props.distinct_id === "string"
? props.distinct_id
: undefined,
account_id:
typeof props.account_id === "string"
? props.account_id
: undefined,
plan: typeof props.plan === "string" ? props.plan : undefined,
schema_version: 1,
migration_source: "mixpanel",
},
};
}
Mixpanel can export timestamps in different representations depending on the endpoint option. Confirm the response contract for your request and normalize once. GraphJSON requires whole Unix seconds.
Remove undefined values, validate the serialized size, and quarantine invalid rows rather than coercing them silently.
Resolve distinct_id#
distinct_id may represent an authenticated user, an anonymous device, or an identifier affected by Mixpanel’s identity features. Classifying every value as user_id can merge unlike concepts in GraphJSON.
Sample records from before sign-in, after sign-in, and after an identity merge. Then decide:
- known application IDs →
user_id - anonymous browser/device IDs →
anonymous_id - verified merge relationship → explicit
identity_linkedevent or controlled rewrite
Preserve the unmodified source value in a scoped field during the migration pilot when it helps investigation, then decide whether it belongs in the final contract.
Import and reconcile#
Import to a temporary collection first if the mapping is still changing. Once the transform is stable, send batches of up to 50 records to the final collection with conservative concurrency.
Reconcile:
- JSONL rows = accepted rows + quarantined rows
- counts by event and UTC occurrence day
- unique
$insert_idversus unique destinationevent_id - unique authenticated and anonymous identifiers
- required-property completeness
- totals for revenue and other business facts
Mixpanel reports may apply project timezone, identity merging, exclusion rules, or computed definitions. Compare raw exported events first, then rebuild each derived metric with the same definition.
Cutover checklist#
- The export server matches the Mixpanel project region.
-
properties.timeis normalized to whole Unix seconds. -
distinct_idclassification has been tested across identity states. - Only approved custom properties are imported.
-
$insert_idis preserved when available. - Invalid rows are quarantined with reason codes.
- A pilot interval and business metric both reconcile.
- New production events are dual-written before retirement.