Skip to main content
Odoo 19 to Odoo 20 Migration: A Technical Guide for Custom Databases
14 min read
Odoo

Odoo 19 to Odoo 20 Migration: A Technical Guide for Custom Databases

Migrate Odoo 19 to Odoo 20 safely with a technical checklist for custom modules, ORM changes, data scripts, APIs, testing and production cutover.

An Odoo 19 to Odoo 20 migration is not complete when the standard database reaches version 20. If the system contains custom modules, integrations or historical custom data, three things have to arrive at the target version together:

  1. the standard Odoo database;
  2. the custom source code and data model;
  3. the external systems and business workflows around it.

Odoo’s official custom-upgrade process reflects that separation: freeze and challenge custom development, request an upgraded database, make custom modules work on a clean target-version database, make them work with the upgraded customer database, test extensively, rehearse, and only then upgrade production.

For developers, the important question is therefore not “Does the module install on Odoo 20?” It is:

“Does the Odoo 20 code preserve the intended behavior and historical data of the Odoo 19 implementation?”

This guide focuses on that technical migration.

Version note: Odoo 20 documentation and upgrade tooling are still evolving. Re-check the official 20.0/master documentation and the availability of your exact upgrade path before production.

The migration in one view

A reliable 19 → 20 custom migration can be organized into six workstreams:

WorkstreamMain question
Standard databaseDoes Odoo’s standard upgrade complete cleanly?
Custom sourceDoes every required custom module run correctly on Odoo 20?
Custom dataDid renamed/changed models, fields, XML IDs and records survive correctly?
Frontend/reportsDo views, JS/Owl, assets and reports still behave correctly?
IntegrationsDo APIs, payloads, authentication and external jobs still work?
Business regressionDo real end-to-end workflows produce correct results?

Keeping these workstreams separate makes debugging much faster.

Step 1 — Freeze the Odoo 19 codebase and inventory the delta

Odoo recommends a development freeze when starting a customized upgrade, except for necessary bug fixes.

Create a migration branch from a known production revision and inventory every custom module.

For each module, record:

  • purpose and owner;
  • dependencies;
  • models and fields added/overridden;
  • views and reports;
  • controllers/API endpoints;
  • JavaScript/Owl code;
  • scheduled/server actions;
  • security rules;
  • external integrations;
  • test coverage;
  • whether Odoo 20 now replaces the customization natively.

The last item matters.

A module that is no longer necessary should usually be retired rather than migrated.

Step 2 — Establish a clean Odoo 20 compatibility baseline

Before introducing upgraded customer data, make each required custom module installable on an empty Odoo 20 database.

Odoo recommends this explicitly because it isolates source-code compatibility from historical data/configuration issues.

A clean-target test should catch:

  • missing dependencies;
  • renamed or removed framework APIs;
  • invalid views;
  • Python errors/warnings;
  • broken assets;
  • JS/Owl issues;
  • missing XML IDs;
  • security-definition problems;
  • test failures.

Only after the module behaves correctly on clean Odoo 20 should you combine it with the upgraded customer database.

Step 3 — Review documented Odoo 20 ORM changes

The official Odoo ORM changelog already identifies changes that custom developers should search for.

Model._table_query is removed

Odoo 20 removes Model._table_query.

If a custom reporting/SQL-backed model depends on this mechanism, do not wait for a production traceback. Search the codebase during migration and redesign the implementation using the supported Odoo 20 approach.

Repository-level searches for removed/deprecated framework patterns should be part of the migration checklist.

Binary-field RPC representation changed

Odoo 20 adds BinaryValue.filename and updates the RPC format for binary fields.

This matters beyond Python code. An external client that assumes a specific binary payload shape may need regression testing.

Check:

  • mobile apps;
  • portal middleware;
  • document integrations;
  • import/export services;
  • custom controllers or RPC consumers.

Translation behavior changed

The Odoo 20 changelog also documents simplified translation manipulation and changes to storage of manifest translation terms.

If custom modules manipulate translations programmatically or ship substantial localization data, include translations in the migration test plan rather than assuming they are purely static files.

New populate tooling

Odoo 20 adds a populate script for generating fake test data.

