Call +1 515-644-1400Call any time. 24/7 outage response is an add-on.hello@allodi.us

Blog

Upgrading custom Odoo modules 16 to 19: what breaks

Three major versions of changes land at once when you move custom modules from 16 to 19. The ones that break real modules, checked against the source, and a way to rehearse until upgrade day is dull.

Odoo gives each major version three years of standard support. Its support table shows standard support for 17.0 ending in September 2026, and for 16.0 in September 2025, so many companies are planning a jump straight to 19. For a standard database that is mostly a data migration. For a database with custom modules, three versions of changes to views, the ORM and the web client land at once, and Odoo's upgrade documentation is clear about whose job that is:

"If a change introduced by a new version breaks a customization, it is the responsibility of the maintainer of your custom module to make it compatible with the new version of Odoo."

The same page adds that a database with custom modules "cannot be upgraded until a version of your custom modules is available for the target version of Odoo." Below are the changes that actually break real modules between 16.0 and 19.0, each checked against Odoo's source or changelog, followed by the rehearsal method we use.

Two separate jobs

An upgrade is two pieces of work that are easy to blur together:

  • Migrating the data. For Enterprise, Odoo's upgrade service does this for the standard modules. For Community, the OCA's OpenUpgrade project provides migration scripts, one major version at a time. It has a 19.0 branch; check its coverage file for 18.0 to 19.0 against your installed modules, because many modules are marked done or "nothing to do" and others are still open. Our article on upgrading Odoo Community from 18 to 19 with OpenUpgrade has the counts and a rehearsal method.
  • Porting your code. Custom modules, and any third-party modules you rely on, must exist in a version that installs and works on 19.0. Nobody does this for you by default.

The changes that bite

Version numbers like 17.2 or 18.1 are Odoo's intermediate releases; the change reaches self-hosted users in the next major version.

SinceChangeWhat breaks
17.0attrs and states removed from viewsAny view using them fails validation with "Since 17.0, the "attrs" and "states" attributes are no longer used." Rewrite as Python expressions in invisible, readonly and required.
17.0fields_view_get removed (deprecated in 16.0)Overrides that altered views at runtime. Use get_view.
17.0Legacy JavaScript fields and views removedWidgets built on the old AbstractField classes. Rewrite as OWL components.
16.4 / 18.0name_get deprecated, then removed in 18.0Custom record names. Override _compute_display_name instead.
17.2group_operator renamed aggregatorStill accepted in 18 and 19 with a deprecation warning, and removed in 20.0. Rename it while you are there.
18.0<tree> views renamed <list>; view type tree becomes listEvery list view, every view_mode containing tree, xpaths on //tree, and tree_view_ref context keys. The 17.0 web client quietly mapped "tree" to "list"; we found no such alias in the 18.0 or 19.0 source.
18.0user_has_groups removedUse self.env.user.has_group(...).
18.0Legacy web.Widget removed; the rpc service replaced by an rpc function from @web/core/network/rpcClient code calling useService("rpc") and anything still built on the old widget class.
18.1Constraints and indexes declared as model attributes (models.Constraint, models.Index)In 19.0, _sql_constraints is no longer supported: Odoo logs a warning and the constraint is simply not created. A module installs fine and quietly loses its uniqueness checks.
18.1JSON routes: type='json' renamed type='jsonrpc'Nothing yet: 19.0 still accepts type='json' as a deprecated alias. Rename it in custom controllers while you are there.
18.2read_group deprecated in favor of _read_group (backend) and formatted_read_group (public API)Warns at runtime from 19.0. Reports and dashboards that group data.
19.0odoo.osv.expression deprecated; use odoo.fields.DomainCode that builds domains with expression.AND and friends. The package is removed in 20.0.
19.0self._cr, self._uid, self._context deprecatedUse self.env.cr, self.env.uid, self.env.context. Very common in older modules.
19.0Groups reorganized: new res.groups.privilege; category_id moves from groups to privileges; users becomes user_ids; groups_id becomes group_ids on users, views, menus and actionsSecurity XML files, and any code reading or writing group membership.
19.0@api.returns removedMethods decorated with it.

Two more to know: from 18.0 the /** @odoo-module **/ comment is optional for files under static/src and static/tests, and 18.0 added check_access and has_access as the preferred way to test access rights. Neither breaks code, but both show up in every diff.

The quiet one

The _sql_constraints change is the one that passes tests. Nothing fails at install. Search your 19.0 upgrade logs for "no longer supported" and port every constraint to models.Constraint before go-live.

Let the tool do the mechanical part

