Khichdi InfoTech
Skip to content

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.

Guide: attribute of parent → field; many rows/workflow/ACL → new model.
Standard fit?

Configure and train

Odoo covers ≥80% of the process

Unique process?

Custom module

Competitive advantage depends on it

External master?

Integrate

Another system owns the data

Unclear ROI?

Defer / pilot

Scope is speculative

Compare stored, related, and non-stored compute for search and write cost.
OptionBest whenTrade-off
ConfigureStandard Odoo covers the needLimited uniqueness
CustomizeDifferentiating workflowUpgrade cost
IntegrateExternal system of recordSync complexity
Show product.template ↔ custom child model ↔ master data.
  • 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.

Designing a durable data model?

We help teams choose field vs model boundaries, access rules, and upgrade-safe schemas before custom development starts.

Business technology and software delivery