# Odoo customization in code, so your changes survive the next version

Canonical: https://erpfly.com/odoo-customization/
Last updated: August 28, 2026

Odoo customization rarely starts big. A field on the quotation, a column in the order lines, a warning before someone confirms a bad deal. The question is where those changes end up living. erpfly writes them as a small inheriting addon, so they sit in Git and upgrade with the rest of your code.

### Deliverables

- **Fields on the models you already use.** New fields on sale.order, res.partner or stock.picking through _inherit, with computed values stored where you'll filter or group on them.
- **View changes by xpath.** Inherited form, list and search views that add or move elements without copying the original view. The standard view stays untouched underneath.
- **QWeb report tweaks.** Quotation, invoice and delivery slip templates inherited and adjusted, so a new Odoo release doesn't wipe out your layout.
- **Server actions and automation rules.** Contextual actions, automation rules and ir.cron jobs defined in XML data files, versioned with the code instead of typed into a settings screen.
- **OWL widgets where a field isn't enough.** A small OWL component registered as a field widget, for the rare case where the standard widgets can't show what your team needs.

### Process

1. **Share version, edition and hosting.** Odoo 17, 18 or 19, Community or Enterprise, Odoo.sh or self-hosted. If Studio is already in use, we'd like to know which views it has touched.
2. **Describe what should look different.** Screenshots with arrows are perfect. Tell us who should see the change and who shouldn't, because groups on fields are half of most Odoo customizations.
3. **Review on a copy of your database.** The addon is installed on a duplicate with your real data, so you see the new column on real orders, not on demo data.
4. **Merge and update the module.** Push to a branch on Odoo.sh or update with -u on your own server. Uninstall the addon later and its view changes go with it.

### Three places an Odoo change can live

In Odoo, a customization ends up in one of three homes, and they age very differently.

**Developer mode edits.** Turn on developer mode, open Edit View, change the arch. It works right away and it's the worst option. Standard views are reset when their module is updated, so your change can disappear during an ordinary update.

**Studio.** Enterprise only. Studio creates inherited views, `x_studio_` fields and automations for you through a friendly editor. It's good at what it's for. The catch is that everything lives in the database, so moving a change from staging to production means doing it again or exporting a generated zip, and reviewing a Studio change means clicking around to find it.

**An inheriting addon.** A small module that `_inherit`s the models and views it wants to change. It takes a developer (or erpfly) to write, and in return it's diffable, testable and repeatable on every database. This is what we build, and our longer write-up on [Odoo Studio versus a custom module](https://erpfly.com/blog/odoo-studio-vs-custom-module/) explains when we'd still reach for Studio.

### Inheritance does the heavy lifting

The reason Odoo customization can be safe at all is inheritance. You never edit `sale/views/sale_order_views.xml`. You write a new view whose `inherit_id` points at it, then use xpath to say where your changes go: `before`, `after`, `inside`, `replace` or `attributes`.

Models work the same way. Adding `_inherit = "sale.order"` with a new field extends the existing table. Override a method, call `super()`, and your logic runs alongside the standard behaviour instead of replacing it. There's a separate pattern, `_inherits`, which links a new model to an existing one by delegation, and mixing the two up causes some genuinely confusing bugs. We wrote a whole post on [`_inherit` vs `_inherits` in Odoo](https://erpfly.com/blog/odoo-inherit-vs-inherits/) because the question comes up so often.

Version matters in the XML too. Since Odoo 17, visibility and read-only rules are plain expressions like `invisible="state != 'draft'"` instead of `attrs` dictionaries. Since Odoo 18, list views are `<list>` rather than `<tree>`, so an inherited view still pointing at `order_line/tree` fails to install until someone fixes the path. And if you override how records are labelled, it's `_compute_display_name` now, not `name_get`.

### Reports, automations and the odd OWL widget

**QWeb reports.** The quotation PDF is a QWeb template, `sale.report_saleorder_document`. A customization inherits it and uses xpath, exactly like a form view. Please don't copy the whole template to change one line. That copy won't pick up fixes in the next version, and a year from now your developer won't know it was copied.

