Odoo Development · intermediate · 12 min read
Security: Groups and Record Rules
Odoo access rights: ir.model.access.csv, security groups, record rules, field-level restrictions, and multi-company patterns that enforce server-side.
Last reviewed 2026-07-30 · Estimated effort: 2–6 weeks typical implementation slice
Table of contents
Odoo enforces security in layers: model access (CRUD per group), record rules (row-level domains), field groups on views, and optional Python checks in methods. UI-only hiding is not security.
ir.model.access.csv grants read, write, create, unlink flags per model per group. Record rules further filter rows — global rules combine with AND; group-specific rules add OR branches within the same rule group.
Multi-company deployments rely on company_id fields, allowed_company_ids in context, and rules referencing user.company_ids. Test with non-admin users on staging before production.
Every custom model ships with implicit superuser access only until you add ACL. Record rules are easy to get wrong — overly broad domains leak data; overly tight domains break legitimate workflows.
This article walks through defining groups, wiring CSV access, writing record rules, and validating with real users — not only the admin account.
res.groups define roles (Sales / User: Own Documents Only). implied_ids chain inheritance — manager groups imply user groups. Category in module_data.xml groups related security entries in Settings.
Assign default groups via res.users template data sparingly. Custom groups should name your module prefix to avoid clashing with standard group xml ids.
- ▸module_category_data.xml for Settings grouping
- ▸group_system bypasses most checks — limit admin accounts
- ▸portal and public groups for website controllers — separate ACL
Each line: id, name, model_id:id, group_id:id, perm_read, perm_write, perm_create, perm_unlink. Use model_id ref format module.model_model_name matching ir.model external id.
Ship minimal access first — read-only for observers, write for operators, create/unlink only where business requires. Missing CSV line means only superuser sees the model.
ir.rule records attach domain_force Python expressions evaluated per user. global=True applies to everyone unless bypassed; groups limit rule to members.
Common pattern: ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] for multi-company. Own-documents-only uses ('user_id', '=', user.id) on models with responsible field.
HTTP routes with auth='user' still run as that uid — ACL applies. auth='public' with sudo in controller is a frequent data leak — validate tokens and scope queries.
RPC and XML-RPC/JSON-RPC honor the authenticated user. Cron runs as specific user — set user_id on ir.cron so rules match intended visibility.
Create test users per role on staging. Attempt read/write via UI and RPC. TransactionCase tests can switch user with self.env['model'].with_user(user).
When rules fail mysteriously, inspect combined domain in log or use shell to print env['ir.rule']._compute_domain.
- ✓Add security XML and CSV in the same PR as new models — not a follow-up ticket.
- ✓Mirror company rules on all custom models with company_id.
- ✓Avoid sudo in business methods; push elevation to controlled utility layers.
- ✓Document which group unlocks each menu and sensitive field.
- ✓Review implied_ids when cloning groups from demo modules.
- ✓Use read-only ACL for audit/report roles instead of write with hidden buttons.
- !No ACL file — model works for admin only in UAT, fails for users at go-live.
- !Record rule domain referencing field user cannot read — recursive denial.
- !global=True rule conflicting with group rules — unexpected empty lists.
- !Testing only as admin then blaming Odoo permissions.
- !Portal user with excessive model write access via overly broad CSV.
- !Cron running as superuser when business needs company-scoped processing.
Security is server-enforced or it is optional. Ship ACL and record rules with every model, validate as real users, and treat sudo as audited exception.
Automated jobs and APIs inherit the same rules — configure cron users and integration accounts deliberately.
Authenticate
User session or API key.
Groups
Feature access.
Record rules
Row-level visibility.
Field access
Read/write restrictions.
- Owners named
- UAT scripts signed
- Rollback documented
- Hypercare roster ready
Frequently asked questions
Do record rules apply to sudo()?
sudo bypasses both ACL and record rules unless re-applied with a specific user via with_user. Treat sudo as breaking the security stack.
How are multiple record rules combined?
Global rules AND together. Group-specific rules for the same model OR within their set, then AND with globals. Exact evaluation is implemented in ir.rule — test edge cases.
Can I restrict a single field?
Use groups= on field in form view for UI. For true enforcement, override write/create to reject changes from unauthorized groups or use computed readonly fields.
Where do I define security in the module?
Typically security/security.xml for groups and rules, security/ir.model.access.csv for ACL. Load groups before CSV and rules in __manifest__.py order.
Related articles
Models, Fields, and Relationships
Designing Odoo models: field types, Many2one/One2many/Many2many, inheritance modes, constraints, and schema decisions that survive reporting and upgrades.
View →Views, Menus, and Actions
Building and inheriting Odoo UI: form, list, kanban, search views, xpath, window actions, menus, and client-side patterns for Odoo 16/17.
View →Testing Odoo Modules
Automated testing for Odoo addons: TransactionCase, SavepointCase, test tags, mocking RPC, view loading checks, and CI patterns for Odoo 16/17.
View →Maintaining Large Odoo Projects
Governance for multi-module Odoo estates: ownership, branching, dependency maps, technical debt triage, documentation, and sustaining teams after go-live.
View →Permissions chaos after go-live or upgrade?
We audit ACL, record rules, and integration users — then fix leaks without disabling multi-company isolation.
