Odoo 18 Multi-Company Setup: Guidelines and Tips
By Braincuber Team
Published on February 26, 2026
Managing multiple companies within a single Odoo 18 instance is powerful — and dangerous if you get it wrong. One misconfigured security rule and your warehouse staff in Company B is looking at Company A's purchase orders. One missing company_dependent=True flag and your product costs are bleeding across legal entities. This guide walks through the ORM mechanisms Odoo 18 provides for multi-company operations, with real code patterns and the pitfalls we've seen break live deployments.
What You'll Learn:
- How company-dependent fields store different values per company without duplicating records
- Enforcing multi-company consistency with
_check_company_autoandcheck_company - Setting default company on
company_idfields to prevent creation failures - Writing security rules that restrict data access by company
- Common mistakes that cause cross-company data leaks
Odoo 18 Multi-Company ORM Mechanisms
Odoo's ORM includes several built-in mechanisms to handle multi-company operations. Get these wrong and you're looking at accounting discrepancies, inventory errors, and compliance violations across your legal entities.
Company-Dependent Fields
Store different values per company on the same record. One product, different costs across 3 legal entities — without duplicating the product record.
Multi-Company Consistency
Automatic validation that related records belong to the same company during create() and write() operations. Prevents cross-entity contamination.
Default Company
Auto-assign the correct company context when creating records via backend scripts, automated actions, or wizards. Prevents missing company_id errors.
Security Rules
Record-level rules based on company_ids that restrict users to data belonging only to their authorized companies.
Company-Dependent Fields
In a multi-company environment, shared records like products, accounts, and partners often need different configuration values per company — without duplicating the entire record. Odoo achieves this with the company_dependent=True parameter in field definitions.
Think of it this way: you've got one product called "Widget X" that exists in Company A and Company B. Company A buys it for $14.50. Company B buys it for $18.75. Without company-dependent fields, you'd need two separate product records. With it, one record holds both values.
Defining a Company-Dependent Field
display_name = fields.Char(company_dependent=True)
How Company Context Works
When you read a company-dependent field, Odoo returns the value set for your current company context. If User A is logged into Company A, they see Company A's value. Switch to Company B, and they see Company B's value — same record, different data.
The standard_price field on products is a textbook example. It stores different costs within different companies for the same product. But here's where people mess up: if you need to read values set by a different company, you must explicitly switch the company context using with_company().
User A Logs into Company A
Updates the display_name field on a shared record. That value is stored only for Company A.
User A Switches to Company B
Accesses the same record. The display_name field returns a different value — the one set by Company B.
Reading Cross-Company Values
To read Company B's value from Company A's context, use record.with_company(company_b).field_name. Without this, you'll always get Company A's value.
Common Pitfall
If you forget with_company() when reading cross-company data in cron jobs or server actions, you'll silently get the wrong company's values. No error. No warning. Just wrong data flowing into your reports and calculations.
Multi-Company Consistency
Maintaining data consistency across companies is non-negotiable. Whether you're linking partners to invoices or associating products to stock moves, every related record must belong to the same company. Get this wrong and you're looking at accounting discrepancies that take weeks to untangle.
Enforcing Consistency — Two Required Steps
Set _check_company_auto = True
Add this as a class attribute on your model. This tells Odoo to automatically validate company consistency during every create() and write() operation.
Add check_company=True to Relational Fields
For every Many2one, Many2many, and One2many field that references a model with a company_id, define it with check_company=True. This activates built-in validation.
class MyModel(models.Model):
_name = 'my.model'
_check_company_auto = True
company_id = fields.Many2one(
'res.company',
default=lambda self: self.env.company,
required=True
)
partner_id = fields.Many2one(
'res.partner',
check_company=True
)
product_id = fields.Many2one(
'product.product',
check_company=True
)
Once both flags are in place, Odoo automatically validates company relationships during every create() or write() operation. Try to link a Company A partner to a Company B invoice and the ORM throws a validation error before the data hits the database.
Default Company Configuration
Setting a default company on the company_id field is not optional — it's a requirement if you want records to create successfully across all contexts: UI, backend scripts, automated actions, wizards, API calls.
The Default Company Pattern
company_id = fields.Many2one(
'res.company',
default=lambda self: self.env.company,
required=True
)
Why Default Company Matters
| Scenario | Without Default | With Default |
|---|---|---|
| UI Record Creation | User must manually select company | Auto-filled with current company |
| Backend Script / Cron | Fails with missing required field error | Creates successfully with env.company |
| Wizard Creation | Validation error if company field is hidden | Works even when field is invisible |
| Non-Multi-Company Users | No way to provide company_id (field hidden) | Automatically assigned, no user action needed |
| API / External Integration | Must explicitly pass company_id in payload | Falls back to calling user's company |
Critical for Hidden Fields
The default company is especially important when the company_id field is hidden in the view (for non-multi-company users). Without it, users have no way to provide the value through the UI, and record creation fails silently or throws a cryptic error.
Security Rules for Multi-Company Access
Security rules are how you prevent User A from Company A seeing Company B's purchase orders, invoices, or employee records. Odoo uses ir.rule records with domain filters based on company_ids — the list of companies the current user is authorized to access.
Writing a Multi-Company Security Rule
<record id="res_partner_multi_company_rule" model="ir.rule">
<field name="name">Partners of user's companies</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="domain_force">
[('company_id', 'in', user.company_ids.ids)]
</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
What This Rule Does
Domain Filter
Only records whose company_id is in the logged-in user's company_ids list will be visible. All other records are hidden.
Group Scope
Applies to all users in base.group_user — which covers all internal users. Admin users with superuser access bypass these rules entirely.
Automatic Enforcement
The rule applies to all CRUD operations — read, write, create, unlink. Users cannot read, edit, or delete records from companies they're not authorized to access.
Key Parameters Quick Reference
| Parameter | Where Used | What It Does |
|---|---|---|
company_dependent=True | Field definition | Stores different field values per company on the same record |
with_company() | Recordset method | Switches company context when reading cross-company field values |
_check_company_auto | Class attribute | Enables automatic company consistency validation on create/write |
check_company=True | Relational field parameter | Validates linked record belongs to same company |
self.env.company | Default lambda | Returns current user's active company for default assignment |
user.company_ids.ids | Security rule domain | List of all companies the user is authorized to access |
Complete Multi-Company Model Example
Here's a complete model definition that implements all four mechanisms together — company-dependent fields, consistency checks, default company, and the matching security rule.
from odoo import models, fields
class MultiCompanyAsset(models.Model):
_name = 'multi.company.asset'
_description = 'Multi-Company Asset'
_check_company_auto = True
name = fields.Char(required=True)
# Company-dependent: different values per company
cost = fields.Float(company_dependent=True)
location = fields.Char(company_dependent=True)
# Default company assignment
company_id = fields.Many2one(
'res.company',
default=lambda self: self.env.company,
required=True
)
# Relational fields with company checks
partner_id = fields.Many2one(
'res.partner',
check_company=True
)
warehouse_id = fields.Many2one(
'stock.warehouse',
check_company=True
)
<record id="multi_company_asset_rule" model="ir.rule">
<field name="name">Assets: multi-company</field>
<field name="model_id" ref="model_multi_company_asset"/>
<field name="domain_force">
[('company_id', 'in', user.company_ids.ids)]
</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
Development Best Practice
Always test your multi-company setup with at least two companies and two users who have different company access. Log in as each user and verify they only see their authorized data. We've seen deployments pass QA testing with a single company and break spectacularly when the second entity goes live.
Need Help Configuring Odoo 18 Multi-Company?
Our team has configured multi-company setups across 50+ Odoo implementations. We handle company-dependent fields, security rules, intercompany transactions, and cross-entity reporting — so your data stays clean and your auditors stay happy.