**Server actions and automation rules.** A server action adds an item to the Action menu or runs Python on selected records. Automation rules (called automated actions before Odoo 17) run when a record is created, updated or reaches a date. Both can be clicked together in settings, but erpfly defines them in XML data files so they reach production the same way the code does.

**OWL widgets.** The web client is built on OWL. If you need something a standard widget can't show, like a colour-coded margin bar inside a list, it's a small OWL component registered in the fields registry and loaded through `web.assets_backend` in the manifest. Most teams don't need one. Check the existing widgets before you pay for a custom one.

### Example: a margin floor for a food wholesaler

Picture a wholesaler selling chilled goods to restaurants. Margins are thin, prices move weekly, and salespeople under pressure knock a few percent off to win an order. Finance spots the damage at month end, when the orders are long delivered.

The request is the prompt at the top of this page. What erpfly builds:

- A dependency on `sale_margin`, which already computes cost and margin per order line. No point rebuilding that.
- A company-level margin floor setting and a stored computed field on the order that flags any line below it.
- An override of `action_confirm` that blocks salespeople, but not managers, from confirming a flagged order, plus the approval button and banner in the view on this page.
- An inherited QWeb template that adds the delivery window under the order details. The customer's PO number is already printed by the standard report when it's filled in, so that part needed nothing.

The part that takes the most thought isn't the XML. It's deciding who counts as a manager. Odoo's own Sales Administrator group is usually too broad, because it often includes people from finance and IT. So erpfly asks, and if needed creates a dedicated group for margin approvals, referenced from both the view and the Python check so the button and the rule can't disagree.

Roughly a dozen files, all small. For the wider sales flow this fits into, see the [sales order management module](https://erpfly.com/modules/sales-order-management/).

### When customizing Odoo is the wrong move

- **You're rebuilding a standard app by xpath.** When most of a form has been replaced, it's time for a separate model, and our page on [Odoo module development](https://erpfly.com/odoo-module-development/) covers that route.
- **The process isn't agreed yet.** Customizing to one manager's version of the approval rules means customizing again next month.
- **A setting exists.** Odoo already covers more than people think. Purchase has a built-in approval threshold for large orders, for instance. Check the settings of each app before writing code.

If you're still weighing platforms, our [comparison of ERPNext and Odoo](https://erpfly.com/compare/erpnext-vs-odoo/) looks closely at how each one handles customization.

### FAQ

### Should we use Studio or have the customization coded?

Studio is fine for a handful of fields on an Enterprise database, especially on Odoo Online where custom code isn't allowed. Once there's logic involved, or more than one environment to keep in sync, code wins, because you can review it, test it and see exactly what changed.

### We already have Studio changes. Can you work around them?

Yes, but tell us up front. Studio stores its changes as inherited views and x_studio_ fields in the database, and a coded view that targets the same element can collide with them. erpfly can recreate the important ones in code so you can retire the Studio versions.

### Will xpath customizations break when we upgrade Odoo?

Some will, and anyone claiming otherwise hasn't done many upgrades. If the element your xpath points at is renamed or moved, installation fails with a clear error. That's annoying but good, because the failure is loud and happens on staging. We anchor on field names rather than positions to keep it rare.

### Can you customize Odoo Online databases?

Not with code. Odoo Online doesn't allow custom Python modules, so the choices are Studio or moving to Odoo.sh or your own hosting. We'll help you decide whether the move is worth it.

### Is editing views in developer mode a customization?

Technically, and we'd avoid it. Direct edits to standard views are overwritten when that module is updated, so the change can vanish after a routine update, and the first sign is a user asking where the field went.

### Sources

- [View records, Odoo 19 developer documentation](https://www.odoo.com/documentation/19.0/developer/reference/user_interface/view_records.html)
- [Odoo Online, Odoo 19 documentation](https://www.odoo.com/documentation/19.0/administration/odoo_online.html)
- [Automation rules, Odoo 19 Studio documentation](https://www.odoo.com/documentation/19.0/applications/studio/automated_actions.html)
- [sale_margin addon (19.0), Odoo source code](https://github.com/odoo/odoo/tree/19.0/addons/sale_margin)
- [Purchase settings (19.0), Odoo source code](https://github.com/odoo/odoo/blob/19.0/addons/purchase/models/res_config_settings.py)