Most ERP migration budgets are built around data. How many years of history, how many legal entities, how many open documents. Data volume is easy to count, which is why it dominates the spreadsheet.
Then the programme starts, and the schedule slips somewhere else entirely: in the two hundred modifications nobody has owned since 2014. The question that actually decides your cost and your timeline is not how much data you are moving. It is how far your system has drifted from the product you licensed.
The three big vendors have converged on one answer
This is the genuinely new thing, and it is easy to miss because each vendor uses its own vocabulary for it.
Microsoft is the most blunt. Its Finance and Operations documentation states plainly that overlayering — editing the standard code in place — is not supported, and that extensions exist because they remove the code and metadata conflicts that made upgrades expensive.
SAP calls the same idea clean core, and has been tightening what qualifies. Its guidance directs extensions towards released APIs and the ABAP Cloud model, so that what you build survives an upgrade rather than blocking one; the ABAP extensibility guide is the current reference for what counts.
Oracle arrives from a different direction and lands in the same place. Fusion separates change into configuration, extension and personalisation, and those are preserved across release updates — which matters more in Oracle's world than anywhere else, because updates arrive four times a year on a fixed quarterly schedule. A heavily customised Fusion instance does not fail once. It fails every quarter.
Three vendors, three vocabularies, one architecture: the product stays standard, and your specifics live beside it.
Why this changes the shape of a migration
Under the old model, a customisation was a one-time cost. You paid for it, it worked, and it sat there.
Under the continuous-update model, every modification becomes a subscription. Someone has to regression-test it against each release, forever. That cost does not appear in a migration business case, because it is not a migration cost — it is the cost of the decisions you carry across.
This is why two companies of identical size, moving identical data volumes, can land eighteen months apart. The difference is rarely the data. It is the inventory of things that were built because the standard process did not fit, or because nobody had the authority to say no.
A way to classify what you have
Before costing a migration, sort every modification into four buckets. This is tedious and it is the single highest-value week of the whole programme.
| Bucket | What it is | What to do |
|---|---|---|
| Obsolete | Built for a process, a report or a regulation that no longer exists | Retire. Expect this to be 20–40% of the list, and verify by usage logs, not by asking |
| Now standard | The vendor has since shipped the capability natively | Replace with the standard function and delete the custom version |
| Genuinely differentiating | Encodes something your competitors cannot copy | Rebuild as a clean extension, outside the core |
| Compliance or integration | Statutory formats, bank files, customer EDI | Rebuild, but check whether a localisation pack or a partner connector already covers it |
The ordering matters. Teams usually start with the hardest bucket because it is the most interesting. Start with obsolete instead: it is the only bucket where the work is deletion, and every item removed is an item you never migrate, test, document or support again.
How to find out what is actually used
Opinions about which custom programmes matter are unreliable, including the opinions of the people who wrote them. Measure instead.
- Pull execution statistics for the last 24 months, not 12. Annual processes such as statutory reporting and year-end valuation will not appear in a single-year window, and deleting one of those is a painful way to learn this.
- Separate runs from users. A programme executed nightly by a batch job with no human consumer is often an orphan feeding a report nobody opens.
- Map each surviving object to a business owner by name. An object with no named owner is a strong candidate for retirement, and the search for an owner tends to settle the argument quickly.
- Check the integration layer separately. Interfaces are frequently invisible to usage statistics but very visible on the day they stop.
What good looks like afterwards
The target is not zero extension. Any business with a real operating model needs to express it somewhere. The target is that nothing you built blocks an upgrade.
Practically, a healthy landscape after migration has: every extension built on released or supported interfaces; a named owner for each one; a regression pack that runs against each vendor release; and a standing rule that new requirements are met by configuration first, extension second, and modification never.
That last rule is the one that decays. It survives the programme and erodes about eighteen months later, when the first urgent exception arrives and the governance forum is no longer meeting. Putting the rule in writing, with a named approver, is worth more than any amount of technical cleverness during the build.
Key takeaways
- Migration cost tracks customisation debt far more closely than it tracks data volume.
- SAP, Oracle and Microsoft have independently converged on the same architecture: standard core, extensions outside it.
- Oracle's quarterly update cadence makes the cost of modification recurring and visible; the principle applies equally to the others.
- Classify every object into obsolete, now-standard, differentiating or compliance, and start with obsolete.
- Decide ownership and governance before the build, because that is the part that erodes first.
