Best Practices for Migrating from Salesforce to Dynamics 365 (Dataverse)

A technical guide to planning and executing a large-scale Salesforce to Dataverse migration, from data mapping and record IDs to attachments, audit history, and cutover.

A step-by-step technical guide for planning and executing a large-scale migration from Salesforce to Dynamics 365 (Dataverse). Covers data mapping, preserving record IDs, handling attachments, managing audit history, and avoiding common pitfalls. Solzet is a Microsoft Dynamics 365 Customer Engagement and Power Platform consultancy headquartered in Yerevan, Armenia, and cross-platform data migration is core to the implementation and project rescue work we deliver. The practices below are the ones we apply on real client environments, so they focus on the Dataverse mechanics that actually decide whether a migration lands cleanly rather than a generic checklist.

Common pitfalls to avoid

Treating it as a straight copy instead of a remodel

Salesforce and Dataverse do not share a data model. Objects become tables, fields become columns, picklists become choices, and Salesforce master-detail and lookup relationships have to be re-expressed as Dataverse lookups or many-to-many relationships. Trying to lift and shift the schema one to one produces a target that fights the platform. The mapping has to be designed deliberately, table by table, before a single row moves.

Losing record identity and breaking relationships

Dataverse keys every row on a GUID, while Salesforce uses its own 15 and 18 character Ids. If you do not carry the original Salesforce Id across, child records cannot find their parents and integrations that referenced the old Ids break. The fix is to keep the source Id as a dedicated column with an alternate key so relationships resolve reliably and re-runs stay idempotent.

Ignoring attachments and files until the end

Salesforce Notes, Attachments, and Files (ContentVersion and ContentDocument) do not come across with the record data and are usually the largest and slowest part of the move. Left to the end they blow the timeline. They need their own plan up front: where they land in Dataverse (notes, file columns, or SharePoint), how they are batched, and how throughput and size limits are handled.

Expecting to write into the Dataverse audit log

Dataverse audit history is system managed and cannot be back-filled with historical Salesforce field-history rows. Teams that assume they can rewrite the audit trail lose that history. The workable approach is to preserve original created and modified dates on the live records and archive the historical change log into a read-only table or an external store, so the past is retained without pretending it was audited in Dataverse.

Running the load with automation live

Leaving plugins, real-time workflows, Power Automate flows, and duplicate detection switched on during a bulk load fires side effects on every row, overwrites created dates with load dates, corrupts owner assignment, and slows the whole run to a crawl. Automation has to be disabled for the migration and re-enabled deliberately afterwards, with created and modified metadata written explicitly.

No reconciliation, so nobody trusts the result

Migrating without record counts, control totals, and spot checks means defects surface only after go-live, when the business has already started working in the new system. Every load needs a reconciliation step that compares source and target counts per table, samples key records field by field, and reports what did not import and why before cutover is signed off.

Step-by-step: plan and execute the migration

