Skip to content
AI & Agents
Skill

/frappe-core-permissions

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,

From plugin
frappe-claude-skill-package
17961 skills
Install
$ npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-core-permissions --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/frappe-core-permissions

Context preview

The summary Claude sees to decide when to auto-load this skill.

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,

SKILL.md

frappe-core-permissions.SKILL.md
name: frappe-core-permissions
description: >
  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.
license: MIT
compatibility: "Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16."
metadata:
  author: OpenAEC-Foundation
  version: "2.0"

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

Need to control access?
├── Who can Create/Read/Write/Delete a DocType? → Role Permissions
├── Which specific records can a user see? → User Permissions
├── Which fields should be hidden? → Perm Levels (permlevel 1+)
├── Which fields show masked values? → Data Masking [v16+]
├── Custom runtime deny logic? → has_permission hook
├── Filter list queries dynamically? → permission_query_conditions hook
└── Share one document with one user? → frappe.share

Checking permissions in code?
├── Before action → frappe.has_permission() or doc.has_permission()
├── Raise on denial → doc.check_permission() or throw=True
├── System bypass → doc.flags.ignore_permissions = True (ALWAYS document why)
└── List query → ALWAYS use frappe.get_list() for user-facing data

---

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

# DocType-level
frappe.has_permission("Sales Order", "write")

# Document-level (by name or object)
frappe.has_permission("Sales Order", "write", "SO-00001")
frappe.has_permission("Sales Order", "write", doc=doc)

# For specific user
frappe.has_permission("Sales Order", "read", user="john@example.com")

# Throw on denial
frappe.has_permission("Sales Order", "delete", throw=True)

# Debug mode — prints evaluation steps
frappe.has_permission("Sales Order", "read", debug=True)
print(frappe.local.permission_debug_log)

Document Instance Methods

doc = frappe.get_doc("Sales Order", "SO-00001")

# Returns bool
if doc.has_permission("write"):
    doc.status = "Approved"
    doc.save()

# Raises frappe.PermissionError if denied
doc.check_permission("write")

Get Effective Permissions

from frappe.permissions import get_doc_permissions

perms = get_doc_permissions(doc)
# {'read': 1, 'write': 1, 'create': 0, 'delete': 0, ...}

perms = get_doc_permissions(doc, user="john@example.com")

---

User Permissions (Record-Level)

Restrict users to specific Link field values (e.g., specific Company, Territory).

from frappe.permissions import add_user_permission, remove_user_permission

# Restrict user to one company
add_user_permission(
    doctype="Company",
    name="My Company",
    user="john@example.com",
    is_default=1,            # auto-fill in new documents
    applicable_for="Sales Order"  # only for this DocType (optional)
)

# Remove restriction
remove_user_permission("Company", "My Company", "john@example.com")

# Query current restrictions
from frappe.permissions import get_user_permissions
perms = get_user_permissions("john@example.com")
# {"Company": [{"doc": "My Company", "is_default": 1}], ...}

---

Sharing (Document-Level)

Grant access to a single document for a specific user.

from frappe.share import add as add_share, remove as remove_share

add_share("Sales Order", "SO-00001", "jane@example.com",
          read=1, write=1, share=0, notify=1)

remove_share("Sales Order", "SO-00001", "jane@example.com")

# Share with everyone
add_share("Sales Order", "SO-00001", everyone=1, read=1)

---

Field-Level Permissions (Perm Levels)

Group fields by `permlevel` (0-9). Level 0 MUST be granted before higher levels.

{
  "fields": [
    {"fieldname": "employee_name", "permlevel": 0},
    {"fieldname": "salary",        "permlevel": 1}
  ],
  "permissions": [
    {"role": "Employee",   "permlevel": 0, "read": 1},
    {"role": "HR Manager", "permlevel": 0, "read": 1, "write": 1},
    {"role"
Read more
Ships withfrappe-claude-skill-package

60 deterministic Claude AI skills for Frappe Framework & ERPNext v14-v16 development and operations

Get the whole plugin
Stats
178
Stars
53
Forks
Maintained
Maintenance
Python
Language
2mo ago
Last commit
8mo ago
Created
14d ago
Added

Repo: Impertio-Studio/Frappe_Claude_Skill_Package