Why data migrations actually fail
Migrations rarely fail because someone picked the wrong tool. They fail in four specific, repeatable ways.
The mapping was incomplete, not wrong. Teams map the obvious fields, then find after go-live that a legacy free-text "Account Manager" field was never mapped. The fields that get missed are rarely the ones on the page layout. They are the ones a single team relied on in a report.
Load order broke the relationships. Contacts loaded before Accounts either fail outright or land with a blank AccountId. If they land blank and nobody checks, you get a working-looking org where a chunk of your Contacts attach to nothing. That is worse than a hard failure, because a hard failure gets fixed on day one.
Nobody reconciled. The load finished, the success file had a lot of rows, everyone moved on. Weeks later finance notices last quarter's closed-won total is wrong because a few hundred Opportunities silently failed a validation rule nobody remembered was active.
There was no rollback plan. Salesforce has no "undo last load" button. Without the created record IDs, or an export of the fields you overwrote, you cannot reverse a load cleanly. You can only patch forward.
Everything below prevents those four failures. If data is one workstream in a bigger project, pair this with the Salesforce implementation checklist.
1. Pre-migration planning
Build a real field mapping document
One row per source field, not per target field. Starting from the source turns every field you skip into a recorded decision rather than an omission you find later. Each row carries:
- Source object, field name, data type, max length
- Target object and field API name, with type, length, and required flag
- Transformation rule (trim, concatenate, date format, unit conversion, value remap)
- Who signed off, and the disposition if not migrating: archive, drop, or hold in staging
Type mismatches cause most of the pain. A 500-character description going into a 255-character field truncates or fails depending on the API. A local-time date-time shifts unless you normalize to UTC. A source picklist with 40 values going into a restricted Salesforce picklist with 12 rejects every unmatched row.
Add an External ID field to every object
The highest-leverage decision in the plan. On each target object, create a text field marked External ID and Unique, and load the source system's primary key into it. Three things become possible:
- Loads become idempotent. Re-running as an upsert on the External ID updates the existing record instead of duplicating it, so you can rerun a failed batch without cleaning up first.
- Relationships resolve without an ID map. Data Loader can populate a lookup or master-detail field by referencing the parent's External ID instead of its Salesforce ID.
- Rollback has a handle. You can identify exactly which records this migration created.
Audit the data before you touch Salesforce
Cleaning in the source, or in a staging layer, is cheaper than cleaning in Salesforce after the fact. Run these checks and fix them upstream:
- Duplicates. Exact and fuzzy, on the keys that matter: email for Contacts, domain or normalized name for Accounts.
- Required-field gaps. Count the source rows that would fail a required field or validation rule, before the load tells you.
- Picklist mismatches. Unrestricted picklists accept values that are not in the list, producing records that look fine and break every report grouping.
- Referential integrity. Child rows whose parent key is missing from the parent extract become orphans on the Salesforce side.
- Owner mapping. Every record needs an active user as owner. Decide early where records owned by departed staff go.
- Encoding and whitespace. Non-UTF-8 characters, stray spaces, and embedded line breaks cause a large share of "unexplained" failures.
Finally, map the dependency graph: which objects look up to which. Include self-relationships and junction objects, since those cannot resolve in a single pass.
2. Load order
Load order follows the relationship graph. A child cannot reference a parent that does not exist yet. Get it wrong and you get hard failures or, worse, silently empty relationship fields.
With users provisioned and record types, picklists, and reference data already configured, a typical CRM object graph loads in this sequence:
- Accounts
- Contacts
- Products and Price Book entries
- Opportunities
- Opportunity Line Items, which depend on both the Opportunity and the Price Book entry
- Cases
- Custom objects, in their own dependency order
- Activities, Tasks and Events, which reference nearly everything above
- Notes, attachments, and files, last, because they attach to records that must already exist
Two patterns need a second pass. Self-referencing hierarchies: load all Accounts with ParentId blank, then run an update pass to set ParentId once every record exists. Circular references: an Account whose primary contact lookup points at a Contact belonging to that Account. Load both, then update the lookup.
3. Migration execution
Dry run in a sandbox, twice
The first sandbox run finds structural problems: bad mappings, missing fields, failing validation rules. The second, after fixes, produces your timing estimate and your reconciliation baseline. One clean run is not enough.
Use a sandbox with a realistic data profile. A Developer sandbox validates mapping and load order but tells you nothing about timing or lock contention at volume. Full sandboxes have the longest refresh interval of any type, so schedule the refresh early.
Turn off what will fight you
Before a bulk load, temporarily disable validation rules legacy data cannot satisfy, flows and Apex triggers that fire on create or update, assignment and auto-response rules, and duplicate rules if you deduplicated upstream. Set email deliverability to system only so the load does not notify thousands of real customers. Write the list down as you go, because re-enabling is a checklist item, not a memory exercise.
To preserve original created and modified dates, enable the audit field permissions in Setup first. Setting them retroactively is not straightforward.
Pick the right tool honestly
Data Import Wizard is browser-based, with guided mapping and native duplicate-rule handling. It caps at 50,000 records per import, supports a limited object set (Accounts, Contacts, Leads, Solutions, Campaign Members, Person Accounts, custom objects), and has no delete or export. Right tool for a one-off list of a few thousand records into a supported object.
Data Loader is the default for migration work: every standard and custom object, insert through hard delete plus export, and a scriptable command-line mode. It needs a Java runtime and manual field mapping, and does no validation before it starts. See the Data Loader complete guide and the side-by-side comparison if you are still choosing. For very large or heavily transformed migrations, a dedicated ETL layer buys repeatable transformation logic and a real staging area.
Batch sizing and lock contention
Data Loader with the Bulk API supports up to 10,000 records per batch. The REST and SOAP path uses much smaller batches, 200 being the standard maximum. Bigger is faster until it is not: a batch that trips a timeout or a governor limit fails as a unit, so 10,000 is not automatically right for records with heavy trigger logic behind them.
Row lock contention is the failure mode that catches people out. When many child records share a parent, parallel processing can put two batches on the same Account at once. Sort the input by parent ID so those records land in the same batch, and fall back to serial mode where contention persists.
Keep every success and error file. The success file holds the IDs of the records you just created, and that file is your rollback handle.
4. Reconciliation
The load finishing is not the same as the load working. Reconcile on three axes.
Counts. Per object in Salesforce, versus the source extract, versus the success file. Then count by a meaningful grouping: per owner, per record type, per year of creation. A matching total can still hide an empty bucket.
Sums. For any numeric or currency field a report depends on, compare the sum. Total Opportunity Amount by stage and by close-date quarter is the standard check, because it is the number a finance stakeholder notices first.
Orphans and relationships. Query for child records whose parent lookup is null: Contacts with no AccountId, Opportunities with no Account, Line Items with no Opportunity. Check the reverse too. Verify roll-up summary and formula fields, which calculate after load and are an independent signal that relationships are intact.
Then spot-check by hand. Pick 20 to 30 records deliberately, not randomly: your largest Account, the deepest hierarchy, the oldest Opportunity, a record whose owner was remapped. Automated checks confirm shape, manual checks confirm meaning. Have a business stakeholder sample records they know well. They catch what a technical reconciliation cannot, like a status that mapped to a valid but semantically wrong picklist value.
5. Cutover
Cutover is a sequence with a time box, not an event. Announce the freeze early and more than once, with exact times. The most common cutover problem is someone typing into the old system during the freeze.
- Freeze writes in the source system at a stated time
- Take the final full extract
- Export the target org's current state, if the org is not empty
- Run the final load in the agreed order
- Reconcile against the checks you rehearsed in the sandbox
- Re-enable validation rules, flows, triggers, assignment rules, and email
- Verify sharing and record visibility for a sample user from each profile
- Go or no-go against the pre-agreed criteria
- Open the org, with a named support channel for day one
If the business cannot fully stop during the freeze, plan a delta load: records created or modified in the source after the main extract, upserted on External ID once the main load lands. Fix the cutoff timestamp in advance and name who owns the delta.
Write the go/no-go criteria before cutover day. At 2am, with stakeholders waiting, nobody makes a good call on whether a 3 percent variance is acceptable.
6. Rollback
Rollback in Salesforce is rarely a clean undo. What is reversible depends entirely on what the load did.
Inserts are reversible, if you prepared. The success file holds the ID of every record created. Feed it back as a delete and you remove exactly what you added. Two caveats: deleting a parent cascades to children in master-detail and some standard relationships, so delete in reverse load order. And the Recycle Bin has both a retention period and a size cap, so very large deletes may not be fully recoverable from it.
Updates are only reversible if you took a backup first. There is no field-level version history beyond fields with Field History Tracking enabled, and tracking has per-object field limits. If your migration updates existing records, export the current values of every field you will touch, keyed by record ID, before you load. Without that file, rollback means manual reconstruction.
Some things do not roll back at all. Automations that fired during the load and sent emails, created tasks, or pushed data downstream have already had their effect. Deleting the record does not recall the email.
What should trigger a rollback decision
Agree these thresholds before cutover and write them down:
- Record count variance above tolerance on a core object, after investigation
- Material variance in a reconciled financial sum
- Orphaned child records above tolerance that cannot be repaired forward
- Sharing or ownership misconfigured such that users see records they should not
- Failure volume high enough that patching forward takes longer than reloading clean
- Corruption in a field that feeds a downstream integration
Note what is not on that list: a handful of failed records with a known cause and a known fix. Most problems get patched forward. Rollback is for when the org's state is untrustworthy, not when it is imperfect.
Rehearse the rollback in the sandbox. A plan that has never been executed is a document, not a plan.
The checklist
Planning
- Field mapping document, one row per source field, business sign-off
- Unique External ID field on every object, holding the source key
- Duplicates, required-field gaps, and picklist mismatches fixed upstream
- Owner map complete, every owner an active user
- Relationship graph documented, self-references and junctions included
Load order
- Sequence written down, parents before children
- Second-pass updates identified for hierarchies and circular references
- Activities, notes, and attachments scheduled last
Execution
- Sandbox dry run completed, then repeated after fixes
- Validation rules, flows, triggers, assignment and duplicate rules disabled, with a written re-enable list
- Email deliverability set to system only, audit field permissions enabled
- Batch size set, input sorted by parent ID, serial mode where contention appears
- Every success and error file retained
Reconciliation
- Counts match across source, success file, and org
- Counts by owner, record type, and period checked
- Currency sums match on report-critical fields
- Orphan query run on every child object
- Roll-up and formula fields calculating correctly
- 20 to 30 records spot-checked, plus a business stakeholder sample
Cutover
- Freeze window announced more than once, with exact times
- Pre-load export of target org state taken
- Delta load plan defined, cutoff timestamp and owner named
- Automations and email deliverability re-enabled after load
- Sharing and visibility verified per profile
- Go/no-go criteria agreed in writing beforehand
- Day-one support channel named and staffed
Rollback
- Pre-load field-level backup for every record the load will update
- Success files retained as the delete handle for inserts
- Reverse-order delete sequence documented for cascades
- Rollback thresholds agreed and written down
- Rollback rehearsed once in the sandbox
Where AI helps, and where it does not
The parts of a migration that reward AI assistance are high-volume and pattern-based: profiling a source extract for field-level quality problems, finding duplicate clusters across inconsistent formatting, normalizing picklist and address values, and running reconciliation queries after every load pass instead of only at the end.
Clientell's agent works on those pieces. You describe the check or the cleanup in plain English, it runs against your org, shows you what it found, and waits for your approval before changing anything. It compresses the audit and reconciliation loops, the steps teams skip when a deadline is close.
It does not make the judgment calls. Which legacy fields matter, what the go/no-go thresholds are, and whether to roll back or patch forward all need someone who understands the business. If you want that judgment alongside the execution, our Salesforce implementation services team runs migrations with this checklist as the working document.
The migrations that go quietly are not the ones with the best tooling. They are the ones that mapped every field, loaded in the right order, reconciled before anyone asked, and knew what to do if it went wrong.
