Odoo Customization · intermediate · 12 min read
Custom Fields vs Custom Models in Odoo
Decide when to extend product.template (or other core models) with fields versus creating a new model—covering relations, stored vs related fields, performance, and security.
Last reviewed 2026-07-30 · Estimated effort: 2–6 weeks typical implementation slice
Table of contents
Extend an existing model when the new data is an attribute of that business object—for example a compliance code on `product.template` or a delivery promise on `sale.order`. Create a new model when you are tracking a separate lifecycle, many rows per parent, or a process that needs its own states, chatter, and access rules.
Relational design matters as much as the field type. Many2one / One2many keep history and cardinality honest; stuffing repeating data into Char fields or free-text JSON usually becomes a reporting and upgrade problem later.
Choose stored, related, or non-stored compute fields based on search, report, and write frequency. New models always need ir.model.access.csv (and often record rules)—fields alone inherit the parent model’s security, which is both a benefit and a risk if you overshare sensitive attributes.
One of the earliest design choices in an Odoo customization is whether to add columns on a standard model or introduce something like `x_quality.inspection` or `product.certification`. The wrong choice creates either a bloated product form or a forest of models nobody can report on.
This guide walks through decision criteria with `product.template` as the running example, then covers relational patterns, stored versus related fields, performance implications, and security expectations for brand-new models.
Add fields on `product.template` when the value is a property of the product itself: brand segment, hazardous-goods flag, default subcontract route hint, or a single certification number that users expect to see on the product form and on order lines via related fields.
The same rule applies to `res.partner`, `sale.order`, and `stock.picking`: if users naturally open that form to edit the value, and cardinality is one-per-record, a field (or a small set of fields) is usually enough. Prefer Selection or Many2one to master data instead of free text when values must be filtered and reported.
- ▸One value per parent record, edited on that form
- ▸Needed on reports/search of that model
- ▸No separate workflow, approvals, or history beyond chatter notes
- ▸Upgrade impact is a column add—not a new process engine
Create a new model when each parent can have many child events (inspections, price agreements, serial-level tests), when the entity has its own state machine, or when different teams need different ACLs than the parent. A quality check that moves draft → done → failed is a model; a single “QC required” checkbox is a field.
New models also win when you must attach documents, activities, or dedicated list/kanban views. Trying to simulate that with one2many of ad-hoc lines hidden on the product form often becomes unmaintainable.
Link custom models to core with Many2one (`product_tmpl_id`) and expose One2many on the parent for UX. Use a separate master model for reusable catalogs (certification types, inspection templates) instead of hard-coding Selection keys that product owners will ask to change weekly.
Avoid circular depends between custom modules. If both product extensions and a quality app need each other, extract the shared fields into a thin bridge module or keep the relation optional.
- ▸Many2one to parent + One2many inverse for lists
- ▸Master data models for reusable taxonomies
- ▸Cascade vs restrict ondelete chosen deliberately
- ▸Index foreign keys you filter on in domains and reports
Related fields are convenient for showing `partner_id.country_id` on a child, but non-stored related/compute fields cannot be searched efficiently and may recompute often in list views. Store values you filter, group, or export regularly; keep pure display helpers non-stored.
Heavy stored computes on `product.template` or `sale.order.line` hurt write performance if they depend on wide relation graphs. Prefer recomputing on explicit triggers, batch cron for analytics, or inverse methods that write only when inputs change. Measure with real catalog sizes on staging—not a three-product demo DB.
Every new model needs access rights in `security/ir.model.access.csv`. Decide who can read, write, create, and unlink by group. If rows are company-specific or salesperson-specific, add record rules; do not rely on “users will only open the menu we gave them.”
Fields on core models inherit that model’s ACLs. That is fine for operational flags; it is wrong for salary-like or personally sensitive data—those belong on a restricted model or behind groups that hide the view and field.
- ✓Default to fields on the core model for simple attributes; promote to a new model when cardinality or workflow appears.
- ✓Use Many2one/One2many for repeating history; never encode lists in Char or HTML fields.
- ✓Store fields that must be searchable, groupable, or used in domains; keep decorative related fields non-stored.
- ✓Add `index=True` on foreign keys and high-cardinality filters used in warehouse or ecommerce traffic.
- ✓Ship `ir.model.access.csv` (and record rules if needed) in the same module as the new model—never as a follow-up ticket.
- ✓Document ownership: which team maintains the model, which reports depend on it, and which modules may depend on it.
- ✓Review the product form after field additions; if the sheet is crowded, move detail into a notebook page or dedicated model.
- !Adding twenty sparse columns on `product.template` instead of a related configuration model.
- !Creating a new model for a single checkbox that never has its own lifecycle.
- !Using non-stored related fields in search views and wondering why list filters are slow.
- !Forgetting access rights so only Administrator can see the new model in production.
- !Leaving `ondelete='cascade'` on accidental links that wipe history when a product is archived incorrectly.
- !Putting confidential attributes on widely readable core models without field-level group restrictions.
- !Computing expensive stored fields on every write of high-volume transactional models.
Choose fields when the data is an attribute of an existing business object; choose models when you are managing a separate lifecycle, cardinality, or security boundary. Get the relation and storage choices right early—refactoring later is possible but costly in UAT and reporting.
If you are unsure, prototype the form and the main report. Whichever design makes both obvious without heroic domains is usually the one to ship.
Configure and train
Odoo covers ≥80% of the process
Custom module
Competitive advantage depends on it
Integrate
Another system owns the data
Defer / pilot
Scope is speculative
| Option | Best when | Trade-off |
|---|---|---|
| Configure | Standard Odoo covers the need | Limited uniqueness |
| Customize | Differentiating workflow | Upgrade cost |
| Integrate | External system of record | Sync complexity |
Client / Channel
Users & devices
Odoo Application
Business logic
PostgreSQL
System of record
Workers / Cron
Async work
- clientapp(HTTP)
- appdb(ORM)
- appjobs(queue)
Frequently asked questions
Should Studio custom fields and Python fields be mixed on the same model?
Prefer one ownership path. Studio fields are harder to version-control and review. If the project already uses Git-based modules, add fields in Python/XML. If Studio already owns a field, avoid duplicating it in code—migrate it into the module when you professionalize the customization.
Can I always use related fields instead of duplicating data?
For display, yes. For search, domain, and reporting, store a copy or a dedicated field when users filter on it constantly. Related fields follow the source; if the source changes often across large graphs, stored computes need careful dependency lists.
How do I migrate from fields-on-product to a new model later?
Create the model, write a post-migration script that copies values into new rows, switch views to the One2many, then deprecate the old columns in a later module version. Plan dual-read during UAT so reports can be compared side by side.
Do custom models slow down Odoo more than extra fields?
Not inherently. Poor indexes, unbounded One2many lists on form load, and heavy computes hurt more than an extra table. A well-indexed child model is often faster than scanning wide product rows for sparse attributes.
Related articles
How Custom Odoo Modules Work
A practical tour of custom Odoo modules: __manifest__.py, models, view XML, security CSV, data XML, dependencies, install vs upgrade, and _inherit.
View →How to Build Upgrade-Friendly Odoo Modules
Practical patterns for custom Odoo modules that survive major version upgrades: inheritance over core edits, stable views, minimal overrides, tests, and migration scripts.
View →When Should You Customize Odoo?
Decision criteria for customizing Odoo: when to change the process instead, when Studio is enough, and when a custom module is justified.
View →Odoo Customization Best Practices
Operational best practices for sustainable Odoo customization: staging, code review, ACL and record rules, documentation, definition of done, small PRs, and post-deploy monitoring.
View →Designing a durable data model?
We help teams choose field vs model boundaries, access rules, and upgrade-safe schemas before custom development starts.
