When the topic comes to Odoo 20, the traditional way of using record rules (ir.rule) and access rights (ir.model.access) for security is completely changed. In Odoo 20 a single ir.access model handles both the record rules and access rights.
Implementing multi_company data security in Odoo 20 requires a new approach, a unified ir.access.csv file. Here is how to build a multi-company record rule in Odoo 20. Let's get started.
1. Model Setup:
In order to apply the multi_company rule, the model must be configured to handle company data.
- Set the model parameter _check_company_auto = True. This will trigger the _check_company method on write and create operations to ensure company consistency on the relational fields having check_company=True.
- Define a company_id field with a default value of lambda self: self.env.company. This will make the record’s company ownership upon creation of the record.
company_id = fields.Many2one(
comodel_name='res.company',
string='Company',
default=lambda self: self.env.company,
)
- Add check_company=True to any relational fields (e.g., a Many2one pointing to res.partner). This ensures that only data related to the same company is linked, and if not, Odoo raises an error. This prevents attaching a Company A contact to a Company B document.
partner_id = fields.Many2one(
comodel_name='res.partner',
string='Partner',
check_company=True,
)
2. Permission and Restriction in ir.access.csv
Instead of writing complex XML records for ir.rule, Odoo 20 handles restrictions (record rules) directly in the CSV along with access rights.
Creating a row in the security/ir.access.csv file with the group column (group_id/id) blank and the value in the domain column will create a global restriction for every user. Leaving the group column blank means this rule or restriction is applicable to every group. So the records will be filtered based on the domain in the domain for all users.
Creating a row with a value in the group column will create permission for the specified group. By giving value in the group column, Odoo 20 reads it as an access right and gives access to the group specified in the group column.
Creating a row with a value in the group column and the domain column will create a permission with a restriction. That is, the group specified in the group column will have access to the records, but the records will be filtered based on the domain in the domain column.
The value in the options column determines what operations the user can perform. Set the options as crud to give full permission to all operations. To give permission for a single operation, set the value of the options column as c or r or u or d for create, read, update, or delete respectively. When setting a value to the options column, the order of letters must be followed.
| id | name | model_id | group_id/id | operation | domain |
| permission | test.model | test.model | base.group_user | crud |
|
| multi-companyrestriction | multi-company test.model | test.model |
| crud | ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] |
3. How the Context Powers company_ids
The company_ids variable in the restriction evaluates dynamically based on the current user's active session. When a user toggles their active companies using the checkboxes in the Odoo systray on the top left corner, the UI injects the allowed_company_ids key into the session context. The Odoo ORM immediately reads this context and populates env.companies with the selected company records. Your CSV domain dynamically compares the record's company_id against this active environment to filter visibility on the fly.
4. Common Leaks
Small configuration mistakes can easily expose multi_company data.
- Quoting Variables: Writing "company_ids" inside quotes within your domain makes Odoo evaluate it as a string rather than a dynamic list of company IDs, leading to immediate access errors when users try to create records.
- Accidental Group Assignment: Assigning a specific group to your restriction row means users outside that group bypass the rule entirely.
- Sudo Usage: Using .sudo() in backend Python logic can bypass all restrictions.
Odoo 20 makes its security architecture very easy by combining record rules and access rights under one ir.access model without risking data separation for record rules. By applying control mechanisms like _check_company_auto=True and check_company=True combined with global limitations without using any group, you will make sure that your data is completely safe, even when there are multiple companies present. It is better to apply these mechanisms from the beginning stage of the development process to save yourself from any future security problems.
To read more about How to Migrate ir.model.access.csv and Record Rules to ir.access.csv in Odoo 20, refer to our blog, How to Migrate ir.model.access.csv and Record Rules to ir.access.csv in Odoo 20.