All Articles
Odoo Development & Customization

Odoo Custom Module Development: Best Practices for Clean, Upgrade-Safe Code

Shahid MalikBy Shahid MalikAugust 23, 202610 min read

Master the design patterns senior Odoo developers use to write maintainable, performant, and upgrade-safe custom modules—from ORM inheritance to security rules.

Writing custom Odoo modules that survive major software updates, maintain fast execution times, and remain readable requires software engineering discipline. As Odoo releases major versions annually (Odoo 17, 18, and 19), unrefined custom code quickly turns into technical debt — the same debt I cover from the process side in Odoo customization best practices.

Following established architectural patterns ensures your custom apps integrate smoothly with native Odoo modules while simplifying future database migrations.

Key Development Principles at a Glance

Core Pillars of Custom Odoo Development

1. Extension Inheritance2. Logic in Models3. Granular Security4. Automated Testing5. Upgrade Protection

1. Use Extension Inheritance, Not Copying

Always extend core models using _inherit rather than redefining existing models or hacking core source code. Extension inheritance ensures your custom logic remains compatible when base Odoo code receives security patches or version updates.

# Good Practice: Extension Inheritance
from odoo import models, fields, api

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    custom_commission_rate = fields.Float(
        string="Commission Rate (%)",
        digits=(16, 2),
        help="Custom sales representative commission rate."
    )
  • View Extensions: Always use XPath expressions (<xpath expr="..." position="...">) to modify existing forms, trees, or kanban views instead of overriding entire view templates.

  • Keep Dependencies Explicit: Declare all module dependencies accurately inside your manifest.py file under the 'depends' key to avoid load-order errors.

2. Keep Business Logic Inside Models

Resist the temptation to embed heavy business logic inside HTTP controllers, webhooks, or UI wizards. Models are the single source of truth — keeping logic inside model methods makes features reusable across automated server actions, XML-RPC APIs, and external integrations.

# Good Practice: Clean, Reusable Compute Logic
@api.depends('order_line.price_total')
def _compute_custom_totals(self):
    for order in self:
        # Use filtered() or mapped() over manual loops where clean
        discounted_lines = order.order_line.filtered(lambda l: l.discount > 0)
        order.total_discounted_amount = sum(discounted_lines.mapped('price_subtotal'))

Performance Rules for ORM Logic

  • Avoid Database Operations in Loops: Never execute direct SQL queries or trigger write calls (write(), create()) inside loops.

  • Batch Operations: Perform record operations across recordsets (e.g., records.write({'state': 'done'})) rather than iterating through records individually.

  • Decorators: Use @api.depends() correctly to compute stored fields, and avoid non-stored compute fields in large list views to prevent SQL query bloat.

3. Implement Security Rules Early

Security should never be retrofitted at the end of a project. Define access control rules (ir.model.access.csv) and record rules (ir.rule) right from the initial module structure.


id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_custom_deal_user,custom.deal.user,model_custom_deal,base.group_user,1,1,1,0
access_custom_deal_manager,custom.deal.manager,model_custom_deal,sales_team.group_sale_manager,1,1,1,1

  • Avoid Overusing base.group_user: Define explicit custom access groups inside security/security_groups.xml.

  • Sudo Guardrails: Avoid calling .sudo() across entire processes. Use .sudo() sparingly and restrict its scope strictly to security bypasses required by lower-privilege operations.

4. Write Automated Unit & Integration Tests

Odoo features a fast, built-in test runner built on Python's unittest framework. Writing unit tests for business logic, custom calculations, and constraint methods prevents quiet regressions during version updates.

from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError

class TestCustomSales(TransactionCase):

    def setUp(self):
        super(TestCustomSales, self).setUp()
        self.partner = self.env['res.partner'].create({'name': 'Test Partner'})

    def test_commission_calculation(self):
        order = self.env['sale.order'].create({
            'partner_id': self.partner.id,
            'custom_commission_rate': 10.0,
        })
        self.assertEqual(order.custom_commission_rate, 10.0)

Run tests locally during development using the following command:

odoo-bin -c odoo.conf -d test_db -i custom_module_name --test-enable --stop-after-init

5. Build for Safe Upgrades

To ensure your custom modules move seamlessly through version migrations (e.g., from Odoo 17/18 to 19):

  • Avoid Overriding _auto_init or init: Use standard field definitions and ORM tools instead of writing custom raw SQL DDL unless required for complex database views.

  • Use Hooks for Data Transformations: Execute data migrations using post_init_hook or dedicated migration scripts inside the migrations/ directory.

  • Keep XML IDs Consistent: Never alter primary xml_id identifiers once deployed to production, as this corrupts database references during updates.

Custom Module Quality Checklist

AreaBest Practice CheckRisk Avoided
ArchitectureInherit existing models using _inheritPreserves standard core fixes and features
PerformanceBatch record updates (write, create)Eliminates slow UI responses and memory spikes
SecurityExplicit ir.model.access.csv and Record RulesPrevents unauthorized data access across users
UpgradesUse XPath selectors in views, avoid hardcoded IDsPrevents broken interfaces during major upgrades

Need Support with Custom Odoo Development?

Building enterprise-grade Odoo systems takes backend Python expertise combined with a real understanding of Odoo's core workflows — the patterns above are the difference between a module that survives its first major upgrade and one that becomes next year's rewrite. If you're planning custom development, refactoring legacy modules, or want an experienced second opinion on your architecture before you build, get in touch or see how this fits into full Odoo implementation work.

Related Articles

Odoo Development & Customization

Odoo 20: What’s Coming in the Next Odoo Release?

Odoo 20 is set to bring deeper AI, smarter automation, improved service planning, better user experience, and more connected business workflows. Here is what businesses and Odoo users should know before upgrading.

5 min read
Odoo Development & Customization

Odoo v19: What's New for Developers in 2025

A deep dive into the major developer-facing changes in Odoo 19 — new OWL components, improved Python APIs, and performance upgrades.

6 min read
Shahid Malik - AI-First Odoo Consultant

Shahid Malik

AI-First Odoo ERP Specialist

Shahid Malik is an AI-first Odoo consultant helping businesses solve complex ERP and business process challenges. His work combines Odoo consulting, process optimization, automation, integrations, migrations, and practical AI solutions to build scalable and reliable business systems.

Book a consultation for your Odoo project
Discuss Your Odoo Project