Building Custom Odoo Modules: A Practical Guide for European SMBs
Odoo has become the ERP of choice for thousands of small and mid-sized businesses across the Benelux and Nordic regions, and for good reason. It covers finance, inventory, manufacturing, CRM and HR in a single codebase, it runs comfortably on modest infrastructure, and its licensing model is far friendlier to a 60-person company than the enterprise suites built for multinationals.
But the moment a business hits a process that Odoo doesn't handle natively — a Belgian intracommunity VAT edge case, a Danish payroll integration, a bespoke quality-control workflow on the shop floor — the conversation turns to custom module development. That's where things get interesting, and where a lot of Odoo projects quietly go wrong.
This guide covers what custom Odoo module development actually involves, how to decide whether you need it, and how to build in a way that doesn't punish you at the next version upgrade.
First, Decide Whether You Actually Need a Custom Module
The most valuable Odoo development work is often the work you avoid. Before writing a line of Python, run the requirement through a short filter:
Can Odoo Studio handle it? Studio lets you add fields, adjust views, build automated actions and create simple reports without code. For roughly 40–50% of the "we need a customisation" requests we see, Studio is enough. It's not free of trade-offs — Studio customisations are stored as data and can be harder to version-control — but for a straightforward extra field on a sales order, it's the right tool.
Does a community or OCA module already exist? The Odoo Community Association maintains thousands of well-maintained, peer-reviewed modules covering everything from SEPA direct debit variants to advanced inventory valuation. For European localisation needs in particular, OCA is often years ahead of a from-scratch build. Check apps.odoo.com and the OCA GitHub repositories first.
Is the requirement a process problem in disguise? A Rotterdam distributor once asked us to build a complex three-tier approval workflow for purchase orders. When we mapped the actual approvals, two of the three tiers existed because of a control weakness in a legacy system that Odoo had already eliminated. The solution was a configuration change and a policy update, not a module.
Only when those three doors are closed does custom development earn its place.
Anatomy of a Well-Built Odoo Module
An Odoo module is a directory of Python and XML files that Odoo loads at startup. A clean structure looks roughly like this:
my_company_quality/
├── __init__.py
├── __manifest__.py
├── models/
│ ├── __init__.py
│ └── quality_check.py
├── views/
│ └── quality_check_views.xml
├── security/
│ ├── ir.model.access.csv
│ └── quality_security.xml
├── data/
│ └── quality_data.xml
├── report/
├── static/
└── tests/
└── test_quality_check.py
my_company_quality/
├── __init__.py
├── __manifest__.py
├── models/
│ ├── __init__.py
│ └── quality_check.py
├── views/
│ └── quality_check_views.xml
├── security/
│ ├── ir.model.access.csv
│ └── quality_security.xml
├── data/
│ └── quality_data.xml
├── report/
├── static/
└── tests/
└── test_quality_check.py
The __manifest__.py file declares your module's name, version, dependencies and data files. Getting dependencies right matters enormously: declaring depends: ['stock', 'mrp'] tells Odoo the load order and guarantees the models you're extending actually exist.
Extend, Don't Replace
The single most important principle in Odoo development is inheritance. Odoo gives you three mechanisms:
- Classical inheritance (
_inherit) — add fields and methods to an existing model. This is what you'll use 90% of the time. Adding ax_customs_codefield toproduct.templatetakes four lines. - Prototype inheritance (
_inherit+_name) — copy an existing model's structure into a new model. - Delegation (
_inherits) — embed one model inside another, the wayres.usersdelegates tores.partner.
For views, use XPath-based view inheritance rather than replacing whole views. If you overwrite the entire sales order form, every future Odoo improvement to that form disappears, and every other module touching it will conflict with yours.
A concrete example — adding a field to the sales order form:
<record id="view_order_form_inherit" model="ir.ui.view">
<field name="name">sale.order.form.customs</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='client_order_ref']" position="after">
<field name="x_export_declaration_ref"/>
</xpath>
</field>
</record>
<record id="view_order_form_inherit" model="ir.ui.view">
<field name="name">sale.order.form.customs</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='client_order_ref']" position="after">
<field name="x_export_declaration_ref"/>
</xpath>
</field>
</record>
Small, surgical, and it survives upgrades.
Never Modify Core Code
It bears repeating because it still happens: editing files in Odoo's addons directory directly is the fastest way to make your system unupgradeable and unsupportable. Every customisation belongs in your own module directory, tracked in Git.
The European Compliance Layer
For SMBs in the Benelux and Nordics, a large share of custom development is compliance-driven, and this is where local expertise pays for itself.
E-invoicing is the big one. Belgium made structured e-invoicing mandatory for B2B transactions from January 2026. The Netherlands, Norway, Denmark, Sweden and Finland all operate within the Peppol network with their own profile requirements. Odoo has strong native Peppol support, but mapping your product catalogue, tax codes and customer master data to a valid UBL document is real project work.
GDPR shapes data model design. If your custom module stores personal data — candidate records, customer service logs, employee performance notes — you need retention rules, a defensible legal basis and an actual mechanism for erasure requests. Building active flags and archiving logic in from day one is far cheaper than retrofitting it.
Local accounting and payroll differ meaningfully. Dutch btw reporting, Belgian Intervat filings, Norwegian SAF-T exports and Swedish bokföringslagen archiving requirements each carry specifics that a generic module won't cover. Use the localisation modules Odoo and OCA maintain, and build on top of them.
Multi-language and multi-currency are near-universal requirements in these markets. Make every user-facing string translatable, and never hardcode currency logic — use Odoo's res.currency conversion methods.
Testing, Deployment and the Upgrade Question
Custom modules need automated tests. Odoo's test framework (built on Python's unittest) lets you write tests that run against a temporary database. A test suite covering your business logic turns the annual upgrade from a week of manual clicking into a few hours of reviewing failures.
Run at least three environments: development, staging (a recent copy of production data) and production. Deploy through Git, not by copying files onto a server. Odoo.sh provides this out of the box; self-hosted setups can achieve the same with Docker and a modest CI pipeline.
On upgrades: Odoo releases a major version annually and supports each for roughly three years. The realistic cost of an upgrade is directly proportional to how much custom code you carry and how cleanly it was written. Modules built with proper inheritance typically need minor adjustments. Modules built by overwriting core behaviour often need rewriting.
Where External Expertise Changes the Economics
Many SMBs start with an internal developer learning Odoo on the job. That works for simple field additions. It becomes expensive when the requirements touch accounting engines, stock valuation, or the ORM's performance characteristics — areas where Odoo has non-obvious conventions and where mistakes surface months later as reconciliation errors.
The practical middle path most of our clients land on: bring in specialist support to establish the architecture, coding standards, testing setup and deployment pipeline, then hand day-to-day iteration to the internal team with periodic review. You get the speed of in-house ownership without inheriting a codebase that blocks your next upgrade.
At GEC Business Growth Services, we help SMBs across the Benelux and Nordics get Odoo right — from scoping which customisations genuinely earn their keep, to building upgrade-safe modules and localisation for e-invoicing and regional compliance. If you're planning an Odoo implementation or wrestling with custom code you've inherited, we're happy to take a look.