AccountedPro - Developer reference
For engineers extending the app or writing code that must respect its rules.
The golden rule for custom reads
The permission hooks and field/child redaction cover normal Desk / REST / MCP access. But raw reads bypass redaction: frappe.db.sql(…), and frappe.get_all / get_list used only for row filtering — they respect row conditions but not field masking or child-row hiding — and Server Scripts / custom Query Reports that assemble their own payloads.
If your code returns document contents to a person, run the rows through readable_rows so field masking and child redaction are applied identically:
frappe.call(
"advanced_permission.api.readable_rows",
doctype="Sales Invoice",
names=["INV-0001", "INV-0002"],
fields=["name", "customer", "grand_total"], # optional; defaults to all
)
# -> the same rows the person would get through the normal read path, masked per their doors.
For files, gate downloads with the file guard rather than serving bytes directly:
from advanced_permission.permission.file_guard import assert_file_access
assert_file_access(file_url, user) # raises if the person may not have this attachment
Whitelisted API
| Method | Purpose |
|---|---|
simulate(user, doctype, name=None) |
The engine's answer for a user: row condition, visible/total counts, cascade sources, and (with name) opening doors + actions + field access. Manager-gated; powers the Simulator. |
readable_rows(doctype, names, fields=None) |
Rows with the caller's field/child redaction applied — the safe way for custom code to return contents. |
has_restrictions(doctype) |
Whether the DocType has an enabled rule (a cheap client-side check). |
get_rule(target_doctype) / save_rule(payload) |
Load / upsert a whole rule (used by the builder). |
list_rules() |
DocTypes that have a rule, with door counts. |
builder_meta() / child_tables(dt) / link_fields(dt) / doc_fields(dt) |
Metadata for the builder UI. |
How a decision is made
- List
WHERE—query_conditionsunions the applicable doors' scopes, ANDs cascade Gates, and honoursdefault_deny. A door whose Current user is gate excludes the person is skipped, so a per-user door never gates everyone out. Fails closed on error. - Per-document —
has_permissionevaluates the opening doors, cascade Gate/Grant/Inherit, and approved grants. Fails safe (defers) on error. - Scope compilation — each Field-condition chip compiles to SQL with every value escaped; a compiled fragment can contain a literal
%, so per-doc checks escape values inline rather than as bound parameters. Blank sub-conditions are dropped, so an incomplete chip can never emit malformed SQL.
The DocTypes behind it
Useful when granting permissions or moving configuration between sites.
| DocType | Holds |
|---|---|
| Access Rule | One per governed DocType — doors, cascade, guards, request policy |
| Access Door (child) | A single grant — who × which rows × what they can do |
| Access Profile | A reusable bundle of actions + default field access |
| Access Profile Field Rule (child) | A field → access-level entry on a profile |
| Access Cascade Target (child) | Push visibility onto a linked DocType (Gate/Grant/Inherit) |
| Access Link Guard (child) | Block saving a link to a record the person can't access |
| Access Grant Request (+ Approver child) | A request for elevated access, with routing + expiry |
| Access Log | The audit trail |
| Throttle Rule | Per-window read / export caps |
The never-govern list
The app never wires its hooks onto core / meta DocTypes (DocType, DocField, Report, Property Setter, …) or its own config DocTypes. Governing meta DocTypes breaks Frappe's dynamic-link machinery — the symptom is “No permission for DocType” on ordinary forms. If you add a config DocType to the app, add it to that list.
—
Back to Advanced Permission.