How to Understand Many2one vs One2many in Odoo 18: Complete Step by Step Guide
By Braincuber Team
Published on March 31, 2026
In Odoo 18, linking one model to another requires the use of relational fields. These fields are essential for connecting different models, ensuring data consistency, and maintaining logical relationships throughout your application. Odoo provides three types of relational fields: Many2one, One2many, and Many2many. This complete tutorial will break down the major differences between Many2one and One2many fields so you can choose the right one for your data modeling needs.
What You'll Learn:
- What Many2one fields are and how they work in Odoo 18
- What One2many fields are and how they differ from Many2one
- Complete syntax and parameters for both field types
- Database storage behavior for each relational field
- Practical examples with real-world Odoo models
- How to choose the right field for your use case
Understanding Relational Fields in Odoo 18
Relational fields are the backbone of data modeling in Odoo. They allow you to create connections between different models (database tables), enabling complex data relationships that mirror real-world business scenarios. Understanding when and how to use Many2one versus One2many is fundamental to building efficient, well-structured Odoo modules.
| Field Type | Relationship | Database Column |
|---|---|---|
| Many2one | Many records to One record | Adds foreign key column |
| One2many | One record to Many records | No new column (uses inverse Many2one) |
| Many2many | Many records to Many records | Creates junction table |
Step 1: Understanding Many2one Fields
A Many2one field connects multiple records from one model (called the comodel) to a single record in another model. In database terms, a Many2one field adds a foreign key column to the model's table, which stores the ID of the related record. This is the most commonly used relational field in Odoo.
Define the Relationship Direction
Many2one means many records on the current model point to one record on the target model. Think of it as a dropdown selector.
Database Storage
The data is stored directly in the current model's table as a foreign key column containing the ID of the related record.
field_id = fields.Many2one('comodel.name', 'Field Name')
Consider a practical example: employees (many) referencing a department (one). Many employees can belong to one department. Here's how you would define this relationship:
department_id = fields.Many2one('hr.department', string='Department')
# Access related field values:
department_name = department_id.name
Accessing Related Fields
Once you have a Many2one field defined, you can access any field from the related model using dot notation. For example, department_id.name retrieves the department name from the hr.department model.
In the form view, a Many2one field renders as a dropdown that allows users to select a single record from the target model. The selected record's display name appears in the field, and clicking it opens the related record's form view.
Step 2: Many2one Field Parameters
Many2one fields support a rich set of parameters that control their behavior, validation, and database performance. Understanding these parameters is crucial for building robust Odoo modules.
| Parameter | Type | Description |
|---|---|---|
| comodel_name | str | Name of the target related model. Mandatory attribute. |
| domain | list | Filter to limit selectable records. Eg: [('customer','=',True)] |
| context | dict | Passes extra info when selecting or creating. Pre-fills fields. |
| ondelete | str | Defines behavior when related record is deleted (cascade, set null, restrict). |
| auto_join | bool | Performs SQL JOIN with related model during search/read operations. |
| delegate | bool | Inherits fields of related model as if they belong to current model. |
| check_company | bool | Validates related record belongs to the same company. |
| default | any | Sets a default value for the field. |
| readonly | bool | Prevents users from changing the value. |
| required | bool | Marks the field as mandatory in the UI and validation. |
| tracking | bool | Enables change tracking visible in Chatter. Eg: tracking=True |
| index | bool | Creates a database index to speed up searches. Eg: index=True |
partner_id = fields.Many2one(
'res.partner',
string='Customer',
domain=[('customer_rank', '>', 0)],
context={'default_customer_rank': 1},
ondelete='restrict',
required=True,
tracking=True,
index=True,
)
The ondelete parameter is particularly important for data integrity. It has three options: 'cascade' deletes the dependent record when the related record is deleted, 'set null' removes the reference (sets to NULL), and 'restrict' prevents deletion of the related record if it is still in use.
Step 3: Understanding One2many Fields
A One2many field defines the reverse relationship of a Many2one field. It allows one record in the current model to relate to many records in another model. Unlike Many2one, a One2many field does not create a separate column in the database. Instead, it references the Many2one field in the related model that connects back.
Inverse Relationship
One2many is always defined as the inverse of an existing Many2one field. It uses the foreign key already defined in the related model.
Naming Convention
By convention, One2many field names use the _ids suffix to indicate they hold multiple record IDs.
field_ids = fields.One2many('comodel.name', 'inverse_name', 'Field Name')
There are two mandatory parameters for One2many fields: comodel_name (the name of the related model) and inverse_name (the name of the Many2one field in the comodel that links back). Let's look at a practical example:
# On res.partner model:
invoice_ids = fields.One2many(
'account.move',
'partner_id',
string='Invoices'
)
# Here, 'partner_id' is the Many2one field
# in the 'account.move' model that links back
No Database Column Created
Unlike Many2one, One2many does not create a new column in the database. It acts as a computed view that queries the inverse Many2one field on the target model. This means the Many2one field must exist first.
On the form view, a One2many field typically displays as a list (tree view) of all related records. Users can add, edit, and remove related records directly from this inline list view.
Step 4: One2many Field Parameters
One2many fields have fewer parameters than Many2one since they rely on the inverse Many2one field for most database-level behavior. The key parameters are:
| Parameter | Required | Description |
|---|---|---|
| comodel_name | Yes | Target model for the relation. |
| inverse_name | Yes | The Many2one field on the target model that links back. |
| domain | No | Limit the related records shown in the UI. |
| context | No | Prefill defaults when creating new sub-records. |
order_line_ids = fields.One2many(
'sale.order.line',
'order_id',
string='Order Lines',
context={'default_state': 'draft'},
)
The context parameter is especially useful for One2many fields because it allows you to set default values when users create new sub-records from the inline form view. This reduces data entry errors and speeds up the workflow.
Step 5: Key Differences Between Many2one and One2many
Understanding the core differences between these two field types is essential for proper data modeling in Odoo 18. Let's compare them side by side across all important dimensions.
| Aspect | Many2one | One2many |
|---|---|---|
| Relationship Direction | Many to One | One to Many |
| Database Column | Creates foreign key column | No column (uses inverse Many2one) |
| Mandatory Parameters | comodel_name | comodel_name + inverse_name |
| UI Representation | Dropdown selector | List/tree view of records |
| Naming Convention | field_id suffix | field_ids suffix |
| ondelete Support | Yes (cascade, set null, restrict) | No (controlled by inverse Many2one) |
| Standalone Definition | Yes | No (requires existing Many2one) |
Many2one Use Case
Use when multiple records reference a single record. Example: Many employees belong to one department, many orders belong to one customer.
One2many Use Case
Use when one record has multiple child records. Example: One customer has many invoices, one order has many order lines.
Always Define Many2one First
Since One2many depends on an existing Many2one field, always define the Many2one field on the target model first. Then define the One2many inverse on the source model. The order of definition matters for proper module loading.
Step 6: Complete Working Example
Let's put everything together with a complete example that demonstrates both Many2one and One2many fields working in tandem. This example shows a department-employee relationship.
from odoo import fields, models
class HrDepartment(models.Model):
_name = 'hr.department'
_description = 'Department'
name = fields.Char(string='Department Name', required=True)
# One2many: One department has many employees
employee_ids = fields.One2many(
'hr.employee',
'department_id',
string='Employees'
)
from odoo import fields, models
class HrEmployee(models.Model):
_name = 'hr.employee'
_description = 'Employee'
name = fields.Char(string='Employee Name', required=True)
# Many2one: Many employees belong to one department
department_id = fields.Many2one(
'hr.department',
string='Department',
ondelete='set null',
index=True,
)
In this example, the department_id Many2one field on hr.employee creates a foreign key column in the employee table. The employee_ids One2many field on hr.department does not create any new column. Instead, it queries the employee table using the department_id foreign key to find all employees belonging to that department.
Choosing the Right Field Type
Understanding when to use Many2one versus One2many comes down to identifying the direction of the relationship you need to model. In Odoo 18, mastering these relational fields is essential for building effective data models. Many2one links many records to one, while One2many does the reverse. The choice depends entirely on which side of the relationship you are defining the field from.
Use Many2one When
You need to select a single related record from a dropdown. The current model "belongs to" the target model. You need ondelete behavior control.
Use One2many When
You want to display and manage child records inline. The current model "has many" of the target model. You already have a Many2one on the target side.
Frequently Asked Questions
What is the main difference between Many2one and One2many in Odoo 18?
Many2one links many records to one record and creates a foreign key column in the database. One2many is the reverse relationship that links one record to many records without creating a new column, relying on the inverse Many2one field instead.
Can I define a One2many field without a Many2one field?
No. One2many fields require an existing Many2one field on the target model as the inverse_name parameter. The Many2one field must be defined first since it creates the actual database foreign key that One2many references.
What does the ondelete parameter do in Many2one fields?
The ondelete parameter controls what happens when the related record is deleted. 'cascade' deletes the dependent record too, 'set null' removes the reference, and 'restrict' prevents deletion if the record is still in use.
How do I filter selectable records in a Many2one field?
Use the domain parameter to filter records. For example, domain="[('customer_rank', '>', 0)]" limits the dropdown to only show customer records. You can also use dynamic domains that reference other field values.
What is the naming convention for One2many fields in Odoo?
One2many fields should use the '_ids' suffix by convention (e.g., employee_ids, order_line_ids). This makes it immediately clear that the field holds multiple record IDs rather than a single reference.
Need Help with Odoo?
Our experts can help you configure relational fields, build custom modules, and optimize your Odoo 18 data models for performance and scalability.
