Migrating to Dynamics 365 from a Legacy or Custom CRM: Scoping the Risk Before the Price
Why a credible fixed price needs discovery first, how to compare migration quotes that price different scopes, and the load mechanics and contract terms that protect your data.
Why can nobody quote a credible fixed price for a Dynamics 365 migration before discovery? Because the price depends on facts nobody has measured yet: how many duplicate keys and orphaned references the legacy CRM holds, how complete each field really is, which undocumented scripts and integrations write to it, and what will not migrate at all. Discovery must output a data health report, a script and integration inventory and a signed-off list of what stays behind. Only then can a partner price the mechanics that protect your audit trail: preserved created dates, plugins and flows disabled during the load, staged loads reconciled by count, and a measured dry run, all backed by contract protections.
Why can a credible fixed price not come before discovery?
A fixed price is a promise about effort, and in a data migration the effort sits almost entirely in the condition of the source, not in the size of the destination. Two legacy CRMs with the same number of records can need very different amounts of work, because one has clean keys and documented fields and the other has fifteen years of customisations that nobody who built them still remembers.
When a partner quotes a fixed price from a questionnaire and a demo, one of three things is happening: the price carries a large hidden contingency, the scope is narrower than you think, or the risk will come back to you later as change requests. None of those is dishonest in itself, but none of them is a fixed price you can plan against. The honest sequence is a short, separately priced discovery that measures the unknowns, followed by a fixed price for the migration that is tied to what discovery found.
The unknowns that move the price most are consistent from project to project:
- Duplicate and unstable keys: records merged by hand, reused IDs, and accounts that exist three times under slightly different names. Every duplicate needs a survivorship rule before anything loads.
- Orphaned references: activities pointing at deleted contacts, opportunities pointing at accounts that no longer exist, and lookups stored as free text. These decide whether relationships can be rebuilt or must be parked.
- Field-level completeness: the columns that look mandatory but are empty in older records, and the ones that were repurposed years ago to hold something else entirely.
- Undocumented customisations: stored procedures, triggers, scheduled jobs and scripts in the legacy CRM that change data behind the application, and which someone expects to keep working.
- Integrations: every system that reads from or writes to the CRM, what it authenticates as, and whether it can be pointed at Dynamics 365 or has to be rebuilt.
- History depth and files: how many years of activities, emails and attachments the business really needs in the live system, as opposed to in a searchable archive.
What must a migration discovery produce before anyone can fix the price?
Discovery is only useful if its outputs are written down, owned by you and specific enough that a different partner could price the migration from them. If a discovery ends with a slide deck and a number, it has not done its job. These are the outputs to insist on:
| Discovery output | What it contains | Why it changes the price |
|---|---|---|
| Data health report | Per table: row counts, duplicate key counts, orphaned reference counts, field-level completeness, invalid values against the target choice lists, and date ranges of real activity. | Turns cleansing from a guess into a measured list of defects, each with a proposed rule and an owner. |
| Script and integration inventory | Every trigger, stored procedure, scheduled job, export, import and API client touching the CRM, with direction, frequency, credentials and the business owner who depends on it. | Each integration is its own workstream; the undocumented ones are where fixed prices usually break. |
| Source-to-target mapping | Legacy tables and columns mapped to Dataverse tables and columns, choice value mappings, the legacy ID kept as an alternate key, and the user and team mapping including leavers. | Shows how much is configuration and how much needs custom tables, columns or transformation code. |
| What will not migrate | A signed-off register of data, history, files and behaviour that stays behind, with the reason and where it will live instead (archive, export, or retired). | Stops the scope from growing silently, and stops a quote from looking cheap because it left things out. |
| Volume and capacity estimate | Rows per table, attachment volume and size, and the expected effect on Dataverse database and file capacity. | Drives load design, the number of staged runs and whether extra storage capacity has to be bought. |
| Reconciliation and acceptance definition | The counts, control totals and sample checks that will prove each stage, and who in the business signs them off. | Makes "done" measurable, which is what allows a fixed price to be fixed. |
Why do quotes for the same Dynamics 365 migration differ so much?
Because they are almost never quotes for the same migration. Each bidder reads the same brief and silently decides how many years of history to move, whether attachments are included, who cleans the data, how many rehearsals to run and who pays when a load fails. A quote that looks half the price of another is often pricing half the scope, with the rest either excluded in small print or assumed to be your team's job.
Timelines differ for the same reason. A plan that goes straight from mapping to a single cutover weekend will always look shorter than one with staged loads and a full-volume dry run, right up until the cutover weekend runs out of weekend. The way to compare bids is to force every bidder onto the same scope lines, which is what the grid below is for.
How do you normalise Dynamics 365 migration quotes so they can be compared?
Send every bidder the same grid and ask them to answer each line explicitly: included, excluded, or assumed to be done by you. Any line left blank is excluded until proven otherwise. The third column is where the gap between a low and a realistic quote usually hides.
| Scope line | Question to put to every bidder | What a low quote often leaves out |
|---|---|---|
| Entities in scope | Which legacy tables map to which Dataverse tables, including custom tables and many-to-many relationships? | Custom and junction tables, notes, and anything not visible in the legacy user interface. |
| Volumes | What row counts and file sizes is the price based on, and what happens if actual volumes are higher? | A tolerance band; the quote assumes the demo database, not production. |
| History depth | How many years of activities, emails and closed records are migrated live, and what goes to an archive? | Closed and historical records, or the archive the business still needs to search. |
| Attachments and files | Are attachments, email bodies and documents migrated, into notes, file columns or SharePoint, and at what volume? | Files entirely, or files beyond a size limit, which are usually the slowest part of a migration. |
| Integrations | Which integrations are re-pointed, which are rebuilt, and which are retired, and who tests each one end to end? | Everything outside the CRM itself, including undocumented scripts and scheduled jobs. |
| Customisations and logic | Which legacy triggers, scripts and workflows are rebuilt in Dynamics 365 as plug-ins, flows or business rules? | Rebuilding behaviour, which is build work rather than data work and is often priced as neither. |
| Data cleansing | Who deduplicates, fixes orphans and fills missing values, against which rules, and before or during the migration? | Cleansing, left to your team with no time allowed for it. |
| Audit trail | How are created dates, created by, owners, legacy IDs and legacy change history preserved? | Created dates, so every record appears to have been created on migration day. |
| Users and security | How are legacy users, leavers, teams and ownership mapped, and what is the fallback owner? | Leavers and inactive owners, which break impersonated loads on the day. |
| Rehearsals | How many full-volume dry runs are included, and what is measured in each? | Any rehearsal at full volume. |
| Reconciliation | Which counts, control totals and referential checks prove each stage, and who signs them off? | Reconciliation beyond "the import job reported success". |
| Cutover and delta | How are records changed in the legacy CRM after the bulk extract caught up, and is there a rollback point? | The delta load and the rollback plan. |
| Who fixes a failed load | If a load fails or reconciliation does not match, who diagnoses it, who pays for the rerun, and how fast? | The rerun, which becomes a change request. |
| Licences and capacity | Are Dynamics 365 licences or extra Dataverse capacity in the price at all? | Usually excluded, which is correct; buy licences separately, as the licensing and procurement guide explains. |
| After go-live | How long are migration defects fixed at no cost, and who answers users who say records are missing? | Any warranty on data defects. |
What does scoping look like for 15 years of data, 8 integrations and 600 users on Dynamics 365 Online?
A brief of that shape is common, and it is a good example of why the questions matter more than the headline numbers. Fifteen years of data does not mean fifteen years in the live system: the first scoping decision is how much history users actually work with, and whether the rest belongs in a read-only archive that stays searchable. That single decision usually moves volume, load duration and storage capacity more than anything else on the list.
Eight integrations are eight workstreams, not one line item. Each needs its direction, authentication, error handling and test owner established, and any that wrote directly into the legacy database has to be redesigned against the Dataverse API rather than re-pointed. Six hundred users means a user and team mapping that has to account for everyone who ever owned a record, including people who left years ago, because ownership and impersonated created by values depend on those users existing in Dataverse at load time.
Dynamics 365 Online adds two platform realities a quote should account for: Dataverse service protection limits throttle how fast data can be written through the API, so load design and parallelism affect the cutover window, and database and file capacity are finite, so attachment volume is a cost question as well as a technical one. The load techniques themselves are covered in our Dataverse bulk import guide.
What belongs on a pre-migration checklist for Dynamics 365?
Work through this before signing a fixed price, not after. Every item either removes a risk or turns it into a line on the quote.
- Name a business owner for the data, with authority to decide what is migrated, archived or dropped.
- Confirm the legacy CRM stays licensed, supported and readable until well after go-live, and that you hold full database access, not only application exports.
- Take and verify a restorable backup of the source, and agree the date the source becomes read-only.
- Produce the data health report: duplicate keys, orphaned references and field-level completeness per table.
- Inventory every script, trigger, scheduled job and integration that reads from or writes to the CRM, and find the undocumented ones by reading the database, not by asking.
- Agree the history depth for activities, emails and closed records, and where older history will live.
- Measure attachment count and size, and decide on notes, file columns or SharePoint.
- Build the user and team mapping, including leavers, and agree the fallback owner for inactive users.
- Agree the target data model in Dynamics 365 before mapping starts, including the alternate key that will hold each legacy ID.
- Write the what-will-not-migrate register and get it signed off.
- Define the reconciliation checks and acceptance thresholds for every stage, and who signs them.
- Plan the full-volume dry run, the cutover window, the delta load and the rollback point.
- Keep licensing separate: settle seat numbers and the buying route using the procurement guide, not inside the migration contract.
How is the audit trail preserved when legacy data is loaded into Dataverse?
This is where migrations most often lose the trust of users and auditors, so ask every bidder how they handle each row below. The step-by-step execution, including the privileges and request settings involved, is in our Salesforce to Dynamics 365 data migration guide; the same platform behaviour applies whatever the legacy CRM is.
The point that catches most buyers out is last modified. Dataverse has no supported way to set modifiedon or modifiedby directly. They are stamped with the time of the write and the calling user, and they change again the next time anyone edits the record, so they were never a reliable audit record in the first place. The usual, supportable approach is to carry the legacy values into custom columns such as legacy modified on and legacy modified by, show them on forms and views, and let users know which fields to trust. A pre-operation plug-in that overwrites the native value is a technique some migrations use, but it is not documented platform behaviour, so treat it as an explicit, written risk decision rather than a silent default.
| What you want to keep | Can it be preserved? | How it is typically handled |
|---|---|---|
| Created date | Yes, on create only | Set overriddencreatedon in the create request; Dataverse writes the value into createdon. It cannot be applied after the record exists, so a table loaded without it has to be reloaded. |
| Created by | Yes, with impersonation | Create the record as the mapped legacy user. The migration account is recorded in created on behalf of, and the impersonated user must exist and be enabled at load time. |
| Last modified date and user | No, not directly | Native modifiedon and modifiedby reflect the load. Carry legacy values in custom columns; impersonation at least makes modified by the mapped user rather than the migration account at load. |
| Owner | Yes | Set the owner on create through the user and team mapping, with an agreed named fallback team for inactive owners. |
| Legacy record ID | Yes | Store it in a column defined as an alternate key, so relationships resolve reliably and loads can be rerun as upserts without creating duplicates. |
| Legacy change history | Not into Dataverse auditing | Legacy audit logs cannot be written into the Dataverse audit history. Load them into a custom history table or keep them in a searchable archive, as agreed in discovery. |
| Closed and inactive status | Yes, with a separate pass | Create records open, then set status in a later pass, because closed records cannot be edited and some status transitions have their own rules. |
Why should plugins and flows be disabled during the load?
Logic written for a person typing into a form does not expect hundreds of thousands of historical records arriving at once. Left running, plug-ins, real-time workflows, Power Automate flows, business rules, duplicate detection and SLAs will slow the load dramatically, send emails and notifications about records that closed years ago, create follow-up tasks nobody wants, and overwrite the very values you are trying to preserve, such as owners, statuses and dates.
The standard approach is to switch per-row custom logic off for the duration of each load, or where that is not possible, to use the request-level bypass options Dataverse offers for custom plug-in and flow execution, which need an elevated privilege on the migration account. Auditing is normally turned on again only after the load, so the system does not spend capacity recording its own migration. What a buyer should require is the list: every component disabled, in writing, with a re-enable order and a check after each one, because a flow left off after go-live is a silent production defect. The bulk import guide covers the throughput side in detail.
How should staged loads and reconciliation counts work?
A single big-bang load gives you one moment to discover that something is wrong, usually at the worst possible time. Staged loads in dependency order let each stage be reconciled before the next one builds on it. A typical sequence for a legacy CRM looks like this, and each stage has to reconcile before the next starts:
- Reference data and users: choice values, currencies, business units, teams and the user mapping. Reconcile every legacy owner to an active user or the agreed fallback.
- Parent records: accounts and contacts, loaded as upserts on the legacy ID alternate key. Reconcile row counts per table against the source extract, less the rows the what-will-not-migrate register excludes.
- Transactional records: leads, opportunities, cases and custom tables, with lookups resolved to their parents. Reconcile counts and control totals, such as the sum of opportunity values per year, and report every record whose parent did not resolve.
- Activities and history: tasks, phone calls, appointments and emails, within the agreed history depth. Reconcile counts per type and per year, because gaps tend to cluster by date.
- Files: attachments and documents as their own stage. Reconcile file count and total size, and open a sample to confirm content, not just metadata.
- Status pass and delta: close records that should be closed, then load changes made in the legacy CRM since the extract, and reconcile again before re-enabling logic.
- Every stage produces a reconciliation report showing expected, loaded, rejected and excluded, with a reason for every rejected row. A stage is accepted when the numbers balance, not when the job finishes.
What makes a dry run measured rather than eyeballed?
An eyeballed dry run is someone opening a few records in the new system and saying they look fine. A measured dry run runs the full sequence end to end against full production volume in a sandbox, and records numbers that decide whether cutover can proceed:
- Row counts, control totals and rejected rows per table and per stage, compared with the acceptance thresholds agreed in discovery.
- Referential integrity: the number of records with an unresolved parent, owner or related record, which should be zero or explicitly accepted.
- Audit trail checks on a defined sample: created dates, created by and owners matching the source, and legacy modified values present in their custom columns.
- Elapsed time per stage and in total, including throttling retries, which is what turns the cutover plan from an estimate into a measured window.
- Re-enable checks: every disabled plug-in, flow and rule back on, and a test transaction through each integration.
- Business sign-off on scripted sample checks by the people who use the data, recorded against named records rather than general impressions.
- If the dry run misses its thresholds, fix and rerun it. A second dry run costs far less than a failed cutover.
Which contract protections should a buyer insist on in a migration contract?
These are the clauses and schedules that make a fixed price mean something. They are commercial points to raise with your own legal advisers, not legal advice.
- Discovery as a separate, fixed-scope engagement whose outputs you own outright and can take to any partner, including the data health report, the inventory, the mapping and the register of what will not migrate.
- The migration fixed price tied explicitly to the discovery baseline, with a stated volume tolerance and an agreed method for pricing changes outside it, so change requests are predictable rather than negotiable.
- Acceptance defined as reconciliation thresholds per stage, not as "load completed", with payment milestones attached to accepted stages.
- A clear rule on who fixes a failed load or a reconciliation mismatch caused by the partner, at whose cost and within what time.
- A stated number of full-volume dry runs included, with measured results reported in writing.
- A written list of logic disabled during the load and a re-enable checklist that forms part of the handover.
- Ownership of migration scripts, mappings and transformation logic transferring to you, so a rerun or a later correction does not depend on the same partner.
- A rollback point and a documented decision rule for invoking it during cutover.
- A warranty period for migration defects discovered after go-live, fixed at no additional cost.
- Licences and Dataverse capacity kept out of the services contract, bought through the route set out in the licensing and procurement guide.
Is Dynamics 365 the right destination for your legacy CRM at all?
Discovery sometimes shows that the real problem is not the platform but the data or the process, and that the destination deserves a second look. We recommend the right solution - whether that's Microsoft Dynamics 365, Power Platform, or a custom-built CRM. Some businesses need the Microsoft ecosystem. Others need full control without licensing. We deliver both.
If the legacy CRM is custom-built because the process is genuinely unusual, or per-user Microsoft licensing does not fit the budget or the user base, a custom CRM on React, Node.js, PostgreSQL or .NET can be the better home for the data, and the discovery outputs above apply just as well. If you already run Dynamics 365 and the migration is into an environment someone else built, a Dynamics 365 health check of that target first avoids loading clean data into a system with problems of its own.
Solzet migrates CRM data into Dynamics 365 Customer Engagement and Dataverse, runs the discovery described on this page, and can take over a migration another partner started through our project rescue and takeover service. We do not migrate or implement Dynamics 365 Finance, Business Central, Dynamics NAV, Finance and Operations or other ERP systems; where one is in play, we integrate with it rather than replace it.
Should the legacy CRM move to Dynamics 365 or to something else?
Can afford licensing and want the Microsoft ecosystem
Dynamics 365
Microsoft 365, Teams and Outlook integration, a mature partner ecosystem, Copilot, and apps for sales, service and field operations that are configured rather than built.
Need full control and zero licensing
Custom CRM
A CRM built on React, Node.js, PostgreSQL or .NET that you own outright: your data model, your hosting, no per-user subscription, and features shaped exactly to your process.
Not sure which fits
We help you decide
A short discovery weighs licensing budget, process complexity, integrations and long-term ownership, then recommends one path. We deliver both, so the recommendation has no reason to lean.
What do people ask us?
Can a partner give a fixed price for a Dynamics 365 migration from a legacy CRM without discovery?
They can give a number, but not a credible fixed price. Without measuring duplicate keys, orphaned references, field completeness, undocumented scripts and integrations, the quote either hides a large contingency, narrows the scope, or pushes the risk back to you as change requests. A short, separately priced discovery followed by a fixed price tied to its findings is the honest sequence.
What should a Dynamics 365 migration discovery deliver?
A data health report per table, a script and integration inventory, a source-to-target mapping including users and legacy IDs, a signed-off register of what will not migrate, a volume and capacity estimate, and the reconciliation checks that will define acceptance. You should own these outputs and be able to take them to any partner.
How do I compare different quotes for the same Dynamics 365 migration?
Put every bidder on the same scope lines: entities, volumes, history depth, attachments, integrations, customisations, cleansing, audit trail, users, rehearsals, reconciliation, cutover and delta, who fixes a failed load, and warranty. Ask for each line to be marked included, excluded or assumed to be your work. The realistic quote is the one whose scope matches yours, not the lowest number.
Can the original created and modified dates be kept when migrating to Dynamics 365?
The created date can, by setting overriddencreatedon when each record is created, and created by can be kept by creating records as the mapped legacy user. Modified on and modified by cannot be set directly; Dataverse stamps them at load time and on every later edit. The usual approach is to carry the legacy values in custom columns shown on forms and views.
Why are plugins and flows turned off during a Dynamics 365 data migration?
Because per-row logic slows the load, sends notifications about historical records, creates unwanted follow-up work and can overwrite the owners, statuses and dates you are preserving. Logic is disabled or bypassed with the request-level options Dataverse provides, recorded in a written list, and re-enabled in order with a check after each component.
Should all 15 years of CRM history move into Dynamics 365?
Rarely all of it live. Decide in discovery how much history users actually work with, migrate that into Dynamics 365, and keep older records in a searchable read-only archive. That decision usually has more effect on load time, storage capacity and cost than any other scoping choice.
Are Dynamics 365 licences part of a migration quote?
They should not be. Licences and extra Dataverse capacity are bought separately, directly or through a partner, and mixing them into a services quote makes bids harder to compare. Our licensing and procurement partner guide explains the buying routes and how partner margins work.
Can Solzet run discovery for a migration to Dynamics 365 from a legacy or custom CRM?
Yes. Our senior consultants and full-stack developers run migration discovery and delivery into Dynamics 365 Customer Engagement and Dataverse, and can take over a migration another partner started. We do not migrate Dynamics 365 Finance, Business Central, NAV or other ERP systems. If Dynamics 365 turns out not to fit, we also build custom CRM without Microsoft licensing.
Where should you go next?
Data migration guide: execution detail
Field mapping, overriddencreatedon, impersonation, files, tools, validation and cutover, step by step.
Dynamics 365 licensing and procurement
Buying licences direct or through a partner, and how partner margins work, kept separate from the migration quote.
Dynamics 365 health check
An independent audit of the target environment before clean data is loaded into it.
Dataverse bulk import strategy
Loading large volumes fast: bulk messages, disabling per-row logic, parallelism and throttling.
Project rescue and takeover
Taking over a stalled or failed Dynamics 365 implementation or migration from another partner.
Custom CRM Development
CRM on React, Node.js, PostgreSQL and .NET for organizations that need full control without Microsoft licensing.
Which solution is right for your business?
Tell us what you need. A senior consultant replies within one business day with a recommendation - Dynamics 365, Power Platform, or a custom-built CRM - not a sales script.