impertio-studio/frappe_claude_skill_package/frappe-core-permissions
Claude Code and agent skill by impertio-studio/frappe_claude_skill_package
- community
Checking that the SKILL.md file is still in the repository.
Use when implementing the Frappe/ERPNext permission system. Covers roles, user permissions, perm levels, data masking, and permission hooks for v14/v15/v16. Prevents common access control mistakes and security issues. Keywords: permissions, roles, user permissions, perm levels, data masking,, restrict records, who can see what, department access, row-level, user cannot see document, access denied. access control, security, has_permission.
Install
npx vskill@latest install impertio-studio/frappe_claude_skill_package/frappe-core-permissionsSKILL.md
The same SKILL.md was published earlier as openaec-foundation/frappe_claude_skill_package/frappe-core-permissions.
Frappe Permissions
Deterministic patterns for the five-layer Frappe permission system.
Permission Layers
| Layer | Controls | Configured Via | Version | |
|---|---|---|---|---|
| Role Permissions | What users CAN do | DocType permissions table | All | |
| User Permissions | WHICH records users see | User Permission DocType | All | |
| Perm Levels | WHICH fields users see/edit | Field permlevel property | All | |
| Permission Hooks | Custom deny logic | hooks.py | All | |
| Data Masking | Masked field values | Field mask property | [v16+] |
Decision Tree
Permission Types
| Type | API Check | Applies To | |
|---|---|---|---|
read | frappe.has_permission(dt, "read") | All DocTypes | |
write | frappe.has_permission(dt, "write") | All DocTypes | |
create | frappe.has_permission(dt, "create") | All DocTypes | |
delete | frappe.has_permission(dt, "delete") | All DocTypes | |
submit | frappe.has_permission(dt, "submit") | Submittable only | |
cancel | frappe.has_permission(dt, "cancel") | Submittable only | |
amend | frappe.has_permission(dt, "amend") | Submittable only | |
select | frappe.has_permission(dt, "select") | Link fields [v14+] | |
report | N/A | Report Builder access | |
export | N/A | Excel/CSV export | |
import | N/A | Data Import Tool | |
share | N/A | Share with other users | |
print | N/A | Print/PDF generation | |
email | N/A | Send email | |
mask | Role permission for unmasked view | Data Masking [v16+] |
Automatic Roles
| Role | Assigned To | Notes | |
|---|---|---|---|
Guest | Everyone (including anonymous) | Public pages | |
All | All registered users | Basic authenticated access | |
Administrator | Only the Administrator user | ALWAYS has all permissions | |
Desk User | System Users only | [v15+] |
Essential API
Check Permission
Document Instance Methods
Get Effective Permissions
User Permissions (Record-Level)
Restrict users to specific Link field values (e.g., specific Company, Territory).
Sharing (Document-Level)
Grant access to a single document for a specific user.
Field-Level Permissions (Perm Levels)
Group fields by permlevel (0-9). Level 0 MUST be granted before higher levels.
Rule: Levels do NOT imply hierarchy. Level 2 is not "higher" than level 1. They are independent field groups.
Data Masking [v16+]
Fields with mask=1 show masked values (e.g., ****, +91-811XXXXXXX) to users without mask permission.
Grant mask permission to roles that MUST see unmasked values:
CRITICAL: Data masking does NOT apply to frappe.db.sql() or Query Reports with raw SQL. You MUST mask manually in custom SQL queries.
Permission Hooks
has_permission: Custom Deny Logic
Can only deny access. NEVER returns True to grant. ALWAYS returns None to continue standard checks.
permission_query_conditions: Filter List Queries
Returns SQL WHERE clause fragment. Only affects get_list(), NOT get_all().
ALWAYS use frappe.db.escape() — NEVER use string concatenation with raw user input.
get_list vs get_all
| Method | User Permissions | Query Hook | Use For | |
|---|---|---|---|---|
frappe.get_list() | Applied | Applied | User-facing queries | |
frappe.get_all() | Ignored | Ignored | System/background queries |
ALWAYS use get_list() when returning data to users. get_all() bypasses ALL permission filtering.
Common Patterns
Owner-Only Edit
Role-Restricted Endpoint
Bypass Permissions (Document Why!)
Critical Rules
- ALWAYS use
frappe.has_permission()— NEVER check roles directly for access control - ALWAYS use
frappe.get_list()for user-facing queries — NEVERget_all() - ALWAYS escape SQL in query hooks —
frappe.db.escape(user) - ALWAYS prefix table names in query hooks — `
tabDocType.fieldname` - ALWAYS return
Noneinhas_permissionhooks by default — NEVERTrue - ALWAYS clear cache after permission changes —
frappe.clear_cache() - ALWAYS document
ignore_permissionsusage with a comment - NEVER throw errors in
has_permissionhooks — returnFalseto deny - NEVER grant permlevel 1+ without granting permlevel 0 first
- NEVER assume data masking applies to custom SQL queries [v16+]
Anti-Patterns
| Do NOT | Do Instead | |
|---|---|---|
if "Role" in frappe.get_roles() for access | frappe.has_permission(dt, ptype) | |
frappe.get_all() for user queries | frappe.get_list() | |
return True in has_permission hook | return None | |
f"owner = '{user}'" in SQL | f"owner = {frappe.db.escape(user)}" | |
frappe.throw() in permission hooks | return False | |
frappe.db.set_value() for user-facing updates | doc.save() with permission check | |
| Sensitive data in error messages | Generic frappe.PermissionError |
Version Differences
| Feature | v14 | v15 | v16 | |
|---|---|---|---|---|
select permission | Yes | Yes | Yes | |
Desk User role | No | Yes | Yes | |
Data Masking (mask field) | No | No | Yes | |
mask permission type | No | No | Yes | |
| Custom Permission Types | No | No | Experimental |
Permission Precedence
- Administrator — ALWAYS has all permissions (cannot be restricted)
- Role Permissions — Based on assigned roles
- User Permissions — Restricts to specific document values
- has_permission hook — Can only deny (any
False= denied) - Sharing — Grants access to shared documents
- if_owner — Further restricts to owned documents
Reference Files
| File | Contents | |
|---|---|---|
| permission-types-reference.md | All permission types with options | |
| permission-api-reference.md | Complete API with all signatures | |
| permission-hooks-reference.md | Hook patterns and examples | |
| examples.md | Working implementation examples | |
| anti-patterns.md | Common mistakes and fixes |
Related Skills
frappe-core-database— Database operations that respect permissionsfrappe-core-api— API endpoints with permission checksfrappe-syntax-controllers— Controller permission validationfrappe-syntax-hooks— Hook configuration patterns
Verified against Frappe docs 2026-03-20 | Frappe v14/v15/v16
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/source/core/frappe-core-permissions/SKILL.md
Popularity
- Repository stars
- 107
- Repository forks
- 34
- 7-day trend score
- 23
Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 24 May 2026, 5 months ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/impertio-studio/frappe_claude_skill_package/frappe-core-permissions)