Frappe Permissions Expert
This skill provides comprehensive guidance for understanding and extending Frappe's permission system.
Overview
Frappe's permission system is multi-layered and evaluates permissions in a specific order:
-
Administrator Check: Administrator user bypasses all permission checks
-
Role-Based Permissions: DocType permissions configured through Permission Manager
-
Controller Permissions: Custom has_permission hooks in doctypes
-
User Permissions: Document-level restrictions based on user-specific rules
-
Permission Query Conditions: SQL-based filters for list views and reports
-
Share Permissions: Explicit document sharing between users
Permission Types
Frappe supports the following permission types (ptypes):
rights = ( "select", # View in list (limited fields) "read", # Full read access "write", # Edit existing documents "create", # Create new documents "delete", # Delete documents "submit", # Submit submittable documents "cancel", # Cancel submitted documents "amend", # Amend cancelled documents "print", # Print documents "email", # Email documents "report", # Access reports "import", # Import documents "export", # Export documents "share", # Share documents with others )
Quick Reference
Main Permission Check Function
frappe.has_permission( doctype="DocType Name", ptype="read", # Permission type to check doc=None, # Optional: specific document instance user=None, # Optional: defaults to current user raise_exception=True, # Display error message if False parent_doctype=None, # Required for child doctypes debug=False, # Enable debug logging ignore_share_permissions=False )
Key Concepts
-
Child Tables: Don't have their own permissions; permissions are checked on the parent document
-
Virtual DocTypes: Permission query conditions don't apply; only has_permission hook and role permissions apply
-
Permission Levels: Provide field-level access control within a document (permlevel 0, 1, 2, etc.)
Extension Hooks
Frappe provides seven main hooks for extending permission logic:
- has_permission
- Controller Permission Check
Purpose: Implement custom document-level permission logic
Location: In your doctype's .py file or registered in hooks.py
Signature:
def has_permission(doc, ptype=None, user=None, debug=False): """ Returns: bool or None: - True: Explicitly grant permission - False: Explicitly deny permission - None: No opinion, continue with other checks """
Important Notes:
-
Return None to defer to other permission checks (recommended default)
-
Return False to explicitly deny permission (overrides role permissions)
-
Return True to explicitly grant permission (use with caution)
When to read: See references/has-permission-hook.md when implementing document-level permission logic with 7 detailed examples covering owner access, team hierarchies, status restrictions, and more.
- permission_query_conditions
- List View Filtering and Document Access
Purpose: Return SQL WHERE conditions to filter documents in list views and validate individual document access
Location: In your doctype's .py file or registered in hooks.py
Signature:
def get_permission_query_conditions(user=None, doctype=None): """ Returns: str: SQL WHERE clause without the "WHERE" keyword Returns empty string "" to allow all documents """
Security: Always escape dynamic values with frappe.db.escape()
Important: These conditions are checked both in list views AND when accessing individual documents via has_permission() for read/select operations.
When to read: See references/permission-query-conditions-hook.md when implementing list filtering or document access validation with 7 examples including company filtering, role-based regions, time-based access, and multi-tenant patterns.
- write_permission_query_conditions
- Post-Write Validation
Purpose: Validate that saved/updated documents satisfy custom conditions before committing to database
Location: In your doctype's .py file or registered in hooks.py
Signature:
def get_write_permission_query_conditions(user=None, doctype=None, ptype="write"): """ Return SQL WHERE conditions to validate written documents.
This is automatically called during has_permission() checks
for write/create/submit/cancel/delete operations.
Checked AFTER database write but BEFORE commit.
If validation fails, transaction is rolled back.
"""
When to read: See references/write-permission-query-conditions-hook.md when validating writes before commit with examples for regional restrictions, document age limits, and status-based validation.
- Server Scripts - Permission Query
Purpose: Define permission query conditions using Server Scripts (Python code in the UI)
Location: Created through Frappe UI at /app/server-script
Script Type: "Permission Query"
When to read: See references/server-scripts.md when prototyping permission logic via UI before moving to code, with examples for department filtering and role-based access.
- has_website_permission
- Website/Portal Access
Purpose: Control access to documents on the website/portal (not desk)
Location: In your doctype's .py file or registered in hooks.py
When to read: See references/has-website-permission-hook.md when implementing portal/website access control with examples for customer orders, published content, and contact relationships.
Workflow Permission Hooks
Frappe provides two additional hooks specifically for workflow-based permissions:
- filter_workflow_transitions
- Custom Transition Filtering
Purpose: Filter and customize the list of available workflow transitions based on custom logic
Location: Registered in hooks.py
Use Cases:
-
Hide specific transitions based on document field values
-
Apply time-based or date-based restrictions
-
Implement dynamic transition visibility
- has_workflow_action_permission
- Action-Level Permission
Purpose: Control which users should receive workflow action notifications and have permission to execute specific actions
Location: Registered in hooks.py
Use Cases:
-
Implement approval hierarchies
-
Department or region-based approval routing
-
Amount-based approval limits
When to read: See references/workflow-permission-hooks.md when implementing workflow transition filtering or approval routing with 10+ examples covering hierarchical approvals, time restrictions, and regional routing.
User Permissions
User Permissions restrict access to specific document values for link fields.
Creating User Permissions:
from frappe.permissions import add_user_permission
add_user_permission( doctype="Company", name="Company A", user="user@example.com", applicable_for="Sales Order", # Optional is_default=1, # Optional ignore_permissions=True # Optional )
Use Cases:
-
Restrict sales users to their own territory
-
Limit employees to their branch/department
-
Multi-company access control
When to read: See references/user-permissions.md when implementing document-level restrictions using User Permissions with comprehensive examples for multi-company, territory, and department-based access.
Share Permissions
Documents can be explicitly shared with specific users:
frappe.share.add( doctype="Sales Order", name="SO-0001", user="user@example.com", read=1, write=1, share=0, submit=0, notify=1 )
When to read: See references/share-permissions.md when implementing explicit document sharing between users with examples for collaboration, temporary access, and cross-department workflows.
Permission Levels
Permission levels provide field-level access control:
-
Each field can have a permlevel (0, 1, 2, etc.)
-
Users must have role permission with that permlevel to see/edit the field
-
Permlevel 0 is default and always checked
When to read: See references/permission-levels.md when implementing field-level access control with examples for hiding pricing, cost fields, and internal notes from specific roles.
Best Practices
Security
-
Always Escape User Input: Use frappe.db.escape() when building SQL conditions
-
Fail Secure: Default to denying access when in doubt
-
Validate Hook Returns: Ensure hooks return expected types
-
Test Permission Boundaries: Test with users having minimal permissions
-
Avoid Side Effects: Permission checks should be read-only
Performance
-
Optimize SQL Conditions: Use indexed columns in WHERE clauses
-
Cache User Data: Cache frequently accessed user properties
-
Minimize Hook Complexity: Keep permission logic simple and fast
-
Use Appropriate Hooks:
-
Use permission_query_conditions for list filtering
-
Use has_permission for complex document-specific logic
Maintainability
-
Document Permission Logic: Add docstrings explaining the rules
-
Separate Concerns: Keep permission logic separate from business logic
-
Use Constants: Define permission-related constants
-
Consistent Return Values: Be explicit about what you're returning
When to read: See references/best-practices.md when writing production-ready permission code with comprehensive guidelines for security, performance, and maintainability.
Debugging Permissions
Enable Debug Mode
In Python
result = frappe.has_permission("DocType", "read", doc, debug=True)
Logs will show the permission evaluation flow
Or enable globally
frappe.conf.developer_mode = 1
Check Permission Debug Logs
After permission check with debug=True
logs = frappe.local.permission_debug_log for log in logs: print(log)
When to read: See references/debugging.md when troubleshooting permission issues with comprehensive debugging workflows, common scenarios, and logging techniques.
Common Issues and Solutions
When to read: See references/common-issues.md when facing permission problems with detailed troubleshooting for:
-
User Can't See Documents in List View
-
Can See Document in List but Can't Open
-
Permission Query Hook Not Working
-
Write Operations Fail Silently
-
Virtual DocType Permission Issues
-
Share Permissions Not Working
-
Administrator Not Seeing All Documents
Common Patterns
When to read: See references/common-patterns.md for 15 ready-to-use permission patterns including:
-
Owner-Only Access
-
Role-Based Region Filtering
-
Hierarchical Access (Team/Department)
-
Status-Based Restrictions
-
Time-Based Access
-
Multi-Tenant Access
-
Permission Level Filtering
-
Child Table Permissions
-
Conditional Field Visibility
-
Combined Role and Territory Access
Testing Permission Hooks
When to read: See references/testing.md when writing unit and integration tests for permission logic with comprehensive examples and patterns.
Migration Guide
When to read: See references/migration-guide.md for a step-by-step guide when adding custom permissions to existing DocTypes.
Core Implementation Files
Key files in Frappe codebase:
-
/frappe/permissions.py
-
Main permission system
-
/frappe/model/db_query.py
-
Permission query integration
-
/frappe/model/document.py
-
Document lifecycle and permission checks
-
/frappe/core/doctype/user_permission/
-
User permission management
Usage
When working with permissions:
-
Identify the requirement: What access control is needed?
-
Choose appropriate hook: Select based on use case
-
Reference appropriate guide: Use reference files for detailed patterns
-
Follow best practices: Security, performance, maintainability
-
Test thoroughly: With different roles and edge cases
-
Debug when needed: Use debug mode and logging
Important Notes
-
Administrator always bypasses all permission checks
-
Permission query conditions are applied to both list views AND individual document access
-
Virtual doctypes don't support permission query conditions
-
Child tables inherit parent document permissions
-
Always escape user input in SQL conditions to prevent SQL injection
-
Permission checks should be read-only operations