From Odoo 18 to Odoo 19
Our Real Migration Story (Database, Custom Code, and the Expense Report Curveball) 

August 31, 2026 / Dipak Vyas

Upgrading an ERP is never “just” a version bump — especially when your business runs on years of custom modules and configuration. We recently completed a full migration of our own Odoo environment from version 18 to version 19, covering both the database and our custom codebase, end to end on Odoo.sh. 

This post is for two kinds of readers. If you’re evaluating whether it’s time to move your business to Odoo 19, you’ll get a clear picture of what changes and what to expect. If you’re an Odoo developer planning your own migration, we’ve included the specific technical issues we hit — including one change in Odoo 19 that broke a heavily customized module and forced us to rebuild it from the ground up. 

Why Move to Odoo 19? 

Odoo 19 touches nearly every major module, not just the UI. A few highlights that made the upgrade worthwhile: 

AI woven into daily workflows. CRM lead assignment is now rules-based and AI-assisted, with leads auto-generated from business card scans and live chat conversations. 

A reworked Inventory and Barcode experience — batch transfers, multi-step flows, and cycle counts during live operations. Website and eCommerce upgrades — SEO-optimized product pages, tighter Google Merchant Center integration, and new shop page layouts. 

Stronger accounting tools — simplified OCR-based bank reconciliation, WhatsApp payment reminders, and expanded ISO20022 support. 

A new ESG/sustainability layer — built-in CO₂ footprint reporting aligned with the GHG Protocol and Bilan Carbone standards. 

Odoo’s release cadence means staying multiple versions behind eventually costs you security patches and vendor support. We wanted to move on our own schedule rather than under pressure — which is exactly what we did. 

How We Approached the Migration

Here’s the actual sequence we followed, running on Odoo.sh: 

  1. Backup first, always. Before touching anything, we took a full backup — database and filestore. This is the one step you don’t get a second chance on if something goes wrong mid-migration.
  2. Branch conversion: Odoo 18 → Odoo 19. We converted our Odoo 18 branch to a new Odoo 19 branch/environment. This gave us an isolated space to work through the upgrade without touching the live production database. 
  3. Custom module migration. Every custom module we maintained had to be reviewed line by line and made installable on the Odoo 19 branch. This wasn’t a find-and-replace exercise — several modules needed real rework to match new model structures, renamed fields, and updated API signatures in Odoo 19. 
  4. Migration scripts. For both the database and custom modules, we wrote proper migration scripts (pre-migrate and post-migrate) rather than relying on manual data fixes. This let us safely carry forward data where field structures changed between versions, instead of losing information or patching things by hand after the fact. 
  5. Testing, and fixing what broke. Once the modules installed cleanly, we ran through our core business flows one by one. Several broke in ways that weren’t obvious from the code alone — only surfacing once we actually clicked through the process. Each broken flow got logged, root-caused, and fixed before we moved to the next. 
  6. Odoo.sh build health. Along the way we hit a number of warnings and outright errors on Odoo.sh during the build/deploy cycle. Each one had to be traced back to a specific module or migration step, fixed, and re-committed until every commit came back green. This iterative “commit, check the build, fix, recommit” loop was a big part of the actual day-to-day work. 

The Big One: Expense Reports Disappeared in Odoo 19 

This was the most disruptive change we encountered, and it’s worth calling out on its own. 

In Odoo 18, the Expense Report (hr.expense.sheet) was a core model — expenses were grouped into a report, and that report went through submission and approval. We had built substantial customizations on top of this model to match our internal approval workflow. 

In Odoo 19, Odoo removed the Expense Report model entirely. The stated reasoning is that most expense reports in practice only ever contained a single expense, so Odoo simplified the flow: expenses are now submitted, approved, and posted individually (or in bulk from the list view), with hr.expense.sheet’s remaining logic folded directly into hr.expense. If no expense manager is configured, expenses can even auto-approve on submission. 

That’s a reasonable simplification for a default install. It is not compatible with a heavily customized expense workflow built around the old report-based model. When we migrated, our customizations had nothing left to attach to — the model they extended no longer existed. 

How we solved it: 1. We first rebuilt the standard Odoo 18-style Expense Report behavior as a module on top of Odoo 19’s base Expenses app — effectively reintroducing the reporting layer Odoo had removed. 2. Once that base layer was working and stable, we re applied our original customizations on top of it.

This let us keep the approval workflow our business actually depends on, while still running on the current Odoo 19 core. It was easily the most time-consuming single piece of the entire migration, and it’s the kind of change that a purely “port the code over” approach to migration will not catch until testing — the module doesn’t fail to install, it just has nothing left to extend. 

If you’re planning your own 18 → 19 migration and have customized Expenses in any way, budget real time for this specifically. 

Other Technical Issues Worth Flagging

A few smaller but still meaningful changes we ran into along the way: 

Fields removed outright. Beyond Expenses, we hit cases where fields we relied on in custom code were removed in Odoo 19 rather than renamed, meaning a simple mapping wasn’t enough — the underlying logic had to be re-thought, not just re-pointed. XML ID changes in base modules. When Odoo renames or removes an XML ID, anything referencing it via env.ref() breaks — sometimes silently, sometimes at install time. Worth grepping your codebase for env.ref( before you start. 

The Result 

After working through the branch conversion, the module rewrites, the migration scripts, the expense report rebuild, and a fair number of Odoo.sh build cycles, we’re now running fully on Odoo 19 with our custom workflows intact — including the expense approval process our business actually needs, not just the simplified default. 

The overall lesson: a version upgrade like this is rarely about the database migration itself. The database step is mechanical and well supported. The real work — and the real risk — is in the custom code that sits on top of it, especially where Odoo has removed or restructured a model your business depended on. 

Planning Your Own Odoo 18 → 19 Migration? 

If you’re running custom modules on Odoo 18 — particularly around Expenses, or anything built on a model that Odoo has since restructured — it’s worth having that code audited before you commit to an upgrade timeline. We went through this exact process recently and know where the sharp edges are. 

Get in touch with our team if you’d like a second set of eyes on your migration plan.

Talk to AWS Certified Consultant

    Spread Love By Sharing:

    Let’s Talk About Your Needed AWS Infrastructure Management Services

    Have queries about your project idea or concept? Please drop in your project details to discuss with our AWS Global Cloud Infrastructure service specialists and consultants.

    • Swift Hiring and Onboarding
    • Experienced and Trained AWS Team
    • Quality Consulting and Programming
    Let’s Connect and Discuss Your Project