Since 18.0, Odoo ships an upgrade_code command that rewrites source files. On 19.0 its scripts cover tree to list, _sql_constraints to models.Constraint, JSON route types in controllers folders, self._cr, _uid and _context to self.env, date expressions in domains, and some localization data. Run it in dry-run mode first:

odoo-bin upgrade_code --addons-path /path/to/your/addons --from 17.0 --to 19.0 --dry-run

With --dry-run it lists the files it would modify without changing them. Run it on the 19.0 source tree, since that is where the newest scripts live.

The documentation calls these scripts best effort, and that is fair. They handle the patterns they know. They do not rewrite attrs expressions, OWL components or security files, and they cannot judge business logic. Review the diff like any other.

If you maintain OCA-style modules, the OCA wiki page "Migration to version 19.0" lists the conventions and the typical changes, and the oca-port tool helps carry history across branches.

A rehearsal method

The aim is that upgrade day repeats something you have already done several times. Each step below produces evidence you keep.

1. Inventory and trim first

List every installed module, who wrote it and whether it is used. Remove what is dead before porting anything: every module you uninstall on 16 is one you never port. Our customization inventory queries are a starting point.

2. Port on a branch, one version jump at a time in your head

Even when the data goes straight from 16 to 19, it helps to work through the changes in the order they happened: views for 17, list views and services for 18, constraints, groups and the ORM for 19. Run upgrade_code, then fix the rest by hand, and commit in small steps so a reviewer can follow.

3. Build a neutralized copy

Restore a recent production backup on a separate server and neutralize it (odoo-bin neutralize, available since 16.0) so it cannot send mail, run scheduled jobs or reach payment providers and carriers. Check the list of scheduled actions and integrations yourself too: neutralization covers Odoo's standard features, not a custom module that calls an outside service directly.

4. Run the migration and capture everything

Run the data migration (Odoo's upgrade service for Enterprise, OpenUpgrade for Community), then install your ported modules. Keep the full log. Search it for errors, then for warnings containing "deprecated" and "no longer supported". Time the run: that number is your downtime estimate.

5. Compare numbers, not screens

Before the upgrade, record figures that must not change: trial balance by account, open receivables and payables, stock quantity and value by product category, open sales and purchase orders, record counts for key models. Record them again after the upgrade and compare. A difference is either explained or a bug.

6. Let the people who use it test

Give each department a short script of what they do every week: create a quote, confirm a delivery, post a vendor bill, print an invoice, run the reports they care about. Screens that look fine to a developer can be wrong to the person who uses them daily.

7. Repeat with a fresh copy

Fix what the rehearsal found, then run the whole thing again from a new production backup. When a rehearsal produces no surprises, schedule the real one, and follow the same written steps.

What about Odoo 20?

Odoo 20.0 was released in September 2026. For now we recommend going live on 19 and treating 19 to 20 as a separate, later step. That step removes several things 19.0 only deprecated, including odoo.osv and the group_operator alias, gives read_group a new signature, merges access rights and record rules into a new ir.access model, and moves the web client to OWL 3. Clearing the deprecation warnings from your 19.0 logs now makes that step smaller. OpenUpgrade opened its 20.0 branch on September 28, 2026, and on September 30 it still held no migration scripts. As of September 30, 2026, Odoo's own upgrade service does not yet offer 20.0 as a target (upgrade.odoo.com lists 19.0, 18.0 and 17.0), and Odoo's September 2026 announcement to its customers expects it by the end of November 2026.

What to expect

Most of the time goes into three areas: views written with attrs, JavaScript written for the legacy web client, and security files touched by the 19.0 groups change. Python business logic usually ports with small edits. Modules that nobody can explain are the expensive ones, which is why the inventory comes first. If you would rather hand the whole job over, our Odoo 19 upgrade page explains how we run it and what it costs.

Sources

  1. Odoo documentation: Standard and extended support (support dates)
  2. Odoo documentation: Upgrade (custom modules responsibility)
  3. Odoo documentation: ORM changelog
  4. Odoo documentation: Command-line interface (upgrade_code, neutralize)
  5. Odoo source: attrs and states check (ir_ui_view.py, 17.0)
  6. Odoo source: upgrade_code scripts (19.0)
  7. Odoo source: _sql_constraints warning (odoo/orm/model_classes.py, 19.0)
  8. Odoo source: res.groups.privilege (19.0)
  9. OCA OpenUpgrade, 19.0 branch
  10. OCA wiki: Migration to version 19.0
  11. OCA oca-port

Odoo's pages checked as of September 2026.