Someone on your team opens Odoo and gets 'Access Denied'. Or they cannot find a menu that a colleague uses every day. It feels like a bug, but it almost never is. It is Odoo's permission system doing exactly what it was told.
The catch is that the screen you use to hand out access hides most of what is really going on. You pick a value from a dropdown, and Odoo quietly rewrites a web of groups, rules and filters underneath. Once you can see that web, access rights stop being guesswork. This guide walks the whole path, from the screen you click to the tables behind it, for standard Odoo, the new privilege model in Odoo 19, the optional User Roles add-on, and API and AI access. Every id and field name below is checked against a live Odoo 19 database, so nothing here is invented.
The short version
- Odoo checks four layers, in order: groups, then access rights, then record rules, then field access. One 'no' stops the action.
- The user screen only ever sets one thing: which groups the user is in. Everything else hangs off those groups.
- The per-app dropdowns are 'privileges', a new model in Odoo 19. A privilege is just the dropdown. The real permissions live on the groups inside it.
- User Roles (reusable roles) is an optional add-on, not standard Odoo.
- An API key or AI login runs as a user, so it obeys the exact same four layers.
The four layers
For every single action, read a record, edit it, delete it, Odoo runs four checks in order. If any check says no, the action stops and the user sees an error.
- Groups (
res.groups): which groups is this user in? This is the only thing you set on the user screen. - Access rights (
ir.model.access): may those groups touch this type of record at all? Read, write, create, delete. - Record rules (
ir.rule): which specific records inside that type? A filter, for example 'only your own orders'. - Field access (the
groupsattribute on a field): which fields on the record are visible or editable?
One rule ties the whole thing together and trips up almost everyone: permissions only add up. You cannot take a right away by giving someone an extra group. If a user can see too much, remove a group, never add a restricting one.
The screen you actually use
Open a user under Settings, Users, and go to the Access Rights tab. You will see a Role choice at the top, then grey headers like Sales and Accounting, each with a dropdown.

Here is the part that clears up most of the confusion. Everything on this screen does one thing: it decides which groups the user belongs to. The Role choice, every dropdown, every checkbox further down, they all write to a single place, the user's list of groups. None of them is a separate setting with its own column in the database.
So this screen is layer 1, and only layer 1. Layers 2, 3 and 4 are never shown here. They come attached to the groups you pick, and they were set up long ago by Odoo and by each app. You only find them if you turn on developer mode and open the technical settings.
What 'Role: User / Administrator' really does
The Role choice is the user's base type. In the database it is the field res.users.role, a plain dropdown with two options, User and Administrator, and it stores nothing of its own (Odoo computes it on the fly). Picking a value just adds the user to one base group: User adds the group Role / User, Administrator adds the group Role / Administrator.
The word Administrator is misleading, so be precise about it. It means 'may open Settings and configure the apps'. It does not mean 'manager of every app'. Whether someone can see all sales orders or every project is decided lower down, in the per-app dropdowns. In our test database, the Administrator base group quietly pulls in a bundle of technical groups and is wired to 127 access-right records for configuration models. None of those touch business data like orders or invoices.
Privileges: the per-app dropdowns (new in Odoo 19)
Each grey section holds one or more dropdowns, like Sales or Project. In Odoo 19, one of those dropdowns is a privilege, stored in a new model, res.groups.privilege.

Read a privilege as a question with a fixed list of answers. Take Sales:

