Two class attributes, one letter apart, doing genuinely different things at the database level: _inherit extends an existing model in place; _inherits creates a distinct new model that transparently delegates to another one. Reaching for the wrong one doesn’t error — it just produces a model whose fields live somewhere other than where the code seems to imply.
The short version: _inherit = 'some.model' alone adds your new fields directly onto that model's existing table — no new table, no foreign key, just more columns. _inherits = {'some.model': 'field_name'} creates a genuinely separate model with its own table, plus a required, auto-generated many2one pointing at the parent — and every one of the parent's fields becomes transparently readable/writable on the child anyway, through that delegation.
_inherit alone: extension, same table
The most common pattern in Odoo module development by far — adding a field or overriding a method on an existing model, with no new table involved at all:
class ResPartner(models.Model):
_inherit = 'res.partner'
loyalty_points = fields.Integer(string='Loyalty Points')
Per Odoo’s own developer documentation on inheritance, this extends the existing model — loyalty_points becomes a genuine new column on the actual res_partner table, exactly as if it had been defined in res.partner’s own original class. There’s no separate table, no foreign key, no delegation of any kind: it’s the same table, with more columns than before this module was installed.
_inherits: a new model, transparently proxying another
class ProductProduct(models.Model):
_name = 'product.product'
_inherits = {'product.template': 'product_tmpl_id'}
This is a completely different mechanism: _inherits creates a genuinely new, distinct model — with its own table — and an auto-generated, required many2one field (named whatever the dict’s value says, product_tmpl_id above) pointing at the named parent model. Every field defined on the parent (product.template) then becomes readable and writable directly on the child (product.product) as if it were defined there too — reads and writes are silently proxied through that many2one to the actual parent record.
res.users delegating to res.partner, and product.product delegating to product.template, are the two textbook real examples in Odoo core — in both cases, the child is a genuinely distinct, independently-identified record (its own id, its own access rights, its own menu if it needs one) that happens to transparently expose another record’s fields through delegation, rather than duplicating them.
Why this distinction is worth knowing precisely
_inherit alone | _inherits | |
|---|---|---|
| New database table? | No — extends the existing one | Yes — a genuinely new, separate table |
| New foreign key? | No | Yes — a required, auto-generated many2one |
| The child’s own identity | Same record, same id, as the parent model | A distinct record with its own id, linked to a parent record |
| Typical use | Adding a field/behavior to an existing model | Modeling “is fundamentally a kind of X, but needs its own identity/menu/rights” |
Picking _inherits when a plain _inherit would do creates an unnecessary extra table and a delegation relationship with nothing to actually delegate meaningfully — the child ends up being, for most practical purposes, indistinguishable from just having extended the parent directly, at the cost of a genuinely separate model to reason about. Picking plain _inherit when the real intent is “a distinct business object that happens to reuse another one’s fields” (its own menu, its own access rights entirely separate from the parent’s) can’t express that at all — there’s no way to give the extended model its own independent identity without _inherits’ actual delegation mechanism.
Watch out: a model can also set both _name (a genuinely new name) and _inherit (naming an existing model) together — a distinct third pattern sometimes called "prototype" inheritance, which copies the parent's field *definitions* into a brand-new, otherwise entirely independent table with no relationship to the parent's actual records at all. It's a much less common pattern than either of the two above, and worth not confusing with plain _inherit (same table) just because the attribute name is identical.
Frequently asked questions
Can a model use both _inherit and _inherits at the same time?
Yes — a model can extend one model's table via _inherit while separately delegating to a different model via _inherits; the two mechanisms address different relationships and aren't mutually exclusive.
Does _inherits let me override the parent's methods, the way _inherit does?
No — _inherits only proxies field reads/writes through the delegation, it doesn't give the child model the parent's own Python methods. Overriding behavior (not just fields) requires _inherit.
Is the auto-generated many2one field from _inherits required?
Yes — every record of the child model must be linked to exactly one parent record through that field, since it's what every proxied field read/write actually resolves against.
Which one does a typical "just add a field to an existing screen" module need?
Plain _inherit, almost always — that's precisely the "extend an existing model" case, with no need for the new record's own separate identity that _inherits exists to provide.
Do access rights and record rules need to be defined separately for a model using _inherits?
Yes — because it's a genuinely distinct model with its own table, it needs its own ir.model.access.csv rows and, if relevant, its own record rules, exactly like any other model; delegation doesn't imply inheriting the parent's own access configuration.
Further reading
- Chapter 12: Inheritance — official Odoo 19.0 developer tutorial, covering Python inheritance, model inheritance, and view inheritance together.
And on this site: One2many vs Many2many for the other foundational relational-field distinction this one pairs naturally with, and the External ID / XML ID Helper for naming access-rights rows on a new _inherits-based model consistently.