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 _inherits 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 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 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_confirmthat 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.
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 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 looks closely at how each one handles customization.