Moving from Zendesk to a modern platform like Intercom, Front, or Help Scout looks simple on a slide deck. But for the engineering team tasked with "getting the data out," it is a descent into technical debt. This is the post we wish someone had handed us on day one of our first migration project.
The trap is not that Zendesk refuses to export data. The trap is that the export looks complete until someone tries to use it. Your ticket count appears right. The CSV opens in a spreadsheet. The date columns look parseable. Then the import team asks where the internal notes went, why custom_field_3829103 is supposed to mean "Refund Reason," and how to reconnect a ticket to the customer, organization, agent, SLA, tags, attachments, comments, and custom objects that made it meaningful.
A help desk migration is not a file transfer. It is a reconstruction project. Zendesk is a relational system with years of operational decisions encoded into custom fields, groups, tags, macros, user records, organizations, and conversation history. The destination system has its own model. A correct migration builds a clean, queryable intermediate representation first, then maps that representation into Intercom, Front, Help Scout, or whatever comes next.
The Illusion of the CSV
Zendesk provides a built-in "Export" button. It produces a CSV. For a small shop with 1,000 tickets, this is fine. For an enterprise with 10 years of history, it is a trap.
A CSV is a presentation format, not a migration format. It can represent rows, but it cannot reliably represent nested event streams, many-to-many relationships, schema history, object relationships, field metadata, or state changes over time. It also encourages the worst possible migration workflow: open the export, eyeball a few columns, write a one-off transform, and only discover missing entities when the destination import rejects the payload.
Standard exports almost always fail in three specific areas.
1. The Custom Field Identity Crisis
Your "Refund Reason" or "Product ID" fields aren't exported as names. They appear as custom_field_3829103. Without a mapping layer, your data is a graveyard of anonymous IDs.
The Custom Field ID-to-name mapping lives in a completely separate API endpoint (/api/v2/ticket_fields) that must be joined manually to your ticket export. Most CSV-based workflows skip this step entirely because the join is a non-trivial ETL operation that requires writing code.
The join is harder than it sounds because custom fields are not static. A company that has used Zendesk for 8 years may have deleted fields, renamed fields, changed drop-down options, or repurposed a field from one team to another. The same field ID may carry different business meaning across time unless you preserve the field title, key, type, option metadata, and the extracted value.
For example, a ticket row might include this fragment:
{
"id": 48211,
"status": "closed",
"custom_fields": [
{ "id": 3829103, "value": "billing_refund" },
{ "id": 9182044, "value": "enterprise" },
{ "id": 7710291, "value": "pylon_migration" }
]
}
By itself, this is nearly useless. A correct migration must turn that into human-readable records:
{
"ticket_id": 48211,
"fields": {
"refund_reason": "billing_refund",
"customer_segment": "enterprise",
"migration_target": "pylon_migration"
}
}
That second shape requires a full metadata pass before the ticket export begins. It also requires preserving the raw field ID alongside the mapped key, because compliance teams may need to trace the migrated value back to the original Zendesk configuration.
The API extraction quirks behind bulk exports compound this — rate limits and pagination edge cases silently drop records during the join phase.
2. The Conversation Gap
Zendesk's bulk export often only gives you the "Ticket Header" (Subject/Status). It ignores the Ticket Events — the actual back-and-forth dialogue between your customers and agents. If you move without these, you lose the "Why" behind every ticket.
Conversation history lives in a separate Ticket Comments resource that paginates differently from the main Tickets endpoint. Extracting it correctly requires a full second pass over your data with its own rate-limit handling.
This is where many migrations become dangerous for AI and compliance workflows. A support ticket is not just a final status. It is a chronology: customer message, agent reply, private escalation note, macro response, attachment, system event, reassignment, follow-up, resolution. If you collapse that sequence into a single body field, you destroy the audit trail.
The most important field is often the easiest to lose: whether a comment was public or private. Zendesk preserves that distinction. A flat export often does not. If an internal note says "customer is threatening chargeback" or "legal requested review," that content must not be treated as a customer-facing answer in a new helpdesk or a training set for a RAG pipeline.
A correct comment record keeps at least these fields: ticket ID, comment ID, author ID, body, HTML body if needed, public/private flag, timestamp, attachments, and source channel. The order matters. The author mapping matters. The visibility flag matters.
3. Relational Fragmentation
Tickets, Users, and Organizations are three separate entities linked by IDs. Standard exports don't preserve the "Relational Integrity" required to rebuild these links in a SQL database. This is especially acute for Sunshine Custom Objects — see the Sunshine Custom Objects deprecation timeline and the extraction challenges it creates.
A typical Zendesk instance has 4–7 entity types that must be extracted and rejoined: tickets, users, organizations, groups, custom objects, custom object relationships, and ticket comments. The CSV export gives you maybe 2 of these in a single file.
Relational fragmentation shows up late because the first import can still succeed. You may get tickets into the destination platform, but they arrive detached from their requesters, stripped of organization context, missing the group or inbox assignment that tells the new tool where work should route. Your support managers then spend weeks fixing records manually.
The deeper issue is foreign key integrity. A ticket with requester_id = 28471 is only useful if user 28471 exists, has the right email address, belongs to the right organization, and has not been deduplicated incorrectly. If the requester was deleted, anonymized, merged, or suspended in Zendesk, the migration layer needs a policy for preserving the relationship without corrupting the destination system.
The GDPR Right to Portability vs. The Reality
Legally, under GDPR (Article 20), you have the right to data portability. But Zendesk has no legal obligation to make that data usable. They provide the raw material; you provide the pickaxe.
The legal framework guarantees access; it does not guarantee quality. A vendor can satisfy GDPR by handing you a 50 GB CSV that takes a week of engineering to make queryable.
This distinction matters when migration projects are scoped by legal or procurement teams. "We can export the data" does not mean "we can import it into the next system," and it definitely does not mean "we can preserve it in a way that legal, support, analytics, and AI teams can use later."
For portability to be practical, the export needs four properties:
- Completeness: every relevant entity and relationship is present.
- Interpretability: fields are named and typed in a way humans can understand.
- Provenance: records can be traced back to the original Zendesk IDs and timestamps.
- Reusability: the output can be loaded into a database or destination API without manual spreadsheet work.
A raw CSV usually satisfies only part of the first property. It gives you some rows, not the system. That may be enough to check a legal box, but it is not enough to run a serious migration.
What a Correct Migration Schema Looks Like
The target Postgres schema for a Zendesk-to-anywhere migration should look like this:
CREATE TABLE tickets (
id BIGINT PRIMARY KEY,
external_id TEXT,
subject TEXT,
status TEXT NOT NULL,
priority TEXT,
via_channel TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
requester_id BIGINT REFERENCES users(id),
assignee_id BIGINT REFERENCES users(id),
organization_id BIGINT REFERENCES organizations(id),
tags TEXT[]
);
CREATE TABLE ticket_comments (
id BIGINT PRIMARY KEY,
ticket_id BIGINT NOT NULL REFERENCES tickets(id),
author_id BIGINT REFERENCES users(id),
body TEXT,
public BOOLEAN NOT NULL, -- FALSE = internal note
created_at TIMESTAMPTZ NOT NULL
);
CREATE TABLE ticket_custom_fields (
ticket_id BIGINT NOT NULL REFERENCES tickets(id),
field_key TEXT NOT NULL, -- human-readable
field_title TEXT NOT NULL,
value TEXT,
PRIMARY KEY (ticket_id, field_key)
);
The key column is public BOOLEAN NOT NULL on ticket_comments — that single boolean is what distinguishes a customer-facing reply from an internal agent note. Lose it, and your conversation history becomes useless for compliance review, AI training, or even basic historical lookups.
In practice, a full migration schema usually includes more tables than this minimum. Users and organizations need first-class tables. Groups, brands, ticket forms, fields, tags, attachments, audits, and object relationships may need tables too. The exact schema depends on your destination, but the intermediate layer should be richer than any single destination API.
That intermediate layer is what prevents lock-in during the migration. If you extract Zendesk directly into Intercom's model, then change your mind and move to Front, you start over. If you extract Zendesk into clean Postgres first, you can run multiple destination mappings from the same durable source of truth.
A stronger production schema adds raw payload retention for auditability:
CREATE TABLE raw_zendesk_records (
source_type TEXT NOT NULL,
source_id TEXT NOT NULL,
fetched_at TIMESTAMPTZ NOT NULL,
payload JSONB NOT NULL,
PRIMARY KEY (source_type, source_id)
);
This table is not for daily analytics. It is for proving what was received from Zendesk when someone asks why a field mapped a certain way. Clean relational tables make the data usable; raw JSONB makes the migration defensible.
Destination-Specific Migration Quirks
Different target platforms expose different import constraints. Here's what each requires.
Migrating to Salesforce Service Cloud
Salesforce's data model is closer to Zendesk's than Intercom or Front — Case (=ticket), CaseComment (=comment), Account (=organization), Contact (=user). The complexity is on the import side: Salesforce Bulk API 2.0 batching, picklist value mapping, and referential integrity. See our Zendesk to Salesforce migration guide for the full playbook.
Migrating to Intercom
Intercom's data model centers on the Conversation, not the ticket. The closest mapping for Zendesk tickets is Intercom's Conversation API, but the granularity is different — a Zendesk ticket with 12 comments becomes an Intercom conversation with 12 parts. Custom fields map to Intercom's custom_attributes on the Contact, not on the Conversation itself, which is counterintuitive coming from Zendesk's ticket-centric model.
The practical migration decision is whether Zendesk ticket fields belong on the Intercom contact, company, or conversation. A field like "Customer Tier" usually belongs on the company or contact. A field like "Refund Reason" belongs on the conversation. A field like "Affected Product" may need both: a searchable conversation attribute and a normalized tag for support reporting.
Intercom also changes the meaning of assignment. Zendesk assignees and groups do not map cleanly to Intercom teams and teammates. Before import, you need a routing matrix that decides how historic groups should appear in Intercom. Otherwise, old enterprise tickets may land in the wrong workspace or lose the queue context that explains why they were escalated.
Migrating to Front
Front uses Inboxes and Threads as its primary abstractions. The migration requires deciding which Zendesk Groups map to which Front Inboxes, then converting tickets to Threads inside those Inboxes. Front's tagging model is flatter than Zendesk's — multi-select custom fields need to be flattened to tag combinations.
The hardest part of a Front migration is usually not ticket text. It is workspace design. Zendesk groups often encode support tiers, product lines, geographies, and escalation states all at once. Front wants clearer inbox boundaries. If you import every Zendesk group directly as an inbox, you may recreate the exact operational sprawl you were trying to escape.
A good Front migration uses the extracted schema to answer design questions before import: which groups actually handled meaningful volume, which tags drove routing, which custom fields were required for reporting, and which queues were historical artifacts. The destination mapping should simplify operations without deleting the historical record.
Migrating to Help Scout
Help Scout has the closest data model to Zendesk: Conversations (=tickets), Threads (=comments), Mailboxes (=groups). The migration is mechanically simpler but the API throughput is lower — Help Scout's import endpoints accept fewer records per minute than Zendesk's export endpoints produce, so the extraction typically needs to buffer to disk and import asynchronously.
The main design question is mailbox mapping. Zendesk brands, groups, and forms can all imply where a conversation belongs. Help Scout mailboxes are simpler, so you need a deterministic rule for selecting one mailbox per imported conversation. For example: brand first, then group, then ticket form, then fallback mailbox.
Help Scout's simplicity is a strength once the data is clean. But it means custom field sprawl should be reduced before import. If you bring every historical Zendesk field forward, you can overload a cleaner system with legacy baggage. The better approach is to preserve all fields in Postgres or JSONL, then import only the subset that still matters operationally.
The Evicta Standard: Surgical Extraction
We built Evicta to be the "Surgical Strike" of data migration. Instead of a dumb pipe, we use an AI-assisted architecture that:
- Discovers every custom field in your instance via the Custom Fields API
- Maps the anonymous IDs to human-readable column names using LLM-assisted naming, then asks you to approve every mapping
- Extracts the full conversation history with
public/internalpreserved - Resolves Custom Object relationships into a queryable graph schema
- Streams output to Postgres or JSONL — your choice
The important word is "assisted." The system can propose field names, detect duplicate concepts, and identify likely destination mappings, but a human still approves the final schema. This matters because support data carries business meaning that only your team knows. custom_field_3829103 might look like a refund reason, but your finance team may treat it as a chargeback-risk flag.
A surgical extraction runs in phases:
- Discovery: enumerate fields, forms, groups, brands, users, organizations, tags, and object types.
- Schema preview: generate the proposed relational schema and destination mapping.
- Human approval: let the team rename fields, drop obsolete mappings, and flag sensitive data.
- Extraction: stream records out of Zendesk with rate-limit-aware pagination.
- Validation: compare counts, foreign keys, required fields, and comment visibility.
- Delivery: write clean Postgres output (all tiers) or JSONL (Premium) for import and archival.
The full extraction completes in under 24 hours for typical instances (up to 2M records), regardless of the destination platform. Once the data is in clean Postgres, importing into Intercom, Front, Help Scout, or anywhere else becomes a destination-specific mapping job rather than a data-archaeology project.
That separation is the difference between a migration that can be audited and a migration that depends on a one-off script no one wants to rerun. If the destination import fails, the extraction does not have to start over. If a stakeholder asks for a new mapping, the clean intermediate data is already there. If legal asks for an archive, the JSONL output (Premium) or CSV output (Starter) can be stored separately from the new helpdesk.
The Bottom Line
Don't let your historical context die in a messy CSV. Own your history, in a format you can actually query. The migration decision is the easy part — the data extraction is where projects succeed or fail.
A good helpdesk migration protects three futures at once. Support gets a clean operational system. Data and AI teams get structured history they can query safely. Legal and compliance get an archive that preserves provenance and visibility rules. A CSV export cannot do that job by itself.
If you are planning a Zendesk exit in 2026, start with the extraction architecture, not the destination import wizard. Decide what data must be preserved, what relationships must remain intact, what fields need human-readable names, and what proof you need that the migration is complete. Once that foundation exists, Intercom, Front, Help Scout, and the next platform after them become implementation details.
Frequently Asked Questions
Why do Zendesk CSV exports fail for enterprise migrations?
Zendesk's native CSV export flattens custom fields to anonymous IDs like custom_field_3829103, strips full conversation history down to ticket headers only, and breaks relational links between Tickets, Users, and Organizations — making the data unusable without a dedicated mapping layer.
Do I have a right to my Zendesk data under GDPR?
Yes, under GDPR Article 20, you have a right to data portability. However, Zendesk has no legal obligation to make that data usable. They provide raw exports in their own format — not a clean, structured archive you can directly migrate into a new system.
How does Evicta fix custom field mapping in Zendesk exports?
Evicta auto-discovers every custom field in your Zendesk instance and maps them to human-readable column names, while preserving full conversation threads and relational integrity between tickets, users, and organizations.