Skip to Content

Odoo Migration

Migrating Custom Odoo Modules Between Versions

Custom modules are where upgrade budgets are actually spent. The database conversion is largely mechanical and the business validation is predictable. The code is the variable, and how much it costs was decided years ago by whoever wrote it.

Part of our guide to Odoo Migration

The mechanical part, and its limits

Odoo 19 ships `odoo-bin upgrade_code`, which runs rewrite scripts from `odoo/upgrade_code/` against your source. It handles known renames: the `` to `` view rename, SQL constraints moving from the old list attribute to the newer `models.Constraint` class attribute, the JSON-RPC route type change, and others.

Run it. It removes real work.

Then read its own documentation, which says plainly that the scripts do a best-effort job of migrating source code, that they only help with the heavy lifting, and that they are not silver bullets. That is accurate, and it draws the line correctly: search and replace fixes renames, and everything else on this page is design.

The three questions that predict the cost

Before estimating, look at the module and answer these.

Does it extend or replace? A module that inherits views with XPath and overrides methods with `super()` is carrying forward changes from upstream automatically. One that copied core views and reimplemented core methods is carrying nothing, and every version it skipped is now a gap to reconstruct by hand.

How wide is its surface? A module touching one model in one app is a contained job. One that hooks into accounting, stock and sales is three jobs, and each has to be validated separately.

Are there tests? Not for correctness in the abstract, but because tests are the only way to know the port worked without a human re-doing every scenario. A module with tests is upgraded and verified. A module without them is upgraded and hoped about.

Writing migration scripts

When the schema changes meaning rather than shape, existing rows need converting. Odoo's mechanism is a `migrations/` directory inside the module, versioned, with scripts named by phase:

  • `pre-` runs before the module is initialised. This is where you rescue data you are about to lose: rename a column, stash values, prepare a lookup.
  • `post-` runs after initialisation, when the new fields exist and can be populated.
  • `end-` runs after every module has loaded, which is where cross-module consistency work belongs.

The most common mistake is not writing a bad script, it is writing none. If a field changes type or a model splits and no script runs, the module installs perfectly onto data that is now wrong, with no error anywhere. That failure surfaces weeks later as a report that will not reconcile.

Write the script, then run it against a copy of production and check row counts before and after. A migration script that silently touches zero rows is indistinguishable from one that was never written.

Field renames are an external problem too

Inside the module a rename is a refactor. Outside it, the old field name is a contract that other things depend on: integrations, external reports, BI dashboards, saved exports, filters users set up themselves.

None of these are in your module inventory. Build the list separately and treat each as a test case, because a rename that is clean internally can still break a nightly export that nobody notices until month end.

When porting is the wrong answer

Migrating a module is not automatically correct. Ask whether it should exist.

Some modules were written to add a field and a small rule, which Studio now handles without code. Some duplicate functionality Odoo has since added natively, so porting means maintaining a private version of something you get for free. Some are understood by nobody and touch things nobody wants to touch.

Each of those is a reason to remove rather than port, and the upgrade is the natural moment to decide, because it is the one time somebody is already reading the code.

Do this once per version, not once per four

The total work of upgrading annually and upgrading every four years is roughly similar. The risk is not.

Annually, you absorb one version of framework change at a time, against a codebase you were recently in, with a working intermediate state to compare against. Every four years you absorb all of it at once, in a single testing cycle, with nothing to diff against and no memory of why anything was written that way.

That is the real argument for staying current, and it is a development decision rather than an infrastructure one.

FAQ

Questions, answered.

Does odoo-bin upgrade_code migrate my modules for me?

It applies known mechanical renames to your source, such as the tree to list view rename and SQL constraints moving to the newer class-level form. Odoo's own documentation says the scripts are best-effort and not silver bullets. They handle search and replace; they cannot see design problems, which is where the real cost is.

What is a migration script and when do I need one?

A Python script in the module's migrations directory that converts existing rows when the schema changes meaning. You need one whenever a field changes type, a model is split, or data has to be reshaped. Without it the module installs cleanly onto data that is now wrong, and nothing errors.

Is it cheaper to rewrite a custom module than migrate it?

Sometimes, and it is worth asking honestly. If the module fights the framework, has no tests and nobody understands it, you will pay to carry it forward at every version. A module that only does what Studio or a small clean extension could do is often cheaper to replace than to port.

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 call