Introduction
The Many2One field is one of the foundational building blocks of the Odoo data model. Every time you link a sale order to a customer, assign a product to a category, or connect a task to a project, you are working with a Many2One relationship. It is the most common relational field type in Odoo, and understanding how it works is essential for anyone doing serious Odoo development or customization.
From a business perspective, Many2One fields are what give Odoo its coherence as an integrated system. Without them, every module would be an island. With them, data flows naturally from one record to another, and users can navigate between related documents without ever thinking about database structure.
This guide covers what the Many2One field stores, how it behaves in the Odoo ORM and in the interface, how to create and configure it using Odoo Studio or Python, and real business use cases from CRM, Sales, Inventory, and Accounting.
What is the Many2One Field in Odoo
In the Odoo ORM, a Many2One field creates a link from one record to exactly one record in another model. The name describes the relationship from the perspective of the current model: many records here can point to one record there. For example, many sale orders can point to one customer, and many products can belong to one product category.
At the database level, a Many2One field stores a foreign key in the current table. If a sale order is linked to customer ID 42, the partner_id column on the sale_order table holds the integer 42. Odoo handles the join and fetching of the related record name automatically.
In the user interface, a Many2One field appears as a dropdown or typeahead input. Users can start typing a name, and Odoo filters matching records in real time. Selecting one sets the link. The field displays the name of the linked record, not its internal ID. This makes it one of the most natural field types from a user experience standpoint.
Here is how it looks in a Python model definition:
from odoo import fields, models
class SaleOrder(models.Model):
_inherit = 'sale.order'
x_account_manager_id = fields.Many2One(
comodel_name='res.users',
string='Account Manager',
ondelete='set null',
)
The comodel_name parameter is the technical name of the model you are linking to. The ondelete parameter controls what happens to the current record if the linked record is deleted. The three options are cascade (delete the current record too), set null (clear the link), and restrict (block deletion if any record points to it).
In Odoo Studio, the Many2One field appears under the label Many2One in the field picker. When you add one through Studio, you choose the target model from a list, and Studio handles the field creation. This makes it one of the most accessible Odoo studio fields for setting up relationships without writing code.
How the Field Works
When you read a Many2One field through the Odoo ORM, you get a recordset containing the linked record. If the field is empty, you get an empty recordset. In Python, you can access the related record directly and navigate to its fields using dot notation:
order = self.env['sale.order'].browse(1)
customer_name = order.partner_id.name
customer_city = order.partner_id.city
This chained navigation is one of the most convenient aspects of the Odoo framework. You do not need to write explicit joins or separate queries. The ORM handles the database work in the background.
When reading via the XML-RPC API, a Many2One field returns a list with two elements: the integer ID of the linked record and its display name. For example, [42, "Acme Corp"]. If the field is empty, it returns False. This is important to know when processing API responses in your scripts.
Key Field Attributes
These are the most important properties you can configure on a Many2One field in the Odoo framework:
- comodel_name: The technical name of the target model. This is the only required parameter.
- ondelete: What happens to the current record when the linked record is deleted. Options are
'cascade','set null', and'restrict'. Defaults to'set null'. - domain: A filter that restricts which records the user can select in the dropdown. For example,
domain=[('customer_rank', '>', 0)]limits partner selection to customers only. - context: Extra context values passed when opening the related record or the dropdown. Useful for pre-filling fields on newly created related records.
- required: Makes the field mandatory. The record cannot be saved unless this field is set.
- readonly: Prevents users from changing the linked record. Useful when the link is set programmatically and should not be altered manually.
- delegate: When set to
True, all fields of the related model become directly accessible on the current model. This is used for model inheritance in Odoo development, not for regular relational fields.
How It Appears in Views
In form views, a Many2One field renders as a dropdown with a search input. Users can type a partial name to filter the list. They can also click the arrow icon next to the field to open the linked record directly, which is very useful for navigating between related documents without going through menus.
In list views, Many2One fields display the name of the linked record. They support group-by operations: you can group a list of sale orders by customer, or a list of tasks by project. This grouping is one of the most used features in Odoo reporting.
In search views, Many2One fields can be used as filters and group-by criteria. When you search by customer in the CRM pipeline, you are filtering on a Many2One field.
The Reciprocal One2Many
Every Many2One relationship has a natural reverse: the One2Many. If sale orders link to a customer via Many2One, the customer record can expose a list of all linked sale orders via a One2Many field. In Odoo development, it is good practice to create both sides of the relationship. This allows users to navigate from the customer form to all their orders without running a separate search. The One2Many field does not add a database column; it is computed from the Many2One foreign key on the other side.
Business Use Cases
The Many2One field is everywhere in a standard Odoo implementation. Here are five practical examples from real business workflows.
CRM: Linking Leads to Salespeople
In the Odoo CRM module, each lead or opportunity has a user_id field, which is a Many2One pointing to res.users. This is the salesperson responsible for the lead. Managers can filter the pipeline by salesperson, view conversion rates per rep, and assign leads in bulk. The Many2One field makes this kind of segmentation and reporting possible with no custom development. If you want to add a second salesperson or a dedicated account manager field, you simply add another Many2One field pointing to the same model.
Sales: Connecting Orders to Customers and Pricelists
A sale order in Odoo has at least two important Many2One fields: partner_id (the customer, linked to res.partner) and pricelist_id (the pricelist, linked to product.pricelist). When a salesperson selects a customer, Odoo can automatically populate the pricelist, payment terms, and delivery address based on the customer record. This automatic population is driven by onchange methods that read the Many2One value and fill related fields. It is one of the most visible benefits of a well-structured Odoo data model.
Inventory: Products and Categories
Each product in Odoo belongs to a product category via a Many2One field (categ_id on product.template). The product category controls accounting accounts for cost of goods sold and revenue, valuation methods, and removal strategies in warehouses. Getting the category assignment right on each product is critical for correct financial reporting. A Many2One field makes this assignment simple: one dropdown per product, one category record shared across hundreds of products.
Accounting: Journal Entries and Journals
Every journal entry in Odoo is linked to an accounting journal via a Many2One field (journal_id on account.move). The journal determines the sequence used for document numbering, the type of entry (sale, purchase, bank, cash, miscellaneous), and in some cases the default debit and credit accounts. Selecting the wrong journal on a vendor bill or a payment creates entries in the wrong section of the general ledger. The Many2One field here is not just a convenience; it is a control point for accounting accuracy.
Project Management: Tasks and Projects
In the Odoo Project module, every task belongs to a project via a Many2One field (project_id on project.task). This single link determines which stage progression the task follows, which team members can access it, and how timesheets are allocated. For professional services companies billing by project, the Many2One link between timesheets, tasks, and the parent project is the backbone of revenue recognition and invoicing workflows.
Creating or Customizing the Many2One Field
There are three main ways to add a Many2One field to an Odoo model, depending on your technical context and deployment approach.
Using Odoo Studio (No Code)
Odoo Studio is the built-in low-code customization tool. To add a Many2One field without writing any code:
- Open Odoo Studio from the main menu.
- Navigate to the form where you want the field.
- Drag a Many2One field from the field picker onto the form.
- In the properties panel, choose the target model from the list.
- Set the label and any optional domain filter to restrict which records users can select.
- Save and close Studio.
Studio creates the field with an x_studio_ prefix and adds it to the form view automatically. The field is immediately functional: users can open the dropdown, search for linked records, and select one. This is one of the fastest ways to extend a form with a relational link, and it requires no technical knowledge beyond understanding what the two models represent in your business.
Using Python in a Custom Module
For developers building Odoo modules, Many2One fields are defined directly in Python. This is the recommended approach for any Odoo development that needs to be version-controlled and deployed across multiple environments:
from odoo import fields, models
class ProjectTask(models.Model):
_inherit = 'project.task'
x_client_contact_id = fields.Many2One(
comodel_name='res.partner',
string='Client Contact',
domain=[('type', '=', 'contact')],
ondelete='set null',
help='The main contact at the client for this task.',
)
After defining the field in the model, include it in the relevant view XML and run an upgrade to apply the database changes. This approach gives full control over domain filters, ondelete behavior, and integration with compute methods and constraints. It is the standard Odoo developer guide approach for relational fields in production modules.
When creating a Many2One as part of Odoo customization, it is also good practice to add the corresponding One2Many field on the related model so users can navigate both directions of the relationship:
class ResPartner(models.Model):
_inherit = 'res.partner'
x_task_ids = fields.One2Many(
comodel_name='project.task',
inverse_name='x_client_contact_id',
string='Related Tasks',
)
Using the XML-RPC API
If you are managing Odoo customizations programmatically, for example through a deployment script or a remote configuration notebook, you can create Many2One fields via the XML-RPC API:
field_id = models.execute_kw(
ODOO_DB, uid, ODOO_API_KEY,
'ir.model.fields', 'create',
[{
'name': 'x_client_segment_id',
'field_description': 'Client Segment',
'model_id': model_id,
'ttype': 'many2one',
'relation': 'res.partner.category',
'on_delete': 'set null',
'state': 'manual',
}]
)
The relation key specifies the target model. The on_delete key sets the deletion behavior. When creating via the API, always remember to also create the reciprocal One2Many field on the related model so that navigation works from both sides of the relationship. This is one of the mandatory rules in Dasolo remote configurations for any Many2One field creation.
Best Practices
1. Always create the reciprocal One2Many
When you add a Many2One field to a model, create the corresponding One2Many on the related model as well. This does not cost you an extra database column, but it makes navigation much more natural for users. Without it, users have no way to see from the customer form which tasks, orders, or custom records are linked to them.
2. Use domain filters to restrict selection
A Many2One field pointing to res.partner gives access to all partners by default, including vendors, customers, internal users, and delivery addresses. If your business logic only makes sense for customers, add a domain filter like domain=[('customer_rank', '>', 0)]. This reduces noise in the dropdown and prevents users from making incorrect selections that look valid but produce wrong results downstream.
3. Choose ondelete carefully
The ondelete behavior matters more than it seems. Using 'cascade' means that deleting the linked record will also delete all records that point to it. This can cause accidental mass deletions if not intended. In most business scenarios, 'set null' is the safest choice: it clears the link without deleting anything. Use 'restrict' when the linked record should never be deleted as long as something points to it, for example a product category that has products assigned to it.
4. Avoid duplicating data that should be a Many2One
A common mistake in early Odoo customization projects is adding a char field to store a company name or a category label, when a Many2One pointing to an existing model would be the right approach. Char fields for values that already exist as records in another model create data duplication, make filtering and grouping unreliable, and produce inconsistencies when names change. If the data you need lives in another model, use a Many2One.
5. Use context to pre-fill related records
The context attribute on a Many2One field lets you pass default values when users create a new linked record directly from the dropdown. For example, if a Many2One points to a contact within a project, you can pre-fill the project on the new contact record using context. This reduces manual input and helps maintain data consistency when users create related records on the fly.
Common Pitfalls
Forgetting the reciprocal One2Many
Adding a Many2One field without creating the inverse One2Many on the related model is the most common oversight in Odoo customization. The result is a one-directional link: you can see the linked record from the current model, but from the linked record you have no way to find all records that point to it. Users start complaining that they cannot find related records, and someone ends up building a custom search or report to compensate for a missing inverse field.
Using cascade deletion without thinking it through
Setting ondelete='cascade' on a Many2One that links operational records to a master record can cause serious data loss. If a user archives or deletes a product category, and all products linked to it have cascade deletion, every product in that category disappears. In most cases, set null or restrict is the appropriate behavior for business data.
Not checking for False when navigating in Python
When a Many2One field is empty, reading it returns an empty recordset in Python, which is falsy. If your code does order.partner_id.name without checking whether partner_id is set, it will return an empty string rather than raising an error. That is often acceptable, but if you navigate several levels deep (order.partner_id.country_id.name) and any link in the chain is empty, you get an empty string at the end, which may silently produce wrong output in reports or emails. Always check for empty recordsets when the field is not required.
Pointing to the wrong model
In Odoo, res.partner is used for customers, vendors, contacts, and companies all at once. A Many2One pointing to res.partner without a domain filter gives access to all of them. If you intended the field for customers only but forgot the domain, salespeople will see internal users, delivery addresses, and vendor contacts in the dropdown. Always define a domain filter that matches the actual business intent of the field.
Overusing Many2One where a Selection field would do
If the linked values are a fixed, small list that never changes, a Selection field is often simpler and more performant than a Many2One. Many2One fields require a separate model with records, and they add a database join on every query. For something like a status with three or four options, a Selection field is cleaner. Use Many2One when the set of possible values is large, managed by users, or shared across multiple models.
Conclusion
The Many2One field is at the heart of how Odoo connects data across modules. Understanding it is not just a developer concern. Business analysts, functional consultants, and power users who want to extend Odoo with Studio all benefit from knowing what a Many2One does, when to use one, and what to watch out for.
The key points to remember: always create the reciprocal One2Many so navigation works from both sides, use domain filters to keep dropdowns clean and relevant, choose your ondelete behavior deliberately, and avoid using Many2One where a simpler Selection field would do the job.
Whether you are configuring a field through Odoo Studio, writing a custom Python module, or managing your Odoo data model through the XML-RPC API, getting relational fields right from the start makes your implementation more reliable and much easier to maintain over time.
At Dasolo, we help companies implement, customize, and optimize Odoo across all departments. Whether you need help designing a clean data model, adding relational fields to your forms, or building a full Odoo module from scratch, our team is here to support you. Reach out to us and let's talk about your Odoo project.