Upgrading Odoo with custom modules: why they hold up every upgrade, and how to make the next one cheaper
An Odoo upgrade with custom modules waits on your code, not Odoo's apps. What breaks between versions, the steps and upgrade scripts, and how to keep it cheap.
The usual story of an Odoo upgrade with custom modules: you request a test upgrade, Sales, Inventory and Accounting come through fine, and then everything stops on one line in a log. A custom module will not install.
That is normal, not bad luck. Odoo's upgrade documentation says a database with custom modules cannot be upgraded until a version of those modules exists for the target version. Odoo's tools carry the standard apps across. Your code decides when the upgrade is done.
This guide covers who is responsible for what, what actually breaks between versions, the steps and upgrade scripts, and a module we maintain across six Odoo versions.
When does an Odoo upgrade become mandatory?
Each major Odoo version is supported for three years. After that it depends on hosting:
- Odoo Online: a major version must be upgraded every two years, a minor Online version (17.2, say) a few weeks after the next one ships. Miss the deadline and it runs automatically.
- Odoo.sh: two more years after the three of support.
- On-premise: you can stay indefinitely. Odoo advises against it, and notes that the smaller the gap, the easier the upgrade.
An upgrade never downgrades, never moves you from Community to Enterprise and never changes hosting. And Odoo Online accepts no custom Python modules at all (see buying from the Odoo Apps Store versus building), so the rest of this is for Odoo.sh and on-premise.
Who upgrades what
Odoo counts as custom any module that extends standard Odoo code and was not built with Studio. The service-level agreement on the upgrade page splits the work like this:
| Piece | Who upgrades it |
|---|---|
| Standard Odoo apps | Odoo, under an Enterprise subscription |
| Studio customisations | Odoo, while Studio is installed and subscribed |
| Custom modules under a maintenance of customisations subscription | Odoo |
| Modules written in-house or by any third party, Odoo partners included | Whoever maintains them |
| Apps Store modules | Their publisher, and you buy the new version |
| Cleaning old data, training staff | You |
Row four surprises people. Odoo is explicit: if a new version breaks a customisation, fixing it is the maintainer's job.
The free upgrade service comes with Enterprise. On Community the usual route is the Odoo Community Association's OpenUpgrade, which maintains migration scripts for Odoo's own modules. Your custom modules are yours to port either way.
What breaks in a custom module between versions
Most technical changes are written down in the ORM changelog and the release notes. Real examples:
Views
- Odoo 17 removed
attrsandstatesfrom views. Any module that hides or locks a field withattrsneeds rewriting. - Odoo 18 renamed list views from
treetolist. - An xpath pointing at a spot that moved or vanished breaks the inheritance. During the upgrade, a failing view is disabled and listed in the upgrade report.
- OWL and asset declaration changes, so anything touching the Point of Sale or other JavaScript screens needs real work every version.
ORM methods
name_get()deprecated in 16.4; readdisplay_nameinstead.- The field attribute
group_operatorrenamedaggregatorin 17.2. read_groupdeprecated in 18.2 for_read_groupon the server andformatted_read_groupas the public API.record._cr,record._contextandrecord._uiddeprecated in 19.
16.4, 17.2 and 18.2 are Odoo Online releases; everyone else meets those changes in the next major version.
Models and data
- Models merge or disappear. Odoo's own example:
sale.subscriptionmerged intosale.orderin Odoo 16, and custom fields on it do not move by themselves. - A field your module writes to is removed. That happened to one of ours, below.
- Records flagged
noupdateare not refreshed on module upgrade, so a changed email template or master record needs a script to reach the upgraded database.
The steps to upgrade Odoo with custom modules
This follows Odoo's guide to upgrading a customised database, with what we add from practice.
1. Freeze development and challenge what you have
Odoo recommends a full code freeze, bug fixes aside. Then compare your customisations with what the versions in between added. Plenty of old custom work is now standard, and the cheapest code to upgrade is code you delete. We start with a list of every module, what it does, who uses it and whether it is still needed.
2. Request an upgraded test database
On Odoo.sh, on a staging branch; on-premise, from the command line or odoo.com's upgrade page. This only proves the standard upgrade works.
Test databases are neutralised: scheduled actions off, outgoing mail disabled, payment providers and carriers in test mode, bank sync off. On-premise, the dump arrives without the filestore, so you merge it with production's yourself.
ZATCA e-invoicing is not on that list. Before validating an invoice on the copy, check whether it is in Simulation or Production mode, because an invoice sent to the production portal counts. See ZATCA e-invoicing on Odoo.
3. Make each module work on an empty database
Install your modules one by one on an empty database of the target version and fix every traceback and warning: bad dependencies, attrs, renamed fields and views, lost xpath targets. Then use them, because installing is not working. Test views, email templates, reports, automated actions and computed fields, and make sure the standard tests of the apps you depend on still pass.
4. Make them work on the upgraded database
Anything renamed in your code (a model, a field, an XML id) must move with an upgrade script, or its data is lost. Fix the views the upgrade report disabled, and update noupdate records that need it.
On Odoo.sh, the upgraded staging branch runs in "update on commit" mode: every push restores the upgraded backup, updates your modules and runs their scripts, with the log at ~/logs/upgrade.log. On-premise, you run the update yourself with -u.
5. Test whole processes, then rehearse
Run complete flows on real data: buy and receive, sell and ship, invoice and credit, a web order through checkout, a till session closed. Test integrations and printed reports. Have the people who use Odoo daily do it, not only the developer. If the project runs long, request a fresh test database, and rehearse the whole thing the day before production.
6. Production day
The database is down for the whole upgrade, so pick a quiet time and keep everyone out: anything entered after the copy is taken is not in the upgraded database. On Odoo.sh a failed upgrade rolls back automatically and a successful one keeps a backup from before. On-premise, the backup is your job.
Upgrade scripts: how your data survives
An upgrade script is a Python file with a migrate(cr, version) function that Odoo runs while updating the module. It lives inside the module:
my_module/
upgrades/
18.0.1.1/
pre-rename_fields.py
post-move_data.py
end-cleanup.py
- The folder is
upgrades(or the oldermigrations), then the full version: Odoo's major version followed by the module's. - A script only runs when the module's version goes up. Forget to bump
versionin__manifest__.pyand it never runs. - Three phases:
prebefore the module loads,postafter it and its dependencies are updated,endafter every module is done. Within a phase, files run in name order. - Odoo's upgrade utils cover the common cases:
rename_field,rename_modelandrename_xmlidso renames keep their data,recompute_fieldswhen a computation changes,remove_modulefor a module you are dropping.
A real example: one module, six Odoo versions
Tabby & Tamara for Odoo is a module we built at Kerneltics and sell on the Odoo Apps Store, connecting buy now, pay later payments to the online shop, the Point of Sale and invoices. (It is ours and not affiliated with Tabby or Tamara.) It lives with exactly this problem: one branch per Odoo version, 14.0 to 19.0, Community and Enterprise, and the branch must match the server's major version. Everything below is from the install guide on its site.
Same module, different code. Between branches:
- The gateway screen is Payment Acquirers in Odoo 14 and 15, Payment Providers from 16.
- The API request log screen exists from 15; on 14 there is only the server log.
- Instalment payments at the till go through the receivables account on 14, a journal from 15.
- From 16, providers are created unpublished until you publish them.
One field can stop everything. The 19.0 branch fills the State field on the payment provider, and Odoo 20 removed that field. So 19.0 will not run on Odoo 20, and when the guide was written there was no 20.0 branch. A breaking change does not need a redesign. A removed field is enough.
Spot the wrong branch before installing. Odoo prefixes its own version to a module version that does not start with it. The right build on Odoo 18 reads 18.0.2.1.0; a 17.0 branch on Odoo 18 reads 18.0.17.0.2.1.0. Two version numbers in a row means the wrong branch.
Fewer dependencies, fewer breakages. The only outside library it uses is requests, which Odoo needs anyway.
Updating within one version. Replace the folder, restart, then upgrade the module (-u payment_tabby_tamara): the restart loads the Python, the upgrade refreshes views and data, and skipping either runs half of each release. Never uninstall to start fresh: that drops the module's tables, including the till payment records refunds rely on.
How to write a module the next upgrade will not punish
Upgrades are won or lost when the module is written:
- Inherit, never edit Odoo's own files. More in our guide to custom Odoo add-ons.
- Anchor xpaths on names.
//field[@name='partner_id']outlives//group[2]/field[3], because view layouts change more often than field names. - One job per module. Small modules upgrade one at a time, and the ones that became standard can go. A giant one upgrades all at once or not at all.
- Bump the version with every data change, script in the same commit. Do not save scripts for the big upgrade.
- Automated tests for the processes that matter. A test that passes on 17 and fails on 18 finds the problem before a user does.
- One branch per Odoo version, as with our Tabby & Tamara module, so fixes cross branches on purpose.
- Write down why each module exists. "Do we still need this?" is quicker to answer on paper.
Quick questions
Can we go from 14 straight to 18?
Yes; Odoo's own example is 16.0 to 18.0. Custom modules are written for the target version only, but every change in between lands at once, so the work grows with the jump.
How long does an Odoo upgrade take?
It depends on the number and size of your custom modules, the version gap and how many processes need testing. Anyone quoting a duration before seeing your module list is guessing.
Can we go back if we dislike the new version?
Upgrades do not cover downgrading. On Odoo Online there is no way back once it completes; Odoo.sh keeps a pre-upgrade backup. Hence the test database and the rehearsal.
A module we bought has no version for the new release. Now what?
Wait for the publisher, have a developer port it for your own use if the licence allows, or replace it. Each Odoo version is sold separately. More in buying from the Odoo Apps Store versus building.
Will the upgrade fix our current Odoo problems?
No. Odoo states that cleaning existing data and configuration is not part of an upgrade. If the trouble is how Odoo was rolled out, read why Odoo implementations go wrong first.
How we do it
At Kerneltics we start with an inventory of every custom module and what the new version now does as standard. We request a test upgrade, port each module on an empty database of the target version with tests and upgrade scripts, then on a copy of your data. Your team runs its real processes before the rehearsal, and you get the code with one branch per version, the way we maintain Tabby & Tamara for Odoo.
See our Odoo page and Odoo with ZATCA e-invoicing, or book a call with your current version and module list.