Multi-company functionality in Odoo 19 allows a single database to manage multiple legal entities while maintaining proper data segregation. One of the core mechanisms enabling this is the use of domains, which control record visibility and selection based on the active company or allowed companies.
This blog explains how multi-company domains work in Odoo 19, how they are implemented at both the UI and ORM levels, and how to correctly design them in custom modules.
Understanding Multi-Company Behavior in Odoo 19
In a multi-company environment:
- Each record may belong to a specific company (company_id)
- Some records are shared across companies (company_id = False)
- Users can have access to multiple companies simultaneously
Odoo automatically filters records based on:
- Current company (env.company)
- Allowed companies (env.companies)
Domain:
A domain in Odoo is a filter expression used to restrict records. It is defined as a list of tuples:
[('field_name', 'operator', value)]
Example:
[('company_id', '=', env.company.id)]Default Multi-Company Domain Behavior
Odoo internally applies a domain like:
['|', ('company_id', '=', False), ('company_id', 'in', allowed_company_ids)]This ensures:
- Shared records (no company) are visible
- Records belonging to allowed companies are visible
Using Domains in Fields (UI Level)
Domains are often applied in field definitions to restrict selectable records.
Example: Restricting a Many2one Field
partner_id = fields.Many2one(
'res.partner',
domain="[('company_id', 'in', allowed_company_ids)]"
)
With Shared Records
partner_id = fields.Many2one(
'res.partner',
domain="['|', ('company_id', '=', False), ('company_id', 'in', allowed_company_ids)]"
)
Using Domains in XML Views
You can also define domains directly in views.
Example:
<field name="partner_id" domain="[('company_id', 'in', allowed_company_ids)]"/>Using Domains in Python (ORM Level)
When writing backend logic, always ensure company-safe domains.
Example:
records = self.env['sale.order'].search([
('company_id', 'in', self.env.companies.ids)
])
Including Shared Records
records = self.env['sale.order'].search([
'|',
('company_id', '=', False),
('company_id', 'in', self.env.companies.ids)
])
Special Variables for Multi-Company
Odoo provides useful variables:
| Variable | Description |
| env.company | Current company |
| env.companies | Selected multiple companies |
| allowed_company_ids | Used in UI domains for allowed companies |
Common Mistakes
1. Using Only env.company
('company_id', '=', env.company.id)
Problem: Blocks access to other allowed companies
2. Ignoring Shared Records
('company_id', 'in', env.companies.ids)
Problem: Excludes global records
3. Hardcoding Company IDs
('company_id', '=', 1)
Problem: Breaks portability
Best Practices
- Always use env.companies.ids instead of env.company.id
- Include shared records when needed
- Avoid hardcoding company IDs
- Use consistent domain patterns across modules
Real-World Example
Scenario: Multi-Company Product Selection
You want users to select products only from allowed companies.
Python Field
product_id = fields.Many2one(
'product.product',
domain="['|', ('company_id', '=', False), ('company_id', 'in', allowed_company_ids)]"
)
XML View
<field name="product_id"
domain="['|', ('company_id', '=', False), ('company_id', 'in', allowed_company_ids)]"/>
Debugging Multi-Company Domains
- Enable developer mode
- Inspect applied domains in views
- Use logs to check env.companies.ids
Example:
_logger.info(self.env.companies.ids)
Multi-company domains are essential for maintaining data isolation and integrity in Odoo 19. Understanding how Odoo applies implicit domains and how to correctly extend them ensures that your custom modules behave correctly in complex multi-company setups.
By following best practices and avoiding common pitfalls, you can build robust, scalable multi-company solutions.
To read more about How to Use Dynamic Domains in Odoo 19 Views, refer to our blog How to Use Dynamic Domains in Odoo 19 Views.