Data migration is one of the most consequential operations an enterprise can undertake within its Salesforce ecosystem. Whether you’re consolidating orgs after a merger, migrating records off a legacy on-premise CRM, or running a phased cloud-to-cloud transfer, the preparation looks the same. And so do the failure points. Four hours into the migration window, records start failing. Validation rules reject perfectly clean data. Automations fire email notifications to real users mid-load. Storage hits its ceiling before the Contacts object is even finished. Nobody planned for any of it.
Most Salesforce data migration failures don’t happen during the migration. They happen because of decisions or the absence of them, made in the weeks before. Governor limits nobody checked. Automations nobody disabled. Owner assignments that made thousands of records invisible after the load. All of it is preventable.
This article walks through the foundational work that separates a clean Salesforce data migration from a chaotic one: API selection, governor limit planning, metadata inventory, storage projection, and the automation decisions that even experienced teams can trip up on.
Table of Contents
Understanding the Salesforce Data Landscape
What Makes Salesforce Data Migration Uniquely Complex
Unlike a conventional relational database migration, where data moves from one schema to another via standard ETL pipelines, Salesforce data migration must account for a rich layer of platform-native constructs: object relationships enforced by the platform (Master-Detail and Lookup), formula fields that cannot be written directly, workflow rules and Process Builder automations that fire on record insert and update, sharing rules that affect which records are visible to integration users, and a sophisticated governor limit framework that hard-caps API calls, CPU time, heap size, and DML operations within a 24-hour rolling window.
Because Salesforce is a multi-tenant SaaS platform, no customer has direct access to the database. Every create, read, update, and delete operation must pass through the platform API layer, which means your migration throughput is fundamentally bounded by API limits, not by network bandwidth or disk I/O. Understanding this constraint is the first and most important mental model shift a migration team must internalise.
Core Salesforce API Taxonomy
| API | Protocol | Max Batch Size | Async? | Best Use Case |
|---|---|---|---|---|
| REST API | HTTP/JSON or XML | 200 records (composite) | No | Real-time, low-volume CRUD |
| SOAP API | HTTP/XML (WSDL) | 200 records | No | Legacy integrations, ERP connectors |
| Bulk API 1.0 | HTTP/CSV or XML | 10,000 records/batch | Yes | Large-volume data loads |
| Bulk API 2.0 | HTTP/CSV or JSON | 150 million rows/job | Yes | Large-volume, simplified lifecycle |
| Composite REST | HTTP/JSON | 25 subrequests | No | Orchestrated multi-object inserts |
| GraphQL API | HTTP/GraphQL | Variable | No | Flexible field selection queries |
Governor Limits: The Non-Negotiable Ceiling
Every Salesforce org is subject to a set of per-24-hour API limits enforced at the platform level and cannot be increased by configuration alone (though Salesforce Add-On SKUs can raise some limits). Before scoping a migration project, you must audit these limits against your projected data volume.
| Governor Limit | Default Allocation | Window | Migration Impact |
|---|---|---|---|
| Total API Requests | 1,000 x user licences (min 5,000) | Rolling 24 hours | Primary constraint for REST/SOAP loads |
| Bulk API Batches | 15,000 batches | Rolling 24 hours | Governs Bulk API 1.0 batch counts |
| Bulk API 2.0 Jobs | Unlimited (records limited) | Rolling 24 hours | Governs Bulk API 2.0 concurrency |
| Concurrent API Requests | 25 (long-running) | Point in time | May cause 503 under heavy concurrency |
| SOQL Query Rows | 50,000 per transaction | Per transaction | Affects pre-migration data extraction |
| DML Statements | 150 per transaction | Per transaction | Affects trigger/automation interactions |
Pre-Migration Org Readiness Assessment
A successful migration begins weeks, sometimes months, before a single record is transferred. The pre-migration readiness phase is not an administrative formality; it is a risk mitigation exercise that exposes hidden landmines: circular dependencies between objects, validation rules that reject legacy data formats, picklist mismatches, and schema drift between source and target environments.
Metadata Inventory and Schema Analysis
The first deliverable of any readiness assessment is a comprehensive metadata inventory. This inventory captures every custom and standard object, field, relationship, validation rule, workflow, Flow, Process Builder process, trigger, and sharing configuration in the target org. Salesforce provides two primary mechanisms for this: the Metadata API and the Tooling API.
Retrieving Object Metadata via the Tooling API
# Retrieve all CustomObject metadata using Tooling API (REST)
GET /services/data/v59.0/tooling/query/
?q=SELECT+Id,DeveloperName,Label,DeploymentStatus
+FROM+CustomObject
+WHERE+DeploymentStatus='Deployed'
# Response (truncated)
{
"totalSize": 142,
"done": true,
"records": [
{
"Id": "01I5g000001XXXXX",
"DeveloperName": "Account_Migration_Log__c",
"Label": "Account Migration Log",
"DeploymentStatus": "Deployed"
}
]
}
Field-Level Metadata Extraction
For each object, retrieve the field-level metadata to understand data types, required flags, picklist values, and external ID eligibility. External ID fields are critical during migration because they allow upsert operations without first querying for Salesforce record IDs.
# Describe a specific object to retrieve field metadata
GET /services/data/v59.0/sobjects/Account/describe/
# Key response fields to evaluate per field definition:
# name : API field name
# type : string, double, picklist, reference, etc.
# externalId : true/false — critical for upsert mapping
# nillable : true/false — must match source null strategy
# createable : true/false — system fields cannot be written
# updateable : true/false — read-only fields cannot be patched
# picklistValues: enumerate all valid picklist entries before mapping
Data Volume and Storage Projection
Salesforce storage is metered separately for data storage (records) and file storage (attachments, files, and content versions). Before migration, you must project the post-migration storage footprint and compare it against your org’s allocated storage. Overrunning storage limits mid-migration causes job failures and data inconsistency.
| Storage Type | Default Allocation | Overage Action | Migration Consideration |
|---|---|---|---|
| Data Storage | 10 GB + 20 MB/licence | Purchase additional | Count all record rows, including junction objects |
| File Storage | 10 GB + 2 GB/licence | Purchase additional | ContentVersion records double-count body + metadata |
| BigObjects | Unlimited (indexed) | Included with the licence | Ideal for archiving deep-historical records |
Automation Bypass Strategy
One of the most consequential pre-migration decisions is determining which platform automations to disable during the migration window. When records are inserted via the API, Salesforce executes the full automation stack: Apex before-triggers, validation rules, Flows (before-save and after-save), workflow rules, Process Builder processes, and Apex after-triggers. During a bulk migration, this automation overhead can exhaust CPU time governor limits (10,000 ms per transaction), trigger cascading DML operations causing partial save failures, fire email alerts or Chatter notifications to end users prematurely, and invoke external callouts that fail due to migration-context payloads.
The recommended pattern is to implement a Custom Metadata Type bypass flag that all automations check before executing. This gives you surgical control over automation without requiring the deployment of changed code during the migration window.
// Apex Trigger: Automation Bypass Check Pattern
trigger AccountTrigger on Account (before insert, before update, after insert, after update) {
// Query bypass Custom Metadata (CMDT is cached -- no SOQL per invocation)
Migration_Bypass__mdt bypassConfig = Migration_Bypass__mdt.getInstance('Default');
if (bypassConfig != null && bypassConfig.Bypass_Account_Trigger__c) {
System.debug('AccountTrigger: bypass active -- skipping all handler logic');
return; // Exit immediately; no automation fires
}
// Normal trigger handler dispatch
AccountTriggerHandler handler = new AccountTriggerHandler();
if (Trigger.isInsert && Trigger.isBefore) handler.onBeforeInsert(Trigger.new);
if (Trigger.isInsert && Trigger.isAfter) handler.onAfterInsert(Trigger.new);
if (Trigger.isUpdate && Trigger.isBefore) handler.onBeforeUpdate(Trigger.new, Trigger.oldMap);
if (Trigger.isUpdate && Trigger.isAfter) handler.onAfterUpdate(Trigger.new, Trigger.oldMap);
}
Sharing and Security Model Preparation
The Salesforce sharing model determines record visibility and can affect migration behaviour in subtle ways. When an integration user inserts a record, its Owner is set to that user unless an OwnerId is explicitly supplied in the payload. If your org uses private Org-Wide Defaults (OWD) on the Account or Opportunity object, inserting records owned by the integration user effectively hides them from all other users until Sharing Rules or manual shares are applied.
The migration plan must therefore include an explicit Owner assignment strategy. This typically means including a mapped OwnerId or Owner.Username field in every record payload, with a pre-migration user mapping table that resolves source-system user identifiers to their corresponding Salesforce User IDs.
// User Mapping Pre-Query (SOQL: run before migration starts)
SELECT Id, Username, IsActive, Profile.Name
FROM User
WHERE IsActive = true
AND Profile.Name IN ('Sales Representative', 'Sales Manager', 'System Administrator')
ORDER BY Username ASC
// Persist mapping as: { legacyUserId -> salesforceUserId }
// Use this map to populate OwnerId in every migrated record row
The Mistakes That Show Up Every Time
These aren’t unusual edge cases. They appear on most migrations that skip a formal readiness phase:
- Skipping metadata inventory until schema mismatches surface mid-load
- Not projecting storage, then hitting the ceiling during file migration
- Leaving automations active and watching CPU time limits fail across the first object
- Missing External ID fields, making every retry a duplicate risk
- Forgetting owner mapping, then finding that thousands of records are invisible post-load
- Using the main admin account as the integration user, which muddies API consumption reporting
Most of these have the same root cause: readiness treated as paperwork rather than engineering.
What’s Next in This Series
This is Article 1 of a five-part series on Salesforce data migration. Next up: OAuth 2.0, Named Credentials, and Connected App configuration, the authentication architecture that every server-to-server migration integration needs to get right before a single API call is made.
After that, the series covers Bulk API 2.0 implementation in depth, retry logic and idempotency patterns, and finally compliance, validation, and cutover procedures.
Final Thoughts
A Salesforce data migration is not just a data problem. It’s a platform problem. The data is usually fine. What fails is the preparation, the limit analysis that wasn’t done, the automations that weren’t bypassed, and the external ID fields that weren’t in place.
The readiness assessment isn’t the part that comes before the migration. Done properly, it is the migration.
Frequently Asked Questions (FAQ)
A pre-migration audit that evaluates your org’s metadata, storage capacity, API limits, automation stack, and sharing model to identify risks before any data is moved.
Bulk API 2.0 is the recommended choice for any migration exceeding 200 records. It supports asynchronous processing, up to 150 million rows per job, and a simplified lifecycle compared to Bulk API 1.0.
Governor limits cap API requests, concurrent connections, SOQL query rows, and DML operations within a rolling 24-hour window. Exceeding them mid-migration causes failures, which is why pre-migration limit auditing is essential.
The recommended pattern is a Custom Metadata Type bypass flag checked by all triggers and Flows. Validation rules require a formula-based bypass or temporary deactivation in Setup.
An External ID is a custom, indexed, unique field that allows Salesforce to match incoming records against existing ones during an Upsert operation — preventing duplicates even when retries occur.

Kiran Sreeram Prathi
I’m Kiran Sreeram Prathi, a Salesforce Developer dedicated to building scalable, intelligent, and user-focused CRM solutions. Over the past five years, I’ve delivered Salesforce implementations across healthcare, finance, and service industries—focusing on both technical precision and user experience. My expertise spans Lightning Web Components (LWC), Apex, OmniStudio, and Experience Cloud, along with CI/CD automation using GitHub Actions and integrations with platforms such as DocuSign, Conga, and Zpaper. I take pride in transforming complex workflows into seamless digital journeys and implementing clean DevOps strategies that reduce downtime and accelerate delivery. Recognized by organizations like Novartis, WILCO, and Deloitte, I enjoy solving problems that make Salesforce work smarter and scale better. I’m always open to connecting with professionals who are passionate about process transformation, architecture design, and continuous innovation in the Salesforce ecosystem.
- This author does not have any more posts.







