Skip to content
Odoo 17 · 18 · 19

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

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.

Odoo 19
  • Code in your Git repo
  • Tested on a sandbox first
  • No core files edited

Deliverables

What you walk away with

Not a demo, not a mockup. Files you can open, change and deploy without us.

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.

The process

How the work actually goes

  1. 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. 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. 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. 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.

sale_margin_floor/views/sale_order_views.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <record id="view_order_form_margin_floor" model="ir.ui.view">
        <field name="name">sale.order.form.margin.floor</field>
        <field name="model">sale.order</field>
        <field name="inherit_id" ref="sale.view_order_form"/>
        <field name="arch" type="xml">
            <xpath expr="//header" position="inside">
                <field name="below_margin_floor" invisible="1"/>
                <button name="action_request_margin_approval" type="object"
                        string="Request approval" class="btn-secondary"
                        invisible="not below_margin_floor or state != 'draft'"/>
            </xpath>
            <xpath expr="//sheet" position="before">
                <div class="alert alert-warning mb-0" role="alert"
                     invisible="not below_margin_floor or state not in ('draft', 'sent')">
                    Some lines are under the margin floor. A sales manager has to approve.
                </div>
            </xpath>
            <xpath expr="//field[@name='validity_date']" position="after">
                <field name="delivery_window" readonly="state in ('sale', 'cancel')"/>
            </xpath>
            <!-- Odoo 18+: the order lines use <list>, not <tree> -->
            <xpath expr="//field[@name='order_line']/list/field[@name='price_unit']"
                   position="after">
                <field name="margin_floor_breach" optional="show"
                       groups="sales_team.group_sale_manager"/>
            </xpath>
        </field>
    </record>
</odoo>
An excerpt from a generated module. Trimmed for the page.

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_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.

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.

Sources

The official documentation and source code this page was checked against.

  1. 01 View records, Odoo 19 developer documentation odoo.com
  2. 02 Odoo Online, Odoo 19 documentation odoo.com
  3. 03 Automation rules, Odoo 19 Studio documentation odoo.com
  4. 04 sale_margin addon (19.0), Odoo source code github.com
  5. 05 Purchase settings (19.0), Odoo source code github.com

Modules people build with this

Guides and terms for this work

Questions people ask us

Something missing? Email [email protected] and a person will answer.

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.

Other ways we can help

Your next module is one paragraph away

Write it the way you’d explain it to a new hire. We’ll turn it into an app you can read, test and install.

Odoo 19

Create your account

Free to start. No card needed.

By signing up you agree to our terms and privacy policy.