- The name is 'Sales', the label next to the dropdown.
- The category points to the grey header, stored as
ir.module.category. - The groups are the options inside: User: Own Documents Only, User: All Documents, Administrator.
- The placeholder is the empty option, usually 'No'.
So the screen has three levels, not two. The grey header is a category. Under it sit one or more privileges (the dropdowns). Inside each privilege sit the groups (the options). Category contains privileges, a privilege contains groups.
Why is Sales a single dropdown instead of three checkboxes? Because the options are ranked and mutually exclusive. Administrator already includes All Documents, which already includes Own Documents. You are picking a level on a ladder, not ticking independent boxes.
What you pick is a group, not the privilege
This answers a question that trips up a lot of people: if the dropdown is a privilege, why is the privilege not linked to the user?
Because the thing you actually choose is one option, and every option is a group. Only the group is stored on the user. The privilege is just the packaging. If Odoo saved 'this user has the Sales privilege', it would not know whether that meant Own, All or Administrator, and the privilege carries no permissions of its own anyway. So the user links to the specific group (the answer), never to the privilege (the question).
Inside a group: the hub of the system
A group is where everything meets. On its own it grants nothing. It is an anchor that the real rules point at. Open a group in developer mode (Settings, Users and Companies, Groups) and the tabs tell the story.
Access rights: what the group may do
The Access Rights tab lists one row per type of record, with four checkboxes: read, write, create, delete. This is layer 2.
If a group has no row that grants at least read on a model, its members cannot open that model at all. This is the most common cause of 'Access Denied' for a new user: they are simply missing the group that carries the access.
Inherited: implied, implying and disjoint
The Inherited tab is where the ranking and the exclusivity live. It has three lists, and all three point back at other groups.

- Implied Groups are the groups this one hands out for free. Give someone this group and they get all the implied ones too. This is what makes Administrator pull in a bundle, and what makes a dropdown a ladder where higher includes lower.
- Implying Groups are the reverse view: the groups that include this one. It is the same link read the other way. For Own Documents, the implying group is All Documents, because All sits above Own.
- Disjoint Groups are the groups you cannot hold at the same time. When Odoo sees a set of ranked, disjoint groups, it shows them as one dropdown rather than separate checkboxes. That is why Sales is a single selector.
Record rules: which records
The Record Rules tab narrows access from 'this whole model' down to specific rows, using a domain filter. This is layer 3.

For the Own Documents salesperson, the rules read like [('salesman_id', '=', user.id)], which means 'only records where you are the salesperson'. Their manager sits in a group with a wider rule and sees everything.
The same thing, seen from the model
You can read layers 2 and 3 from the other end too, starting at the record type instead of the group. In developer mode, open a model under Settings, Technical, Models, for example Contact (res.partner).
Access rights per model
The Access Rights tab on the model shows the same rows, now grouped by the group they belong to. This view answers 'who can edit a contact?' rather than 'what can this group do?'
Field access: which fields
The Fields tab is layer 4. Every field can be limited to certain groups, so it is hidden or read-only for everyone else.

This is how Odoo keeps a cost price or a salary field out of sight for most people while a manager still sees it. Same record, a few lines blacked out.
Record rules: global versus group
The Record Rules tab on the model shows a difference that matters a lot.

