
Managing inventory across multiple companies and warehouses becomes challenging the moment different users need different levels of access.
Consider a typical requirement:
- A warehouse user should access only WH/India.
- Another user should access WH/UAE.
- A regional manager should access both.
- A corporate inventory manager should access all warehouses within their allowed companies.
- Manufacturing users should be able to create and process Manufacturing Orders without Odoo’s internal stock moves being blocked by security rules.
Odoo provides Access Rights and Record Rules to achieve this. With Odoo Studio, we can extend this into a practical Allowed Warehouse access-management framework without immediately developing a custom module.
1. The Business Requirement
The objective is to create a simple rule:
If no warehouse restriction is applied, the user can access all warehouses belonging to their allowed companies. If warehouse restriction is enabled, the user can access only the warehouses assigned to them.
For example:
| User | Allowed Companies | Allowed Warehouses | Result |
|---|---|---|---|
| Warehouse Manager | India | Empty | All India warehouses |
| Regional Manager | India + UAE | Empty | All India + UAE warehouses |
| India User | India + UAE | WH/India | WH/India only |
| Regional User | India + UAE | WH/India + WH/UAE | Both warehouses |
The important principle is:
Company access remains the first security boundary.
A warehouse should never give a user access to a company that is not already included in the user’s Odoo Allowed Companies.
2. Access Rights vs Record Rules
Before configuring anything, it is important to understand the difference.
Access Rights
Access Rights answer: “Can this user access this model?”
For example, on stock.picking:
| Model | Read | Write | Create | Delete |
|---|---|---|---|---|
| stock.picking | ✅ | ✅ | ✅ | ❌ |
Record Rules
Record Rules answer: “Which records of this model can this user access?”
For example: a user can access stock.picking, but only pickings belonging to WH/India.
Therefore, our solution uses:
Access Rights + Record Rules + Allowed Companies + Allowed Warehouses
3. Configure Allowed Companies
First configure Odoo’s standard company access.
Go to Settings → Users & Companies → Users and open the required user.
Under Allowed Companies, select the companies the user is permitted to work with.
For example:
- ☑ edu-manufacturing1
- ☑ edu-trading1
- ☐ edu-services1
Set the appropriate Default Company.
This is important because our warehouse security should work inside the company boundary, not replace it.
4. Add Allowed Warehouses Using Studio
Open a user record and activate Studio, then add a new field.
Field configuration
| Property | Value |
|---|---|
| Field Type | Many2many |
| Related Model | Warehouse (stock.warehouse) |
| Label | Allowed Warehouses |
| Suggested technical name | x_studio_allowed_warehouse_ids |
The user record can then contain:
Allowed Warehouses: [ WH/India ] [ WH/UAE ]
For security reasons, this field should normally be editable only by administrators or authorized managers. A user should not be able to change their own warehouse permissions.
5. Add a Warehouse Restriction Switch
For a clean implementation, add another Studio field to the User model.
| Property | Value |
|---|---|
| Label | Restrict Warehouse Access |
| Type | Boolean |
| Suggested technical name | x_studio_restrict_warehouse_access |
This gives us two clearly defined scenarios.
Scenario A — No restriction
Restrict Warehouse Access = False, Allowed Warehouses = Empty.
Result: the user can access all warehouses belonging to their allowed companies.
Scenario B — Restricted
Restrict Warehouse Access = True, Allowed Warehouses = WH/India.
Result: the user can access only WH/India.
This explicit Boolean is safer than trying to infer the user’s security mode purely from whether a Many2many field happens to be empty.
6. Create a Security Group
Create a dedicated group under Settings → Users & Companies → Groups.
For example: Inventory / Warehouse Restricted User
Assign this group to users who should follow the warehouse restriction rules. This gives us a clean way to distinguish:
- Normal Inventory users
- Warehouse-restricted users
- Inventory Managers / Administrators
7. Warehouse Record Rule
Now create the first Record Rule. Go to Settings → Technical → Security → Record Rules and create:
| Property | Value |
|---|---|
| Name | Warehouse Access by Company/Warehouse |
| Model | Warehouse (stock.warehouse) |
| Group | Inventory / Warehouse Restricted User |
| Permissions | Read ✅ · Write ❌ · Create ❌ · Delete ❌ |
The warehouse is configuration data, so ordinary warehouse users should generally not be allowed to modify or delete warehouses.
Domain
The intended logic is:
[
('company_id', 'in', company_ids)
] + (
[('id', 'in', user.x_studio_allowed_warehouse_ids.ids)]
if user.x_studio_restrict_warehouse_access
else []
)This means: Company is allowed AND (Warehouse restriction is OFF OR Warehouse is in Allowed Warehouses).
8. Operation Type Record Rule
The next important model is stock.picking.type. This represents operation types such as:
- Receipts
- Delivery Orders
- Internal Transfers
- Manufacturing
- Returns
Create:
| Property | Value |
|---|---|
| Name | Operation Type Access by Warehouse |
| Model | Operation Type (stock.picking.type) |
| Group | Inventory / Warehouse Restricted User |
| Permissions | Read ✅ · Write ❌ · Create ❌ · Delete ❌ |
Domain
[
('company_id', 'in', company_ids)
] + (
[('warehouse_id', 'in', user.x_studio_allowed_warehouse_ids.ids)]
if user.x_studio_restrict_warehouse_access
else []
)Now a user restricted to WH/India will see WH/India Receipts, Delivery Orders, Internal Transfers and Manufacturing — but not WH/UAE Receipts, Delivery Orders or Manufacturing.
9. Stock Transfer Record Rule
Now we apply the restriction to actual inventory operations on the stock.picking model.
| Property | Value |
|---|---|
| Name | Transfer Access by Warehouse |
| Model | Transfer (stock.picking) |
| Permissions | Read ✅ · Write ✅ · Create ✅ · Delete ❌ |
Domain
[
('company_id', 'in', company_ids)
] + (
[('picking_type_id.warehouse_id', 'in',
user.x_studio_allowed_warehouse_ids.ids)]
if user.x_studio_restrict_warehouse_access
else []
)This allows users to view, create and modify transfers — but only within their permitted warehouse scope.
10. Manufacturing Order Record Rule
This is particularly important for companies using Odoo Manufacturing. The model is mrp.production.
| Property | Value |
|---|---|
| Name | Manufacturing Order Access by Warehouse |
| Model | Manufacturing Order (mrp.production) |
| Permissions | Read ✅ · Write ✅ · Create ✅ · Delete ❌ |
Domain
[
('company_id', 'in', company_ids)
] + (
[('picking_type_id.warehouse_id', 'in',
user.x_studio_allowed_warehouse_ids.ids)]
if user.x_studio_restrict_warehouse_access
else []
)This ensures that a user restricted to a particular warehouse can create and process Manufacturing Orders for that warehouse.
11. Critical Lesson: Do NOT Restrict stock.move Directly
This is one of the most important lessons from implementing this solution.
It may initially seem logical to create a Stock Move Access by Warehouse rule using warehouse_id in user.x_studio_allowed_warehouse_ids.
However, this can break Odoo’s internal inventory and manufacturing workflows.
For example, when a Manufacturing Order is confirmed, Odoo automatically creates and updates a chain of records:
Manufacturing Order → Stock Move → Stock Move Line → Stock Quant
If a warehouse record rule blocks the underlying stock.move, Odoo can generate errors such as “User doesn’t have ‘create’ access to Stock Move” or “User doesn’t have ‘write’ access to Stock Move” — even though the user legitimately has permission to operate the Manufacturing Order.
Therefore
Do not apply the warehouse restriction directly to stock.move initially.
Keep the standard Odoo security for stock.move, stock.move.line and stock.quant, and control access through the business documents instead.
12. Recommended Access Configuration
The resulting architecture should look like this:
| Model | Read | Write | Create | Delete | Warehouse Rule |
|---|---|---|---|---|---|
| stock.warehouse | ✅ | ❌ | ❌ | ❌ | ✅ |
| stock.picking.type | ✅ | ❌ | ❌ | ❌ | ✅ |
| stock.picking | ✅ | ✅ | ✅ | ❌ | ✅ |
| mrp.production | ✅ | ✅ | ✅ | ❌ | ✅ |
| stock.move | Standard | Standard | Standard | Standard | No |
| stock.move.line | Standard | Standard | Standard | Standard | No |
| stock.quant | Standard | Standard | Standard | Standard | No |
| stock.location | Standard | Standard | Standard | Standard | Careful |
The exact standard ACLs should remain appropriate to the user’s Inventory/MRP role.
13. What About Locations?
Locations require special attention. A warehouse may contain a full hierarchy:
WH/India ├── Input ├── Stock │ ├── Shelf A │ ├── Shelf B │ └── Shelf C └── Output WH/UAE ├── Input ├── Stock └── Output
A user restricted to WH/India should not be able to manipulate WH/UAE locations.
However, do not simply apply a generic company-based location rule. Odoo locations can also include:
- Customer locations
- Vendor locations
- Transit locations
- Production locations
- Virtual locations
- Inventory adjustment locations
- Shared/company-neutral locations
Therefore, location security should be designed after reviewing the actual location hierarchy.
14. Why We Should Not Apply the Same Domain Everywhere
This is another important principle.
It is tempting to use warehouse_id in user.allowed_warehouses on every Inventory model. That is dangerous, because different Odoo models have different relationships.
For example, stock.picking reaches the warehouse through picking_type_id → warehouse_id, while other models may use warehouse_id directly, or derive their company/warehouse context through locations or other records.
Therefore: Each model should have a domain based on its actual Odoo relationship.
15. Testing the Security
Create at least four test users.
| Test User | Companies | Restrict Warehouse | Allowed Warehouses | Expected Result |
|---|---|---|---|---|
| Test User 1 | edu-manufacturing1 | OFF | Empty | All warehouses belonging to edu-manufacturing1 |
| Test User 2 | edu-manufacturing1 | ON | WH/Production | WH/Production only |
| Test User 3 | edu-manufacturing1, edu-trading1 | ON | WH/Production, WH/Trading | Only those two warehouses |
| Test User 4 | edu-manufacturing1, edu-trading1 | OFF | Empty | All warehouses belonging to both allowed companies |
16. Test Manufacturing Carefully
For a Manufacturing user, test the complete flow:
Manufacturing → Manufacturing Order → Select Operation Type → Confirm → Check Availability → Produce → Validate
Then verify that Odoo can internally create and update stock.move, stock.move.line and stock.quant without your custom warehouse rules blocking them.
This is exactly where overly aggressive record rules tend to cause problems.
17. Test Cross-Warehouse Transfers
This scenario deserves special attention.
Suppose a user has only WH/India as an allowed warehouse, and attempts a transfer from WH/India/Stock → WH/UAE/Stock.
You need to decide whether this should be allowed. For strict warehouse security, we recommend:
A user should not be able to initiate or manipulate a transfer involving a warehouse outside their permitted scope.
If cross-warehouse transfers are a legitimate business process, create a separate role for users authorized to perform them.
18. Test Direct Record Access
Do not test only menus. A user may potentially access a record through:
- Search
- Smart Buttons
- Related records
- Favorites
- URLs
- Reports
- Many2one fields
Therefore, while logged in as a WH/India-only user, test access to a WH/UAE Transfer, WH/UAE Operation Type, WH/UAE Manufacturing Order and WH/UAE Warehouse.
The security rule should deny access even if the user attempts to reach the record indirectly.
19. The Final Architecture
The complete design becomes:
USER
├── Allowed Companies
└── Warehouse Restriction
└── Allowed Warehouses
│
▼
BUSINESS DOCUMENTS
├── Warehouse
├── Operation Type
├── Transfer
└── Manufacturing Order
│
▼
INTERNAL ODOO RECORDS
├── Stock Move
├── Move Line
└── QuantThe philosophy is simple:
Restrict what the user operates on, not every internal record Odoo creates behind the scenes.
20. Final Recommendation
For an Odoo 19 multi-company environment, we recommend this security strategy:
| Level | Approach | Applies To |
|---|---|---|
| Company level | Use Odoo’s standard Allowed Companies | All users |
| Warehouse level | Use Studio: Allowed Warehouses + Restrict Warehouse Access | Restricted users |
| Business-document level | Use Record Rules | stock.warehouse, stock.picking.type, stock.picking, mrp.production |
| Internal transaction level | Initially leave under standard Odoo security | stock.move, stock.move.line, stock.quant |
This approach gives you warehouse-level security while avoiding the type of MRP errors that occur when a Record Rule blocks Odoo’s internal stock transactions.
One final implementation caution: test the exact domains in a staging database first. Odoo security rules can interact with existing global and group rules, and an overly restrictive rule can unintentionally remove access or break automated workflows.
Need Help Implementing Multi-Company Access Control in Odoo 19?

Mustufa Rahi is an Odoo Certified Functional Consultant and ERP expert at Techvaria with 15+ years of experience in implementation, automation, and business process optimization, helping organizations scale efficiently.