Work through these in order. The early steps profile the source and design the target; the later steps move the data in dependency order, preserve its history, and validate it before go-live.

  1. Profile the Salesforce org before you design anything

    Inventory every object, custom field, picklist, record type, relationship, and automation in scope, along with row counts and file volumes. Identify what is actually used versus what has accumulated over the years. This profile decides what is worth migrating, what should be archived, and where the real volume and complexity sit, so the plan is built on the org as it is rather than as it is assumed to be.

  2. Design the Dataverse target model and field-level mapping

    Map each Salesforce object to a Dataverse table and each field to a column and data type, converting picklists to choices, record types to the equivalent Dataverse pattern, and Salesforce relationships to Dataverse lookups or many-to-many relationships. Decide owner, business unit, and security mapping at the same time. Document the mapping table by table so it becomes the contract that both the build and the migration scripts follow.

  3. Set the record-ID strategy up front

    Add a dedicated column such as Salesforce Id on every migrated table and define it as an alternate key. Loading the original Id lets you resolve parent-child relationships by the source key, keep the migration idempotent so failed batches can be safely re-run, and preserve references from any integration that still points at the old Ids. Where you need deterministic GUIDs, set the Dataverse primary key explicitly on create rather than letting it be generated.

  4. Sequence the load by dependency

    Load reference and parent data before the records that depend on it: users and business units first, then reference tables, then parents ahead of children. Resolve every relationship through the alternate key on the source Id rather than trying to know the new GUIDs in advance. A correct sequence is what prevents orphaned child records and lets large tables load in dependency order without re-work.

  5. Migrate notes, attachments, and files as their own workstream

    Move Salesforce Notes, Attachments, and Files into their chosen Dataverse home (notes, file columns, or SharePoint document locations) in batches, associated back to their parent records through the source Id. Plan for base64 size, storage, and API throughput so the file volume does not stall the run, and reconcile file counts against the source the same way you reconcile records.

  6. Preserve created dates and archive the history

    Write the original creation date to the created-on field using the supported override so records keep their true age, and map created-by and modified-by to the migrated users where possible. For field-level history, load the Salesforce change log into a read-only archive table or external store rather than attempting to write the Dataverse audit log, which is system managed. This keeps history available without misrepresenting it.

  7. Disable automation, load in batches, then re-enable

    Turn off plugins, real-time workflows, Power Automate flows, and duplicate detection for the migration so no side effects fire and metadata is not overwritten. Load in controlled batches with retry and logging so throttling and transient failures are handled cleanly. Once the data is in and validated, re-enable automation deliberately and confirm it behaves correctly against the migrated records.

  8. Reconcile, validate, and run a controlled cutover

    Compare source and target counts per table, sample key records field by field, and produce an exception report of anything that did not import and why. Rehearse the full run in a sandbox, then plan a cutover with a final delta load so records changed in Salesforce during the migration window are brought across. Only sign off go-live once reconciliation is clean and the business has validated its critical processes.

Frequently Asked Questions

How do you preserve record IDs when migrating from Salesforce to Dynamics 365?

Dataverse keys every row on a GUID and does not reuse the Salesforce 15 or 18 character Id, so the practice is to store the original Salesforce Id in a dedicated column and define it as an alternate key. Relationships are then resolved by that source Id, which keeps parent-child links intact, lets integrations that still reference the old Ids keep working, and makes the migration idempotent so failed batches can be re-run safely. Where deterministic GUIDs are needed, you can set the Dataverse primary key explicitly on create.

How are Salesforce attachments and files migrated to Dataverse?

Salesforce Notes, Attachments, and Files (ContentVersion and ContentDocument) are a separate workstream from the record data because they are usually the largest and slowest part of the move. They are migrated in batches into their chosen Dataverse home, which is typically notes, file columns, or a SharePoint document location, and associated back to their parent records through the source Id. The plan has to account for base64 size, storage, and API throughput, and file counts are reconciled against the source just like records.

Can Salesforce audit history be migrated into the Dynamics 365 audit log?

No. The Dataverse audit log is system managed and cannot be back-filled with historical Salesforce field-history rows. The supported approach is to preserve the original created and modified dates on the live records so they keep their true age, and to archive the Salesforce change log into a read-only table or an external store. That keeps the historical record available for reference without misrepresenting it as native Dataverse audit data.

What are the most common pitfalls in a Salesforce to Dynamics 365 migration?

The recurring ones are treating the move as a straight schema copy instead of a deliberate remodel, losing record identity so relationships break, leaving attachments and files until the end, expecting to write into the audit log, and running the load with plugins and flows still live so side effects overwrite created dates and owners. Skipping reconciliation is the other big one, because defects then surface only after go-live. Disabling automation during the load and reconciling counts before cutover avoids most of them.

Can Solzet run a large Salesforce to Dynamics 365 migration?

Yes. Solzet is a Dynamics 365 Customer Engagement and Power Platform consultancy based in Yerevan, Armenia, and cross-platform data migration is part of our implementation and project rescue work. We profile the source org, design the Dataverse target model and mapping, build the ID and relationship strategy, migrate records and files in dependency order, preserve created dates, and reconcile before cutover. We deliver directly or on a B2B and white-label basis for other Microsoft partners.

Planning a move off Salesforce?

Solzet plans and runs Salesforce to Dynamics 365 migrations as part of our Customer Engagement and Power Platform delivery. Tell us the size and shape of your Salesforce org and we will map it to Dataverse and move it cleanly, directly or on a white-label basis for your team.