Frappe answers "can this user do this to this document?" with four checks, in order. Each one can say no. For a list it asks a different set of questions. Most "permission not working" reports are one person testing one path while the system uses the other.
Set up a case below, and watch the checks for a document and for a list side by side.
Try it
Can this user do it?
Pick a case, or set the inputs. The two columns are the two ways Frappe asks: one document, and a list.
Try a case
The user and the right
Opening the document
AdministratorPassed
Not Administrator, so the checks run.
has_permission hook
No hook set. A hook can only deny.
Role permissionPassed
A role of this user grants it.
User PermissionsPassed
This user has no User Permission for Company.
Sharing
Not needed.
Seeing it in the list
Administrator
Not reached in this case.
Read right on the DocTypePassed
A role grants read.
Owner condition
Not reached in this case.
User Permission conditions
None for this user.
permission_query_conditions
No hook excludes it.
has_permission hook
Not asked per row: lists use the SQL conditions above.
Sharing
Not reached in this case.
Frappe v16.52.0, with v15.123.0 where marked. A case, not a measurement: it runs the order Frappe uses, not your site.
What are the four checks on one document?
The controller hook. A has_permission hook can deny. It cannot grant. On v16 a hook that returns None denies. On v15, None meant no opinion, and the official Hooks page still describes that older behaviour.
The role. The DocType's Permissions table has a row per role: read, write, create, submit, cancel, delete and the rest, at a permission level. For the document itself only level 0 counts. A row marked "If owner" gives the right only to the person who created the document.
User Permissions. These restrict a user to certain records, for example one Company. Frappe checks the document's own DocType first, then every Link field on it and on its child rows. A Link field flagged "Ignore User Permissions" is skipped. An empty link passes unless "Apply Strict User Permissions" is on in System Settings.
Sharing. If the first three said no, a share on that one document can still allow read, write, share, submit, email or print.
Administrator passes everything, so test as the user, never as Administrator.
Why does a list behave differently?
A list is one SQL query, so Frappe cannot ask a hook about each row. It builds conditions instead. The role must grant read. An "If owner" role adds owner = you. Each User Permission adds "this Link field is empty, or in the allowed values", and strict mode drops the "empty" part. permission_query_conditions hooks add their own SQL. Shared documents are added back.
The has_permission hook is not asked per row. So a document can show in the list and refuse to open, or open from a link and be missing from the list. get_all skips all of this.
What do permission levels do?
A field can sit at a permission level above 0, and a role is granted levels separately. A user without write at level 1 can open and save the document, but their changes to level-1 fields are quietly put back to the old values, with no error. A read removes the fields the user may not read. A child row uses its parent's permissions.
Why is my user permission not working?
Five causes, in the order to check them. The user has no User Permission for the linked DocType, so nothing filters. The Link field is flagged "Ignore User Permissions". The field is empty and strict mode is off. The role is "If owner" only. You tested as Administrator.
Where this comes from
Read at Frappe v16.52.0 and ERPNext v16.50.0. “(v15)” marks Frappe v15.123.0 and “(ERPNext v15)” marks ERPNext v15.122.0. Each link opens that file at that tag, on that line.
frappe/permissions.py:108–110 · frappe/permissions.py:229–281 · frappe/permissions.py:209–211 · frappe/permissions.py:494–498 · frappe/permissions.py:466–473 (v15) · frappe/permissions.py:316–340 · frappe/permissions.py:353–480 · frappe/permissions.py:367–372 · frappe/permissions.py:179–211 · model/db_query.py:1041–1106 · model/db_query.py:1114–1171 · model/db_query.py:619–639 · model/db_query.py:630–639 · model/document.py:1023–1046 · model/document.py:959–976 · model/document.py:1064–1071
Official page: Users and Permissions, read 14 October 2026. Cases: strict mode emptying a Purchase Invoice list and reports that drop User Permissions.