Frappe Errors Permissions logo

Frappe Errors Permissions

Organization
Impertio-Studio
frappe-errors-permissions

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. Keywords: PermissionError, has_permission, permission_query_conditions,, permission denied, cannot access, user blocked, sharing not working, role not enough. User Permission, perm_level, sharing, guest access, owner permission.

Overview

PublisherImpertio-Studio
RepositoryFrappe_Claude_Skill_Package
Skill namefrappe-errors-permissions
Stars
180
Forks
53
Bundled files
3
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 3 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by Impertio-Studio on GitHub. Read the source before you install it.

Installation

Install the Frappe Errors Permissions AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package.git /tmp/Frappe_Claude_Skill_Package
mkdir -p .claude/skills
cp -r /tmp/Frappe_Claude_Skill_Package/skills/source/errors/frappe-errors-permissions .claude/skills/frappe-errors-permissions
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Frappe Errors Permissions in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Frappe Errors Permissions on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Frappe Errors Permissions is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Permission Error Handling

For permission system overview see frappe-core-permissions. For hook syntax see frappe-syntax-hooks.


Quick Diagnostic: Error Message -> Cause -> Fix

Error MessageCauseFix
frappe.exceptions.PermissionErrorUser lacks role or doc-level accessAdd role in Role Permissions Manager or grant User Permission
"Not permitted" on document openhas_permission hook returns False or role missing readCheck frappe.permissions.get_doc_permissions(doc, user) output
List view shows 0 recordspermission_query_conditions returns overly restrictive SQLDebug 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 readAdd allow_guest=True to @frappe.whitelist()
Field invisible despite role having readperm_level > 0 on field and role lacks that levelAdd role permission row for the specific perm_level
"User Permission restriction" blockingUser Permission on a Link field auto-filters documentsUncheck "Apply User Permissions" on that role row or add matching User Permission
Sharing not granting accessSharing adds access but never overrides role absenceUser MUST have base role permission; sharing only adds doc-level grants
ignore_permissions has no effectFlag set after get_doc already checked permissionsSet flags.ignore_permissions = True BEFORE calling save() or insert()
System Manager cannot accessCustom has_permission hook denies without checking roleALWAYS check for System Manager / Administrator in hook

Decision Tree: Where Is the Error?

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

Permission Hook Errors

has_permission Hook: NEVER Throw

python
# hooks.py
has_permission = {
    "Sales Order": "myapp.permissions.sales_order_has_permission",
}
python
# 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:

  • ALWAYS return None to defer, False to deny. NEVER return True — hooks can only restrict, not grant.
  • ALWAYS wrap the entire function in try/except. An unhandled exception breaks ALL access to that DocType.
  • ALWAYS check for Administrator and System Manager at the top.
  • NEVER call frappe.throw() inside this hook.

permission_query_conditions: NEVER Throw

python
# hooks.py
permission_query_conditions = {
    "Sales Order": "myapp.permissions.sales_order_query",
}
python
# 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:

  • NEVER throw errors — return "1=0" to deny all or a restrictive SQL string.
  • ALWAYS use frappe.db.escape() for every user-supplied value.
  • This hook ONLY affects frappe.get_list() / frappe.db.get_list(). It does NOT affect frappe.get_all() / frappe.db.get_all().

User Permission Errors

Too Restrictive: Records Disappear

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:

python
# Step 1: Check what User Permissions exist
frappe.get_all("User Permission",
    filters={"user": "john@example.com"},
    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"), "john@example.com")

Fix patterns:

  • Remove overly broad User Permissions that filter unintended DocTypes.
  • Use the applicable_for field [v14+] to limit which DocType a User Permission applies to.
  • Uncheck "Apply User Permissions" on the role permission row if blanket filtering is unwanted.

Too Permissive: User Sees Everything

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.


perm_level Errors

Error: Field "cost_center" is invisible despite user having read permission.
Cause: Field has permlevel=1 but role only has permission for permlevel=0.
python
# 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.


Sharing Permission Errors

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.
python
# Share a document (user MUST already have a role with at least read)
frappe.share.add("Sales Order", "SO-001", "john@example.com",
    read=1, write=1, share=1)

# Check if sharing grants access
frappe.share.get_sharing_permissions("Sales Order", "SO-001", "john@example.com")

Rules:

  • ALWAYS ensure the user has at least one role with read permission on the DocType before sharing.
  • Sharing adds document-level grants on top of role permissions.
  • [v15+] frappe.share.add accepts notify=1 to send email notification.

Guest Access Errors

Error: "Not permitted" for unauthenticated users.
Cause: DocType has no Guest read permission, or API missing allow_guest.

Fix for web pages / portal:

python
# Add Guest read permission in DocType Permission table
# Role: Guest, Level: 0, Read: checked

Fix for API endpoints:

python
@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).


Debug Workflow: frappe.permissions

python
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="john@example.com")
# 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="john@example.com", throw=False)

# List all roles for a user
frappe.get_roles("john@example.com")

# Check User Permissions
frappe.get_all("User Permission",
    filters={"user": "john@example.com"},
    fields=["allow", "for_value", "applicable_for", "is_default"])

Critical Rules

ALWAYS

  1. Wrap permission hooks in try/except — unhandled errors break all access
  2. Return None (not True) in has_permission — hooks can only deny
  3. Use frappe.db.escape() in query conditions — prevent SQL injection
  4. Check System Manager / Administrator first in custom hooks
  5. Use frappe.has_permission(throw=True) for endpoint permission checks
  6. Use get_list (not get_all) for user-facing queries — get_all bypasses permissions
  7. Log permission denials for security audit with frappe.log_error()

NEVER

  1. Throw in has_permission or permission_query_conditions — breaks access entirely
  2. Return True in has_permission — has no effect, hooks can only restrict
  3. Use string formatting for SQL — use frappe.db.escape() to prevent injection
  4. Grant Guest write/delete permissions — security risk
  5. Use ignore_permissions without documenting why — creates audit gaps
  6. Assume sharing replaces role permissions — sharing only supplements

Reference Files

FileContents
references/patterns.mdComplete hook patterns, query conditions, API endpoints
references/examples.mdFull working examples with hooks.py configuration
references/anti-patterns.md15 common mistakes with wrong/correct comparisons

See Also

  • frappe-core-permissions — Permission system architecture
  • frappe-errors-api — API error handling (401/403/404)
  • frappe-errors-hooks — Hook error handling patterns
  • frappe-syntax-hooks — Hook registration syntax

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Frappe Errors Permissions AI skill do?

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. Keywords: PermissionErr...

Why use Frappe Errors Permissions on TypingMind?

Because you install it once and use it with any model. Frappe Errors Permissions is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Frappe Errors Permissions in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package/tree/main/skills/source/errors/frappe-errors-permissions. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Frappe Errors Permissions?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Frappe Errors Permissions?

As many as you like. As long as a model supports skills, you can use Frappe Errors Permissions with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Frappe Errors Permissions AI skill free?

Yes. It is published on GitHub by Impertio-Studio under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