All posts

Field note

How to Migrate to Salesforce Without Losing History

Akshit Kandi
#Salesforce#Data Migration#Data Architecture#CRM
How to Migrate to Salesforce Without Losing History
Salesforce

How to Migrate to Salesforce Without Losing History

SkySync

Most Salesforce migrations don't lose data — they lose history. Here's how to name the six kinds of history a legacy system carries, which ones the platform can actually keep, and the one-shot constraint that decides your whole plan.


The day after go-live, someone in support opens an account and the activity timeline starts last Tuesday. Three years of calls, emails, case threads, and notes — the context that told them how to handle this customer — are gone. The records migrated fine. The history didn't.

This is the failure mode that survives every status meeting. Nobody signs off on losing history. It leaks out the side of a project scoped around objects and fields, because history is not a field. It's a property of how records relate to time, to each other, and to the people who touched them. If you don't name it as a deliverable with its own acceptance test, the migration quietly drops it and still passes.

History is six different things, not one

When a stakeholder says "we can't lose history," they're pointing at six distinct things at once. Each has a different cost to preserve and a different way of going wrong. Itemize them before you write a single field mapping — because the mapping spreadsheet only models the first one.

  • Record history — the rows themselves: old accounts, closed deals, archived cases.
  • Activity history — the calls, emails, meetings, and tasks that form the timeline. In Salesforce these are separate Task and Event rows that must be loaded and re-linked to their parent, not columns you carry along with the account.
  • Field-level audit history — who changed what, when, and from what value. The legacy system kept it in its own log; native field history tracking will not reconstruct it.
  • Temporal accuracy — the original CreatedDate, last-modified date, and close date, not the date you happened to load the row.
  • Relationship history — which contact belonged to which account three years ago, before the reorg that re-parented half of them.
  • Document and conversation history — attachments, call recordings, and chat transcripts that live in a separate store and must be re-stitched to the record.

Only the first one is what most plans mean by "migrate the data." The other five are where projects bleed. A clean load of records that arrives with no timeline, no audit trail, and a CreatedDate of go-live day is a technically successful migration that destroyed the asset everyone actually cared about.

The CreatedDate problem decides your whole approach

Here's the part the tooling demos skip. By default, Salesforce stamps CreatedDate as the moment a row lands in the org. Load a 2021 opportunity today and the platform believes it was created today. Every report bucketed by quarter, every cohort analysis, every "deals created last year" dashboard is now wrong — silently, with no error and no failed row to chase.

Salesforce does let you set the system audit fields — CreatedDate, CreatedById, LastModifiedDate — but only through the "Set Audit Fields upon Record Creation" permission, which is off by default and granted per profile or permission set. And it applies only at the instant of the first insert. There's no UI or update path to backfill it later: once a row exists with the wrong CreatedDate, the only fix is to delete and reload it. That one constraint reshapes the project. The date-bearing load has to be right on the first pass, in dependency order, with the real values attached.

If your plan assumes you'll clean up dates in a second pass, you don't have a migration plan. You have a one-shot you haven't rehearsed.

Decide what becomes a record and what becomes an archive

Not all history deserves to be a live, queryable record. Carrying ten years of closed cases into core objects burns storage, slows queries, and clutters the very timelines you were trying to protect. Split history into two tiers and treat them differently.

  • Operational history — recent, relationship-defining, likely to be acted on. This belongs as native records with full activity timelines.
  • Reference history — old, rarely touched, occasionally needed for audit or analytics. This belongs in cheaper storage: Big Objects for high-volume archival inside the platform (queryable through async SOQL, not in standard list views), or Data Cloud where it stays joinable without consuming transactional record storage.

Data Cloud changes the old calculus. The choice used to be binary: pay to bring history into the core org, or lose access to it. Now you can leave deep history in a lake and federate it — the rep or the agent sees a unified profile spanning both, while the bulk never bloats the org. Get the data layer right and the agent layer gets easy. That ordering isn't a slogan; it's a sequencing constraint, and it holds whether the consumer is a human in Service Cloud or an Agentforce agent that needs the full customer story to answer well.

Audit history won't migrate. Draw a freeze line instead.

Field-level audit trail is the most over-promised part of any migration. Native field history tracking generates entries from changes that happen inside Salesforce. It cannot ingest the change log from your old system and pretend those edits occurred here — and if it could, you'd be manufacturing provenance, which is the opposite of what an audit exists to verify. Legacy audit history is, for practical purposes, non-portable into native field history.

So stop recreating it. Preserve the old audit log somewhere immutable and addressable — an archive table, a Big Object, or an object in Data Cloud keyed to the record — and treat the migration date as a seam. Before the seam: query the archive. After the seam: native field history. For regulated work, document the seam explicitly. Auditors accept a defensible boundary with a clear cutover date far more readily than a reconstructed trail whose authenticity nobody can prove.

Relationship history breaks where row counts can't see it

The subtle killer is referential drift. A contact that pointed to Account A in the source ends up orphaned or re-parented in the target because the accounts loaded in a different order, a dedupe merged two parents, or an external ID collided. Your row counts reconcile perfectly. Your relationships are quietly wrong, and nobody notices until a rep asks why this contact is filed under the wrong company.

Defend against it with a stable external ID on every object and upserts keyed to those IDs — never to Salesforce's internal record IDs, which don't exist until load time. Load parents before children so each foreign key has a target. Then reconcile relationships as a first-class check, not just totals: sample parent-child pairs and confirm they survived the trip. Records being present is necessary and nowhere near sufficient.

A sequence that keeps history intact

Order is most of the battle. History is lost in the gaps between steps, so the steps are the design.

  • Grant Set Audit Fields in the target org first, and confirm it's active — it fails silent, stamping today's date with no warning.
  • Profile the source for real date ranges, orphaned children, and duplicate keys, and resolve them before the load, not during it.
  • Load parents before children, in dependency order, so every relationship has something to point at.
  • Carry external IDs and original audit dates on the very first insert of each record — there is no second chance for CreatedDate.
  • Decide per object whether deep history goes native, into Big Objects, or stays federated in Data Cloud.
  • Reconcile on three axes — counts, timeline completeness, and relationship integrity — then have a real user open the ten accounts they know best and tell you whether the history looks right.

That last check is the cheapest insurance you'll buy. Automated reconciliation tells you the numbers tie out. A human who remembers what the old timeline looked like tells you whether you preserved the thing that mattered.

Why the discipline pays off

History isn't sentimentality. It's the substrate everything downstream runs on. Forecasts need accurate close dates. Service quality needs the timeline. And an AI agent needs the full, time-correct customer story to give an answer you'd trust. An agent reasoning over a record that begins on go-live day won't error out — it will be confidently, plausibly wrong, which is worse. The cost of a thin migration is paid later, by the people and systems that assumed the history was there.

Treat history as a named deliverable with its own acceptance criteria, and migrate it on purpose. The org you stand up is only as good as the past it can remember.

Planning a Salesforce migration and want the history to survive the move — and to be agent-ready on the other side? Book a working session with our team.