> Use when debugging or handling permission errors in Frappe/ERPNext. Prevents broken document access from throwing in permission hooks. Covers PermissionError (403), has_permission hook failures, User Permission restricting too much or too little, perm_level blocking field access, System Manager bypass not working, Guest access denied, sharing permissions not applying, permission_query_conditions breaking get_list, owner-based permissions confusion, Apply User Permission checkbox behavior, and the permission debug workflow using frappe.permissions.get_doc_permissions. User Permission, perm_level, sharing, guest access, owner permission.
npx skills add https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-errors-permissions
For permission system overview see frappe-core-permissions. For hook syntax see frappe-syntax-hooks.
| Error Message | Cause | Fix |
|---------------|-------|-----|
| frappe.exceptions.PermissionError | User lacks role or doc-level access | Add role in Role Permissions Manager or grant User Permission |
| "Not permitted" on document open | has_permission hook returns False or role missing read | Check frappe.permissions.get_doc_permissions(doc, user) output |
| List view shows 0 records | permission_query_conditions returns overly restrictive SQL | Debug the SQL condition; check User Permissions for the Link field |
| "Not allowed to access ... for Guest" | Endpoint missing allow_guest=True or DocType lacks Guest read | Add allow_guest=True to @frappe.whitelist() |
| Field invisible despite role having read | perm_level > 0 on field and role lacks that level | Add role permission row for the specific perm_level |
| "User Permission restriction" blocking | User Permission on a Link field auto-filters documents | Uncheck "Apply User Permissions" on that role row or add matching User Permission |
| Sharing not granting access | Sharing adds access but never overrides role absence | User MUST have base role permission; sharing only adds doc-level grants |
| ignore_permissions has no effect | Flag set after get_doc already checked permissions | Set flags.ignore_permissions = True BEFORE calling save() or insert() |
| System Manager cannot access | Custom has_permission hook denies without checking role | ALWAYS check for System Manager / Administrator in hook |
Permission error occurred
├── Document-level (single doc access)?
│ ├── has_permission hook returning False?
│ │ └── Debug: frappe.permissions.get_doc_permissions(doc, user)
│ ├── User Permission restricting Link field?
│ │ └── Check: frappe.get_all("User Permission", filters={"user": user})
│ ├── perm_level blocking field?
│ │ └── Check: role has permission row for that perm_level
│ └── Sharing not applying?
│ └── Check: user has base role + sharing record exists
├── List-level (0 records in list view)?
│ ├── permission_query_conditions returning bad SQL?
│ │ └── Debug: run condition manually in MariaDB console
│ ├── User Permission auto-filtering?
│ │ └── Check "Apply User Permissions" checkbox on role row
│ └── get_all vs get_list confusion?
│ └── ALWAYS use get_list for user-facing queries
├── API endpoint (403 response)?
│ ├── Missing @frappe.whitelist()?
│ │ └── Add decorator to Python method
│ ├── Missing allow_guest=True?
│ │ └── Add allow_guest parameter for public endpoints
│ └── frappe.only_for() blocking?
│ └── Check user has required role
└── System Manager bypass failing?
└── Custom hook does not check for System Manager role
# hooks.py
has_permission = {
"Sales Order": "myapp.permissions.sales_order_has_permission",
}
# WRONG — Breaks ALL document access
def sales_order_has_permission(doc, user, permission_type):
if doc.status == "Locked":
frappe.throw("Locked") # NEVER do this
# CORRECT — Return False to deny, None to defer
def sales_order_has_permission(doc, user, permission_type):
"""
ALWAYS wrap in try/except. NEVER throw. NEVER return True.
Returns: False (deny) or None (defer to standard system).
"""
try:
user = user or frappe.session.user
if user == "Administrator":
return None
# ALWAYS check System Manager early
if "System Manager" in frappe.get_roles(user):
return None
# Deny write on locked docs (but allow read)
if permission_type in ("write", "delete", "cancel"):
if doc.get("status") == "Locked":
return False
return None # Defer to standard permission system
except Exception:
frappe.log_error(frappe.get_traceback(),
f"has_permission error: {getattr(doc, 'name', 'unknown')}")
return None # Safe fallback — defer
Critical rules for has_permission hooks:
None to defer, False to deny. NEVER return True — hooks can only restrict, not grant.try/except. An unhandled exception breaks ALL access to that DocType.Administrator and System Manager at the top.frappe.throw() inside this hook.# hooks.py
permission_query_conditions = {
"Sales Order": "myapp.permissions.sales_order_query",
}
# WRONG — Breaks list view for all users
def sales_order_query(user):
if not user:
frappe.throw("User required") # NEVER do this
return f"owner = '{user}'" # SQL injection!
# CORRECT — Return SQL string or empty string
def sales_order_query(user):
"""
ALWAYS return a string. Empty string = no restriction.
ALWAYS use frappe.db.escape(). ALWAYS wrap in try/except.
"""
try:
user = user or frappe.session.user
if user == "Administrator":
return ""
if "System Manager" in frappe.get_roles(user):
return ""
return f"`tabSales Order`.owner = {frappe.db.escape(user)}"
except Exception:
frappe.log_error(frappe.get_traceback(), "Query conditions error")
# SAFE FALLBACK: most restrictive
return f"`tabSales Order`.owner = {frappe.db.escape(frappe.session.user)}"
Critical rules for permission_query_conditions:
"1=0" to deny all or a restrictive SQL string.frappe.db.escape() for every user-supplied value.frappe.get_list() / frappe.db.get_list(). It does NOT affect frappe.get_all() / frappe.db.get_all().Error: User can't see any Sales Orders despite having Sales User role.
Cause: A User Permission for "Company" exists, and "Apply User Permissions"
is checked on the Sales Order role row. Sales Order has a Company
Link field, so ALL Sales Orders are filtered by that Company value.
Debug steps:
# Step 1: Check what User Permissions exist
frappe.get_all("User Permission",
filters={"user": "[email protected]"},
fields=["allow", "for_value", "applicable_for"])
# Step 2: Check if Apply User Permissions is checked
frappe.get_all("DocPerm",
filters={"parent": "Sales Order", "role": "Sales User"},
fields=["role", "permlevel", "apply_user_permissions"]) # [v14]
# Step 3: Check effective permissions on a specific doc
from frappe.permissions import get_doc_permissions
perms = get_doc_permissions(frappe.get_doc("Sales Order", "SO-001"), "[email protected]")
Fix patterns:
applicable_for field [v14+] to limit which DocType a User Permission applies to.Error: User Permission set for Territory = "North" but user sees all territories.
Cause: "Apply User Permissions" is NOT checked on the role permission row,
or the DocType has no Link field for Territory.
Fix: Ensure the role permission row has "Apply User Permissions" checked AND the DocType has a Link field to the restricted DocType.
Error: Field "cost_center" is invisible despite user having read permission.
Cause: Field has permlevel=1 but role only has permission for permlevel=0.
# Check which perm_levels a role has access to
frappe.get_all("DocPerm",
filters={"parent": "Sales Invoice", "role": "Accounts User"},
fields=["permlevel", "read", "write"])
Fix: Add a new row in the DocType's Permission table for the role at the required permlevel.
Error: Document shared with user but user still gets PermissionError.
Cause: User has NO base role permission on the DocType. Sharing only
supplements — it never replaces role-based permissions.
# Share a document (user MUST already have a role with at least read)
frappe.share.add("Sales Order", "SO-001", "[email protected]",
read=1, write=1, share=1)
# Check if sharing grants access
frappe.share.get_sharing_permissions("Sales Order", "SO-001", "[email protected]")
Rules:
frappe.share.add accepts notify=1 to send email notification.Error: "Not permitted" for unauthenticated users.
Cause: DocType has no Guest read permission, or API missing allow_guest.
Fix for web pages / portal:
# Add Guest read permission in DocType Permission table
# Role: Guest, Level: 0, Read: checked
Fix for API endpoints:
@frappe.whitelist(allow_guest=True)
def public_endpoint():
# ALWAYS validate input — guest endpoints are exposed to the internet
pass
NEVER grant Guest write/create/delete permissions unless the DocType is specifically designed for public submission (e.g., Web Form backend).
import frappe
from frappe.permissions import get_doc_permissions
# Get all effective permissions for a user on a document
doc = frappe.get_doc("Sales Order", "SO-001")
perms = get_doc_permissions(doc, user="[email protected]")
# Returns dict: {"read": 1, "write": 0, "create": 0, ...}
# Check specific permission with full context
frappe.has_permission("Sales Order", ptype="write",
doc="SO-001", user="[email protected]", throw=False)
# List all roles for a user
frappe.get_roles("[email protected]")
# Check User Permissions
frappe.get_all("User Permission",
filters={"user": "[email protected]"},
fields=["allow", "for_value", "applicable_for", "is_default"])
frappe.log_error()frappe.db.escape() to prevent injection| File | Contents |
|------|----------|
| references/patterns.md | Complete hook patterns, query conditions, API endpoints |
| references/examples.md | Full working examples with hooks.py configuration |
| references/anti-patterns.md | 15 common mistakes with wrong/correct comparisons |
frappe-core-permissions — Permission system architecturefrappe-errors-api — API error handling (401/403/404)frappe-errors-hooks — Hook error handling patternsfrappe-syntax-hooks — Hook registration syntaxCreate time-boxed technical spike documents for researching and resolving critical development decisions before implementation.
Automatically convert Confluence specification documents into structured Jira backlogs with Epics and implementation tickets. When an agent needs to: (1) Create Jira tickets from a Confluence page, (2) Generate a backlog from a specification, (3) Break down a spec into implementation tasks, or (4) Convert requirements into Jira issues. Handles reading Confluence pages, analyzing specifications, creating Epics with proper structure, and generating detailed implementation tickets linked to the Epic.
Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions.
Guidelines for clinical decision support (CDS) documents: biomarker-stratified cohort analyses and GRADE-graded treatment reports. Covers structure, executive summaries, evidence grading (1A–2C), stats (HR, CI, survival), and biomarker integration. Use for pharma research docs, clinical guidelines, regulatory submissions.
Generate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings, including patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Supports GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration, and regulatory compliance. Outputs publication-ready LaTeX/PDF format optimized for drug development, clinical research, and evidence synthesis.
Guides through Trail of Bits' 5-step secure development workflow. Runs Slither scans, checks special features (upgradeability/ERC conformance/token integration), generates visual security diagrams, helps document security properties for fuzzing/verification, and reviews manual security areas.
Document architecture decisions with ADR (Architecture Decision Records). Use when making significant technical decisions, choosing between alternatives, or when onboarding needs context on past decisions.
Plan-approval workflow patterns for user control over AI actions in Claude Code Waypoint Plugin. Use when planning complex changes, need user approval before execution, want to prevent mistakes, or need to document proposed changes. Covers plan creation, approval checkpoints, plan deviation tracking, revision management, and learning from approved/rejected plans.
Take impertio-studio/frappe-errors-permissions from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.