That is not a migration blocker, but it is useful for engineering: realistic generated data can improve performance/regression testing without copying sensitive production records into every development environment.

Step 4 — Review the whole custom-module surface

The ORM changelog is a starting point, not a complete migration diff.

Review each layer deliberately.

Python and ORM

Search for:

  • overridden create, write, unlink, search and compute logic;
  • assumptions about field values/types;
  • SQL-backed models;
  • context-dependent behavior;
  • company-dependent logic;
  • access/security behavior;
  • monkey patches or imports from internal modules.

Prefer public supported APIs over implementation details.

XML views

Validate:

  • inherited view targets;
  • XPath expressions;
  • field availability;
  • modifiers/visibility behavior;
  • actions and menus;
  • search views;
  • settings views;
  • kanban/form/list layouts.

A fragile XPath can survive for years and fail as soon as the upstream view changes.

JavaScript/Owl and assets

Review:

  • imports;
  • registries/services;
  • patches;
  • templates;
  • bundles;
  • POS/web-client extensions;
  • RPC assumptions;
  • browser console errors.

A backend migration is not complete if the frontend silently loses custom behavior.

Reports

Validate both rendering and values.

For QWeb/PDF/XLSX or custom reporting logic, check:

  • template inheritance;
  • field names;
  • translations;
  • paper formats;
  • totals;
  • multi-company/currency behavior.

Security

Re-test:

  • ACLs;
  • record rules;
  • groups;
  • implied groups;
  • sudo usage;
  • company boundaries;
  • portal/public access.

A migration that accidentally broadens access is more serious than one that breaks a button.

Step 5 — Migrate historical custom data explicitly

This is where “works on a clean database” and “works for the customer” diverge.

Odoo’s custom-upgrade documentation recommends upgrade scripts when technical data has changed between versions.

Typical cases include:

  • renamed fields;
  • renamed models;
  • renamed external/XML IDs;
  • merged or removed models;
  • changed selection values;
  • data moved between models;
  • custom records that must be recomputed;
  • configuration records that need new values.

Odoo’s Upgrade Utils library provides helpers such as rename_field(), rename_model() and rename_xmlid() to reduce the risk of manually rewriting database references.

A simplified pattern looks like:

python
from odoo.upgrade import util

def migrate(cr, version):
    util.rename_field(cr, "your.model", "old_field", "new_field")

Do not copy this blindly into production. The exact helper and migration stage depend on the code/data change. Verify the current Upgrade Utils API and test the script against a disposable upgraded database.

Why direct SQL is not always enough

Renaming a column manually may preserve the obvious table value while missing indirect references or Odoo metadata.

Upgrade helpers exist partly to keep related references consistent.

Use raw SQL when it is genuinely the right tool—not merely because it is shorter.

Step 6 — Handle noupdate records deliberately

Odoo’s custom-upgrade guidance specifically calls out custom records marked noupdate.

These records may not be refreshed automatically when the module is upgraded.

That can matter for:

  • configuration records;
  • server actions;
  • scheduled actions;
  • email templates;
  • sequences;
  • rules;
  • default data.

If an Odoo 19 noupdate record needs to reflect a new Odoo 20 definition, plan an explicit migration.

Odoo points to helpers such as update_record_from_xml() for this type of case.

The general rule is simple:

Do not assume XML data changed in source code means historical database records changed too.

Verify the actual upgraded record.

Step 7 — Inspect disabled views in the Upgrade Report

During the standard database upgrade, problematic custom views can be disabled.

Odoo tells developers to inspect the Upgrade Report for these cases.

For each disabled view, decide:

  1. Is the customization still needed?
  2. Does its inherited parent still exist?
  3. Is the XPath still valid?
  4. Has standard Odoo 20 made the customization redundant?
  5. Should it be migrated and re-enabled, or deleted?

Do not automatically reactivate old views.

A disabled view can be a useful signal that the underlying standard workflow changed.

Step 8 — Audit integrations separately from module compatibility

An Odoo 19 → 20 migration can leave the custom module healthy while breaking an external application.

Inventory each integration by protocol.

Legacy XML-RPC / JSON-RPC

