AccountedPro - The Access Simulator
The Access Simulator (/app/access-simulator) answers most “why can / can't they…” questions in one click. Enter a user and a DocType (and an optional record), and it shows:
- the row condition (the compiled
WHEREfor that user); - the visible / total counts;
- the cascade sources feeding this DocType;
- for one record: the opening doors, the granted actions, and the field access.
Toggle Show compiled SQL in the builder to see what a single door compiles to. Between the two, most puzzles are settled without reading a line of code.
Troubleshooting
A person sees fewer records than expected
- A per-user door excludes them. A door scoped
Current user is IN (…)doesn't apply to people outside the list — good — but make sure they aren't relying on a door that no longer opens for them. With Default Deny on and every door's scope false, they see nothing (expected); with it off, they fall through to native / cascade. - A cascade Gate is filtering orphans. Under a Gate, a child with an empty / NULL link is excluded. The “missing” rows usually have no parent — fill the link or add a child-side door.
- First match wins is shadowing. A restrictive door listed above a permissive one wins. Reorder with
⋮⋮, or switch to Best access wins.
“No permission for DocType” on an ordinary form
Almost always a core / meta DocType got governed. Frappe routes its dynamic-link machinery through permissions; if a meta DocType (DocType, DocField, Report, Property Setter, …) is wired, ordinary forms with a Dynamic Link field get denied. Don't add Access Rules for core / config DocTypes — the app ships a comprehensive never-govern list.
A whole list errors with a SQL syntax error
A saved scope compiled to invalid SQL — historically a linked-record filter in the wrong format (a Frappe filter dict instead of field / operator / value rows), which left a dangling AND. The compiler now drops blank sub-conditions; rebuild the filter with the + filter rows and use Show compiled SQL to confirm the door is well-formed before saving.
A masked form stays masked after navigating to an unmasked one
The Desk reuses one form view across documents. The client script resets on every refresh, so this shouldn't linger — if it does, it's a stale cached script; hard-refresh, and confirm the console shows the current enforce.js version.
A record opens masked / blank when it shouldn't (or a 500 with no error log)
- No error log + a 500 is the fingerprint of a SQL error — the failing statement aborts before it can be logged. The engine's fail-safe wrappers log the real traceback and degrade instead of 500ing; reproduce, then read the log.
- Fields wrongly hidden — check the door's profile default field access (a Blur / Hidden default is a hard cap) and whether an approved grant is expected to lift it.
An in / not in list matches nothing
The value is a comma-separated string. If the compiled SQL shows it split into single characters, the field name or format is off — the engine splits on commas, so a, b, c becomes IN ('a','b','c').
A custom report leaks masked fields
Raw frappe.db.sql / get_all bypass field redaction. Route the reads through readable_rows (see Developer reference).
Next: Developer reference.