Insights·Consulting·5 min read

Odoo data migration: what to move and what to leave behind

Most Odoo implementations slip on data, not on software. Here is a practical way to decide which records earn a place in the new system and which belong in an archive.

Data migration is the part of an Odoo implementation that looks like a formality on the project plan and quietly becomes the critical path around week six. Configuration is done, processes are agreed, and then somebody opens the customer export from the legacy system and finds four spellings of the same company, invoices with no matching order, and a stock list nobody has counted in two years.

The instinct at that point is to move everything, because moving everything feels safe. It is usually the most expensive option on the table, and it makes the new system harder to trust in its first month.

Ask what each record is for

Every migration decision reduces to one question, asked per data set rather than per record: what will a person actually do with this in Odoo? If the answer is "post a payment against it", "ship it", "call this customer", or "report on it next quarter", it belongs in the system. If the answer is "look it up once a year if there is an audit", it belongs in an archive you can search, not in your live database.

That distinction matters because everything you load carries an ongoing cost. Bad master data propagates into every quotation and delivery note after go-live. Old transactions inflate list views, complicate reconciliation, and make it harder to tell whether a number is wrong because the migration was wrong or because the process is wrong.

What almost always moves

  • Customers, vendors, and contacts that are active, plus any with open balances. Dormant records from a decade ago can stay behind.
  • Products and services you still sell or buy, with units of measure, tax mapping, and cost or price fields agreed in advance.
  • Chart of accounts, aligned to the Odoo localization package you will actually use rather than copied line for line from the old ledger.
  • Opening balances: the trial balance as of the cutover date, open customer invoices, open vendor bills, and bank balances.
  • Open sales and purchase orders, so operations can finish work in flight without running two systems side by side.
  • Inventory quantities at cutover, ideally from a physical count rather than from the legacy system's own numbers.
  • Employee master data where HR or payroll modules are in scope, including the fields the local statutory setup requires.

What usually does not need to move

Closed transaction history is the largest and most contested item. Five years of posted invoices can be loaded into Odoo, but doing it properly means recreating taxes, currencies, payment matching, and journal entries that were produced by a different accounting engine. The usual result is a set of documents that look right in a list view and do not tie to anything.

In most implementations the better trade-off is to migrate summarized opening balances, keep the legacy system available in read-only mode for a defined period, and export a full historical archive as files. Auditors accept this. Finance teams stop asking for it a couple of months after go-live, once the new system holds a full reporting cycle of its own.

The same logic applies to closed sales orders, completed deliveries, bulk attachments, and per-record audit trails from the previous platform. When someone insists a specific history set must be live, ask which report they will run against it. A concrete answer often narrows five years down to one, or down to a single module.

Clean before the load, not after

The cheapest place to fix data is a spreadsheet. The most expensive place is a production database with users already working in it. Deduplicate partners, standardize country and currency codes, decide what a blank tax field means, and settle naming conventions before the first import file is built.

Assign this work to someone from the business, not to the implementation partner alone. Only the business can say whether two similar customer names are one company or two, or which of three price fields is the one people actually quote from.

Expect several passes. A first load into a test database will surface validation errors, and those errors are the point of the exercise. A migration that imports cleanly on the very first attempt usually means the validation rules are too loose.

Reconciliation is the real acceptance test

Before cutover, agree the numbers that must match exactly between old and new: trial balance total, accounts receivable and accounts payable aging, inventory value by location, and open order counts. Sign those figures off in writing with the finance lead.

This is what protects the project from the argument that starts three weeks later, when a report is off by a small amount and nobody can say what the correct starting point was. Without a signed reconciliation, every future discrepancy becomes a debate about the migration.

Run the full migration at least twice: a dry run into a staging database using real data, then the cutover load itself. The dry run tells you how long the load takes, and that number is what determines whether cutover fits into a weekend or needs a longer freeze.

Where this fits in the project

Data migration deserves its own workstream, its own owner, and its own dates, rather than a line item buried inside configuration. Scope it in the same conversation where you scope modules, because the decision to bring five years of history is a scope decision with a real cost attached to it. If you are planning this now, our Odoo ERP implementation service page sets out how the phases fit together.

The goal is not a database that mirrors the old one. It is a system where every number a user sees can be traced, explained, and trusted from the first week.

Let's talk

Let's turn this into a plan.

Book a discovery call and we will map your fastest path to a system that lasts.

Book a discovery call