Current official Odoo documentation says the external XML-RPC and JSON-RPC services are deprecated.

The important timeline is:

  • the legacy db service was removed in Odoo 20;
  • common and object are currently scheduled for removal in Odoo 22;
  • External JSON-2 is the replacement direction.

So a client using common/object is not necessarily broken solely because the server moved to Odoo 20—but it is now technical debt with a documented migration path.

If an integration uses the old db service, Odoo 20 is directly relevant.

Custom JSON-RPC controllers

Do not confuse legacy external RPC services with every custom JSON controller.

Odoo’s deprecation notice explicitly distinguishes custom controllers from the deprecated external service endpoints.

Review custom controllers according to the current HTTP/controller API, not by applying the external-RPC deprecation blindly.

JSON-2

For integrations you are modernizing, evaluate Odoo’s External JSON-2 API.

Do not combine an Odoo 19→20 production migration with a complete API rewrite automatically. Sometimes separating the version upgrade and integration modernization reduces risk.

Other times the upgrade is the right moment to remove a deprecated dependency.

Make that decision per integration.

Binary/document integrations

Because Odoo 20 changes the RPC representation of binary fields, explicitly test integrations that upload/download documents or depend on binary payload assumptions.

Step 9 — Test the upgraded customer database

Once custom code is clean on Odoo 20 and the standard database upgrade is available, combine them.

The goal is no longer “does it install?”

The goal is historical-data correctness.

Choose representative old and new records:

  • records created before major customizations;
  • open orders;
  • partially delivered orders;
  • old invoices/credit notes;
  • products with routes and variants;
  • lots/serials;
  • manufacturing orders;
  • employees/contracts where relevant;
  • multi-company records;
  • archived records;
  • records containing custom fields.

Test reads, edits, transitions and reports.

Step 10 — Build a migration test matrix by business effect

For each custom module, map code to business impact.

Custom areaTechnical testBusiness test
CRM automationserver action runscorrect salesperson/stage/activity
Sales pricingcompute succeedsexpected quotation totals
Inventorystock code executescorrect move/reservation/valuation
MRPcustom hooks runcorrect MO/subassembly result
Accountingposting succeedscorrect accounts/taxes/analytic data
HRworkflow executescorrect approval/balance/payroll effect
APIrequest succeedsexternal system receives correct semantics
Reporttemplate renderstotals/content are correct

This prevents the migration team from declaring success because unit tests passed while the operational result changed.

Step 11 — Run standard tests and add migration-specific tests

Odoo recommends getting standard tests to pass during custom-module migration.

Add tests for the migration risks that matter specifically to your module.

Useful examples:

  • old field data appears in the renamed field;
  • renamed XML IDs resolve;
  • old records retain correct company ownership;
  • custom workflow states remain valid;
  • noupdate configuration has the expected target value;
  • report totals match known fixtures;
  • access rules prevent cross-company leakage.

For a complex module, a migration-specific test can be cheaper than repeatedly finding the same data bug during manual UAT.

Step 12 — Rehearse the full 19 → 20 migration

A technical rehearsal should start from a fresh production copy, not the database developers have been manually fixing for weeks.

Re-run:

  1. standard test upgrade;
  2. target-version code deployment;
  3. custom module update;
  4. pre/post/end upgrade scripts;
  5. filestore handling where applicable;
  6. configuration steps;
  7. automated tests;
  8. technical smoke tests;
  9. representative UAT;
  10. integration checks.

Record every manual action.

If an undocumented manual SQL command is required, the migration is not yet reproducible.

Automate it, script it or formally add it to the cutover procedure.

Step 13 — Production cutover

The production sequence depends on hosting, but the engineering principles remain the same:

  • freeze Odoo 19 changes;
  • stop or control external writers;
  • identify the exact code revision;
  • trigger/restore the production upgrade through the supported process;
  • deploy the tested Odoo 20 code;
  • execute tested upgrade scripts;
  • validate logs;
  • run technical smoke tests;
  • run critical business smoke tests;
  • re-enable integrations deliberately;
  • obtain business go-live approval.

Measure downtime from rehearsal rather than estimating it from another company’s migration.

Odoo 19 → 20 developer checklist

