Odoo Migration
Migrating Data into Odoo from a Legacy System
Coming into Odoo from another system is not an upgrade. Nothing converts automatically, there is no mapping to inherit, and the biggest decision is not technical at all: how much history you actually need, and what it costs to bring it.
Part of our guide to Odoo Migration
This is not an upgrade, and the difference matters
A version upgrade converts a database that Odoo already understands. Coming from QuickBooks, Sage, SAP, a bespoke system or a folder of spreadsheets, none of that applies. There is no schema to convert, no conversion service, and no shared assumptions about what a customer record contains.
What you have instead is a mapping exercise, and the effort scales with how many decisions the old system let people make freely.
Decide what history you actually need
This single decision moves the budget more than any technical choice, and it is usually made backwards. The instinct is "bring everything", because nobody wants to be the person who threw away data.
Three tiers, in the order they earn their cost:
Master data is mandatory. Customers, suppliers, products, chart of accounts, taxes, price lists. The business cannot operate without these on day one.
Open items are close to mandatory. Unpaid invoices, open purchase orders, stock on hand, unfinished projects. These represent work in progress, and leaving them behind means running two systems in parallel until they clear.
Closed history is optional and expensive. Paid invoices from four years ago, completed orders, old journal entries. Each one has to be mapped, loaded, and then validated against the source, and the usual reason given for wanting them is reporting.
For that last tier, ask what the actual requirement is. If it is "we want to compare this year to last year", a summary or a read-only copy of the old system answers it at a fraction of the cost. If it is a genuine statutory retention requirement, keeping the old system accessible often satisfies it more cleanly than reconstructing the records inside Odoo.
Opening balances beat historical journals
The most common expensive mistake in a finance migration is trying to reproduce years of journal entries so the general ledger looks continuous.
It rarely works. The old chart of accounts does not map cleanly, tax treatment has changed, and every difference has to be explained. Meanwhile a set of opening balances at a chosen cut-over date gives you a correct starting position, reconciles in an afternoon, and is what most accountants will recommend anyway.
Pick a cut-over date, load balances as at that date, and let the history live where it already is.
Load in dependency order
Records reference other records, so the sequence is not arbitrary:
1. Company setup, currencies, chart of accounts, taxes
2. Partners, being customers, suppliers and anyone who is both
3. Products, units of measure, categories and price lists
4. Opening balances and stock quantities
5. Open documents, being unpaid invoices and outstanding orders
Loading out of order produces errors that point at the wrong thing. A product import failing because a tax does not exist reports a product problem, and people spend an hour looking at products.
Use external IDs, and treat the load as repeatable
You will not load correctly on the first attempt. Nobody does. So build for a second attempt from the start.
Give every source row a stable external identifier, usually the primary key from the old system, and import against it. Odoo will then match and update rather than create, which means a rerun corrects the data instead of doubling it. Without this, the second run produces two of everything, and the cleanup is worse than the original load.
This is also what makes iterative loading possible: load, validate, fix the mapping, reload, and repeat until the counts and totals agree.
Clean before, not after
Legacy data carries years of accumulated mess: three spellings of the same customer, products that were never retired, addresses in a notes field, duplicates created by a since-departed temp.
Migration is the natural moment to fix this, but do the cleaning in the source or in the intermediate files, not in Odoo afterwards. Cleaning in Odoo means editing records that are already referenced by other records, merging partners that already have transactions attached, and doing it under time pressure with users watching.
Cleaning before also shrinks the load, which shortens every subsequent test cycle.
Validate with counts and totals, not by looking
The same discipline that applies to a version upgrade applies here, with one extra difficulty: there is no "old version of the same report" to compare against, so the comparisons have to be built deliberately.
Agree them in advance. Customer count and supplier count. Product count by category. Total receivables and total payables. Stock quantity and stock value by warehouse. Each of those is a number you can pull from both systems and compare, and a number that matches is the only evidence that survives scrutiny. The full list of checks worth running is in the validation checklist.
Where this meets the rest of the project
A legacy migration usually runs alongside a fresh implementation rather than an upgrade, so it inherits that project's cut-over and its rollback deadline. It also creates its own integration work, because the systems that fed the old platform now need to feed Odoo instead, which is a separate integration scope rather than part of the data load.
Keep those three tracks visible separately. Merged into one line item, the data migration absorbs the blame for every delay in the other two.
FAQ
Questions, answered.
Should we bring all our historical data into Odoo?
Usually not. Open items and master data are essential. Full transactional history is expensive to map, hard to validate, and rarely used once the old system stays available read-only for a year. Bring balances rather than the entries behind them unless a legal or audit requirement says otherwise.
What is the correct order to load data into Odoo?
Dependencies first. Chart of accounts, taxes and currencies, then partners, then products, then opening balances and open documents. Loading in the wrong order produces records that reference things that do not exist yet, and the errors point at the wrong place.
How do we avoid duplicates when a data load has to be rerun?
Give every source row a stable external identifier and import using it. Odoo then updates the matching record instead of creating a second one, which makes the load repeatable. Without external IDs, the second run silently doubles everything.
Keep reading
More on Odoo Migration.
The full guide, plus the other articles in this cluster.
With CODEerts
Planning an upgrade?
We are certified Odoo partners. We migrate and upgrade Odoo databases, including the custom modules and integrations that decide how long it really takes.
See how we can helpBook a callReady to make Odoo work the way your business does?
Book a free callCODEerts is a team of certified Odoo partners and full-stack engineers. We implement, customise and support Odoo ERP, then build the software around it.