- A global rule has no group attached. It applies to everyone and cannot be loosened by any other rule. The classic example is the multi-company rule,
[('company_id', 'in', company_ids + [False])], which keeps each company's records apart no matter how senior you are. - A group rule is attached to one or more groups and widens what those groups can reach, but only within the global boundary.
In plain terms, global rules draw the outer fence that nobody climbs, and group rules hand out access inside it. In logic terms, global rules stack with AND, group rules combine with OR.
For developers: why the dropdown is not a database column
If you go looking for a sales or project column on res.users, you will not find one. The Role choice and every per-app dropdown are reified fields. They have no column. When the form loads, Odoo works out the current value by checking which groups the user is in, and when you save, it writes the right group into the user's group list.
roleis a hand-written selection field (module base, not stored).- The per-app dropdowns are generated at runtime, named like
sel_groups_<ids>, and a standalone checkbox isin_group_<id>. Neither is registered inir.model.fields, which is why searching for them there comes up empty. - The only thing actually stored is the many-to-many
res.users.groups_id, physically the tableres_groups_users_rel(uid, gid).
So the mental model is short: the widgets on the user screen are a friendly front end over one list of groups. The power of each group lives elsewhere, in ir.model.access, ir.rule, the field groups attribute, and the group's own implied groups.
User Roles: the optional add-on
Standard Odoo makes you set access per person. For three people that is fine. For thirty, with turnover, it drifts, and nobody remembers why someone has what.
The OCA add-on base_user_role fixes that. You define a role once as a bundle of groups, then assign the role to people. Change the role, and everyone with it updates at once. You can switch a role on or off by date, which is handy for temporary access.
Two things to be clear about. First, this is not standard Odoo. The role list only exists if you install the module, and it should not be confused with the simple User / Administrator choice, which is standard. Second, under the hood a role bundles its groups through an implied_ids field, and people are linked to roles through a small join record, res.users.role.line, which can carry a from and to date. It is the same implied-groups idea, reused one level up.
Access rights, API keys and AI
This is where it pays off for anyone connecting Odoo to an API or an AI assistant.
An API key grants no permissions of its own. It only says 'I am this user'. A call made with the key runs as that user and goes through the exact same four layers. Want to know what a key is allowed to do? Look at the groups of the user behind it, exactly as you would for a person.
The key lives in res.users.apikeys. Each key belongs to one user (user_id), has a scope and an expiration_date, and its secret is stored hashed, so you can never read it back after creating it. A governance add-on can attach a limited role to a key so it does less than the full user, something standard Odoo cannot do. The practical takeaway: to control what the assistant may do, adjust the user's groups or role in Odoo, the same way you would onboard a careful new colleague.
When to use what
- A small, stable team: the standard user screen is enough.
- Several people in the same job, a growing team, or audit and compliance needs: use User Roles so roles are reusable and easy to review.
- Giving an AI or API access: pair a scoped role with a limited API key, and keep the underlying user tight.
FAQ
What is the difference between a group and a role in Odoo?
A group is the built-in unit that actually carries permissions. A role in the everyday sense is either the simple User / Administrator base type, or, if you install the OCA add-on, a reusable bundle of groups you can hand to many users.
Access rights versus record rules?
Access rights decide what you can do to a whole type of record (read, write, create, delete). Record rules decide which specific records you can do it to, using a filter.
What is a privilege in Odoo 19?
It is one per-app dropdown on the user form, a named set of ranked options. It organizes the screen, it does not grant anything by itself. The permissions sit on the groups inside it.
Can I remove a permission by adding a group?
No. Permissions only add up. To reduce access, remove a group, do not add one.
Do I need the OCA User Roles module?
Only if managing access per person has become a chore, or you need reusable, auditable roles. Small teams are fine without it.
How do access rights affect an AI or MCP connection?
The connection logs in as a user through an API key and obeys the same four layers. Fix access by adjusting that user's groups or role.
Reference: which field controls what
| What you see | Model | Field | What it controls |
|---|---|---|---|
| Whole Access Rights tab | res.users | groups_id (m2m to res.groups) | which groups the user is in |
| Role: User / Administrator | res.users | role (selection, not stored) | base group User or Administrator |
| Per-app dropdown | res.users | sel_groups_<ids> (runtime) | membership in that app group |
| Extra Rights checkbox | res.users | in_group_<id> (runtime) | one group on or off |
| Grey header and grouping | res.groups | privilege_id to res.groups.privilege (has category_id to ir.module.category) | screen layout (Odoo 19) |
| Ranking in a dropdown | res.groups | implied_ids | which groups come along |
| What a group may do | ir.model.access | group_id, model_id, perm_read/write/create/unlink | CRUD per model |
| Which records | ir.rule | groups, model_id, domain_force, global | row filter |
| Which fields | ir.model.fields | groups | field visible or editable |
| The protected thing | ir.model | model (for example project.task) | target of the rights |
| An API key | res.users.apikeys | user_id, scope, expiration_date | authenticates as a user |