Repository

  • Freeze migration baseline.
  • Remove obsolete modules/customizations.
  • Update manifests/dependencies.
  • Search for removed/deprecated framework usage.
  • Review _table_query usage.
  • Review binary RPC assumptions.
  • Review translation manipulation.
  • Review Python ORM overrides.
  • Review XML/XPath inheritance.
  • Review JS/Owl/assets.
  • Review controllers.
  • Review reports.
  • Review security.
  • Run tests on clean Odoo 20.

Data

  • Map renamed models.
  • Map renamed fields.
  • Map renamed XML IDs.
  • Identify removed/merged data models.
  • Review noupdate records.
  • Review disabled views from Upgrade Report.
  • Write and test upgrade scripts.
  • Validate historical custom records.

Integrations

  • Inventory legacy RPC clients.
  • Identify any use of the removed db service.
  • Plan JSON-2 modernization where appropriate.
  • Test custom controllers separately.
  • Test binary/document payloads.
  • Test authentication.
  • Test scheduled/webhook/middleware jobs.

Acceptance

  • Run standard tests.
  • Run migration-specific automated tests.
  • Run end-to-end business UAT.
  • Reconcile critical data.
  • Rehearse from a fresh production copy.
  • Record cutover duration.
  • Complete production smoke tests.

Common Odoo 19 to 20 migration mistakes

Treating installation as migration completion

A module installing on an empty database proves only part of compatibility.

Editing historical data manually until the test works

If the same changes are required in production, encode them in reproducible upgrade scripts.

Migrating obsolete customizations

Challenge the module against Odoo 20 before investing in the port.

Ignoring disabled views

Read the Upgrade Report. A missing customization may simply have been disabled during the standard upgrade.

Forgetting noupdate

Changed XML source does not guarantee the corresponding historical record is updated.

Rewriting every integration during the same cutover

Modernize deliberately. Avoid multiplying migration risk unless there is a reason to combine the changes.

Assuming legacy RPC disappeared completely in Odoo 20

Current official documentation is more nuanced: db was removed in 20, while deprecated common and object services are currently scheduled for removal in 22.

Testing only fresh records

Historical data is the reason an upgraded customer database must be tested after clean-target compatibility.

Frequently asked questions

Can Odoo 19 be upgraded to Odoo 20?

Odoo 20 is the next major version after 19, but production planning should still verify that the official upgrade tooling currently supports the exact database/hosting path before committing to a date.

Do Odoo 19 custom modules need code changes for Odoo 20?

Potentially, yes. Odoo 20 includes documented ORM/framework changes, and any custom module should be tested against a clean target-version database and the upgraded customer database.

What happened to _table_query in Odoo 20?

The official ORM changelog says Model._table_query is removed in Odoo 20. Custom modules using it should be identified during code review and migrated to the supported target-version approach.

Should we use upgrade scripts?

Use them when source-code changes require corresponding historical data changes—for example renamed fields, models or XML IDs. Odoo provides Upgrade Utils to help perform these operations consistently.

Are XML-RPC and JSON-RPC gone in Odoo 20?

Not completely. Current official documentation says the db service was removed in Odoo 20, while deprecated common and object services are scheduled for removal in Odoo 22. JSON-2 is the replacement direction.

Should we migrate our API integration to JSON-2 during the Odoo 20 upgrade?

Evaluate it. If the integration depends on deprecated APIs, modernization is worth planning, but combining an API rewrite with the major-version cutover may increase risk. The best sequencing depends on the integration and test coverage.

Final takeaway

The safest way to think about an Odoo 19 → 20 migration is:

clean-code compatibility first, historical-data compatibility second, business acceptance third.

That ordering gives developers a much clearer debugging path.

If the module fails on clean Odoo 20, fix the source.

If it works clean but fails on upgraded customer data, investigate migration scripts and historical records.

If the technical layer works but the business result changes, investigate the workflow.

Keeping those problems separate is what turns a version upgrade from trial-and-error into an engineering process.


Planning an Odoo 20 project? Octivex Tech helps businesses with Odoo upgrades, custom module migration, integrations and AI-enabled workflows. Explore our Odoo services or contact our engineering team to discuss your environment.