NEXEVES Mega Menu

Role-Based Permissions in ERPNext: A Complete Technical Breakdown

 · 14 min read

Role-Based Permissions in ERPNext: A Complete Technical Breakdown ERPNext Illustration

Role-based permissions in ERPNext control who can see, create, edit, submit, cancel, or amend any document in the system. When properly configured, they become the backbone of security, compliance, and controlled operations across sales, accounts, HR, inventory, and other modules. This guide takes a deep, technical look at how the permission engine works and how you can customize it with settings, workflows, and code.

1. Introduction to ERPNext Role-Based Permissions

In any modern enterprise system, controlling who can access what information is not just a security requirement—it is an operational necessity. ERPNext, being a powerful and highly customizable open-source ERP, uses an advanced role-based permission model designed to ensure that each user interacts only with the data and functions that are relevant to their responsibilities.

At its core, ERPNext permissions are built on the idea that not every user should have equal access, even if they work within the same module. A sales executive and a sales manager both interact with sales-related documents, yet their authority levels differ considerably. ERPNext uses roles, which are bundles of permissions, to translate organizational hierarchy and job responsibilities into system-level access control.

What makes ERPNext’s permission model especially powerful is its multi-layered structure: doctype-level permissions, field-level permissions, workflows, user permissions, and scripted rules. Combined, these give you granular control over what a user can see and what a user can do.

As companies grow with multiple branches, warehouses, or departments, the permission system ensures that data does not leak across functional boundaries, while still keeping work smooth and efficient for end users.

2. How the Permission Engine Works Internally

Internally, ERPNext uses a layered evaluation engine whenever a user interacts with a document. This engine combines static configuration (from Role Permissions Manager and User Permissions) with dynamic logic (workflows and custom scripts).

Conceptually, ERPNext answers one big question on each action: “Given this user, this document, and this action type, should I allow or deny?”

The engine roughly works through these stages:

  • Checks if the user account is active and not disabled.
  • Collects all roles assigned to the user.
  • Reads permission rules for that doctype and those roles.
  • Applies row-level filters using User Permissions and Permission Query Scripts.
  • Respects Workflow rules (if a workflow is defined for that doctype).
  • Runs any custom has_permission hooks or server scripts.

Only if the request passes through all these layers, the operation is allowed. This ensures that even if some configuration is broad, more restrictive layers (like user permissions or workflows) can still safely block access.

3. Key Permission Levels Explained (with Example & Steps)

ERPNext uses specific permission levels to control exactly what actions a user can perform on a document: Read, Write, Create, Delete, Submit, Cancel, Amend. These directly affect data visibility, editing capability, and document lifecycle.

3.1 Example – Sales Order Permissions

Suppose your company wants:

  • Sales User: Can create and edit Sales Orders, but cannot submit or cancel them.
  • Sales Manager: Can create, edit, submit, cancel, and amend Sales Orders.

In the ERPNext UI:

  • Sales User will see Save, but not Submit or Cancel.
  • Sales Manager will see Submit, Cancel, and Amend buttons.

3.2 Steps to Configure Permission Levels

  1. Go to Settings → Role Permissions Manager.
  2. Select Doctype: “Sales Order”.
  3. For the Sales User role:
    • Enable: Read, Write, Create
    • Disable: Submit, Cancel, Amend
  4. For the Sales Manager role:
    • Enable: Read, Write, Create, Submit, Cancel, Amend
  5. Save, then reload the page.

The result is a clearly controlled approval flow: users prepare documents, managers finalize them.

4. Understanding User → Role Mapping (with Example & Code)

ERPNext never assigns permissions directly to users. Instead, each user gets one or more roles, and each role carries permissions. The final access of a user is the combination of all permissions from all their roles.

4.1 Example – Roles for a Sales Manager

User: aliya@nexeves.com

Roles assigned:

  • Employee
  • Sales User
  • Sales Manager

Aliya will now inherit access from all three roles together. This means she can do everything a Sales User can, plus the additional powers of a Sales Manager.

4.2 Steps – Assign Roles to a User

  1. Go to User List.
  2. Open the desired User (e.g., Aliya).
  3. Scroll to the Roles table.
  4. Add rows for: Employee, Sales User, and Sales Manager.
  5. Save the User.

4.3 Script Example – Assign Role Programmatically

import frappe

def add_role_to_user(user, role):
    if not frappe.db.exists("Has Role", {"parent": user, "role": role}):
        has_role = frappe.get_doc({
            "doctype": "Has Role",
            "parent": user,
            "parenttype": "User",
            "parentfield": "roles",
            "role": role
        })
        has_role.insert()

# Example: Give "Sales Manager" role to Aliya
add_role_to_user("aliya@nexeves.com", "Sales Manager")

This pattern is useful in onboarding scripts, batch user creation, or when you integrate ERPNext with external HR systems.

5. Role → Permission Mapping (with Logic & Override Example)

Once roles are defined, you decide what those roles can do on each doctype. This is called role → permission mapping. It ensures that job responsibilities are reflected in system access.

5.1 Example – Sales Order Role Mapping

  • Sales User: Read, Write, Create Sales Orders.
  • Sales Manager: Read, Write, Create, Submit, Cancel, Amend Sales Orders.

5.2 Steps – Map Roles to Doctype Permissions

  1. Open Role Permissions Manager.
  2. Select Doctype: Sales Order.
  3. For each Role, define what actions are allowed.
  4. Click Update.

5.3 Advanced: Custom Permission Logic with Hooks

Sometimes you need additional logic, like: “Only Sales Managers can submit Sales Orders above 50,000”.

# your_app/permissions/sales_order.py
import frappe

def has_permission(doc, ptype, user):
    # Allow only Sales Managers to submit orders above 50,000
    if ptype == "submit" and doc.grand_total > 50000:
        return "Sales Manager" in frappe.get_roles(user)

    # Return None means "use default permission logic"
    return None
# in hooks.py
has_permission = {
    "Sales Order": "your_app.permissions.sales_order.has_permission"
}

This lets you inject business-specific rules on top of the standard configuration.

6. Doctype-Level Permissions (New Matrix & Use)

Doctype-level permissions tell ERPNext which roles can do what on a specific document type. This is the main layer where you restrict access at the document level.

6.1 Example Matrix – Sales Order

Role Read Write Create Submit Cancel Amend
Sales User
Sales Manager
Accounts User
Accounts Manager

6.2 Steps – Configure Doctype Permissions

  1. Open Role Permissions Manager.
  2. Select the Doctype you want (e.g., Sales Order).
  3. For each role, define what actions are allowed at Permission Level 0.
  4. Save and verify by logging in as a user with that role.

As your implementation grows, this matrix becomes the main reference for auditors and admins to understand who has what kind of access.

7. Field-Level Permissions

Field-level permissions control visibility and editability of individual fields on a form. This is useful when some fields are sensitive (like valuation rates, salary, or internal notes) and should be visible only to specific roles.

Common controls:

  • hidden – Hide the field entirely.
  • read_only – Show but don’t allow edits.
  • depends_on – Show based on a condition.

7.1 Example – Hide Valuation Rate from Non-Purchase Managers

frappe.ui.form.on("Item", {
    refresh(frm) {
        if (!frappe.user.has_role("Purchase Manager")) {
            frm.set_df_property("valuation_rate", "hidden", 1);
        }
    }
});

You can manage many of these via Customize Form, but client scripts give dynamic control based on logic at runtime.

8. User Permissions (Advanced Filters with Example & Steps)

User Permissions restrict which records a user can see, even if their role allows access to the doctype. This is crucial for territory-based, branch-based, or department-based visibility.

8.1 Scenario – Sales User Sees Only South Region Customers

Requirement: Each Sales User should see only customers in their assigned Territory.

Step 1 – Ensure Territory Field Exists

On the Customer doctype, make sure the territory field is present (standard in ERPNext).

Step 2 – Create User Permission

  1. Go to User Permissions.
  2. Click New.
  3. Set:
    • User: sales1@example.com
    • Allow: Territory
    • For Value: South Region
    • Applicable For: Customer
  4. Save.

Now, sales1@example.com will see only customers where territory = "South Region".

8.2 Script to Create User Permission

import frappe

def create_user_permission(user, doctype, value, applicable_for=None):
    doc = frappe.get_doc({
        "doctype": "User Permission",
        "user": user,
        "allow": doctype,
        "for_value": value,
        "apply_to_all_doctypes": 0,
        "applicable_for": applicable_for
    })
    doc.insert(ignore_if_duplicate=True)

# Example: Restrict Sales1 to South Region customers
create_user_permission(
    user="sales1@example.com",
    doctype="Territory",
    value="South Region",
    applicable_for="Customer"
)

9. Permission Rules Priority (Flow & Example)

ERPNext evaluates permissions in a specific order. Understanding this order helps you debug conflicts and unexpected “Not permitted” messages.

9.1 Priority Order

  1. System-level: Is the user enabled and allowed to log in?
  2. Role-based permissions: Do their roles allow the action on this doctype?
  3. User Permissions: Are they allowed to access this specific record (row)?
  4. Workflow rules: Is their role allowed to act in the current workflow state?
  5. Custom permission logic: Hooks or server scripts may allow/deny.

9.2 Example – Role Allows but User Permission Blocks

  • Role: Sales User has Read permission on Customer.
  • User Permission: That user is allowed only Territory = South Region.

Result: Customers in North Region will not appear, even though the role says Read is allowed. The more restrictive User Permission wins.

If something seems allowed in Role Permissions Manager but still fails, always check User Permissions and workflows next.

10. How ERPNext Evaluates a Permission Request (Pseudo-Code)

When a user opens, saves, or submits a document, ERPNext internally runs logic similar to the following:

def can_user_do(user, doctype, docname, ptype):
    # 1. Check if user is enabled
    if not is_user_enabled(user):
        return False

    # 2. Load document
    doc = frappe.get_doc(doctype, docname)

    # 3. Check role-based permissions
    if not role_has_permission(user, doctype, ptype):
        return False

    # 4. Check user permissions (row-level)
    if not is_doc_allowed_by_user_permissions(user, doc):
        return False

    # 5. Check workflow permissions (if workflow exists)
    if is_workflow_applicable(doctype):
        if not is_role_allowed_in_state(user, doc.workflow_state, ptype):
            return False

    # 6. Custom app or server script permission hooks
    if not run_custom_permission_hooks(doc, ptype, user):
        return False

    return True

This multi-step evaluation ensures that your most restrictive rules always remain in control, even if some layers are broad.

11. Creating Custom Roles (Steps & Fixtures)

Custom roles let you model your exact organization structure in ERPNext, like “Branch Accountant”, “Quality Supervisor”, or “Purchase Approver”.

11.1 Steps – Create a Custom Role

  1. Go to Role List → New.
  2. Enter Role Name, e.g., Branch Sales Manager.
  3. Save the role.
  4. Open Role Permissions Manager and assign permissions for key doctypes:
    • Sales Order
    • Customer
    • Quotation
  5. Assign the role to selected users in the User doctype.

11.2 Developer Tip – Export Custom Role as Fixture

# in hooks.py of your custom app
fixtures = [
    {
        "doctype": "Role",
        "filters": [["role_name", "in", ["Branch Sales Manager"]]]
    }
]
# from your app directory
bench export-fixtures

Now the role travels with your app and can be installed on multiple sites consistently.

12. Setting Doctype Permissions (Step-by-Step)

Doctype permissions are managed primarily through the Role Permissions Manager. This is where you define which role can perform which actions on a doctype.

12.1 Steps – Example for Sales Invoice

  1. Navigate to Settings → Role Permissions Manager.
  2. Select Doctype: Sales Invoice.
  3. For each relevant role (e.g., Sales User, Accounts Manager), configure:
    • Read / Write / Create / Delete
    • Submit / Cancel / Amend
  4. Click Update after changing each row.
  5. Test by logging in as a user with that role to confirm behavior.

Treat this screen as your core “permission matrix” for each key document like Sales Invoice, Purchase Order, Journal Entry, etc.

13. Using User Permissions for Data Restriction

Beyond controlling which doctypes a user can access, you often need to restrict which records they can see. For example, a branch user should see only their branch’s customers, warehouses, or employees.

13.1 Example – Restrict Sales User to One Company

  1. Go to User Permissions → New.
  2. Set:
    • User: sales2@example.com
    • Allow: Company
    • For Value: Company A
    • Applicable For: Sales Invoice, Sales Order
  3. Save.

Now, that user will see only transactions belonging to Company A, even if their role has access to Sales Order and Sales Invoice in general.

14. Permission Manager vs Role Permissions Manager

ERPNext sometimes has both “Permission Manager” (older pattern) and “Role Permissions Manager” (newer, consolidated view). In most recent versions, Role Permissions Manager is the primary interface.

Feature Role Permissions Manager User Permissions
Scope Doctype-wide, per role Record-level, per user
Best For Defining core access matrix Fine-grained data visibility
Who Uses It System Managers / Admins Admins managing data segregation

Think of Role Permissions Manager as the “big picture” and User Permissions as “per-user filters” applied on top.

15. Role-Based Workflow Permissions (Example Workflow)

Workflow permissions control which roles can transition a document from one state to another (e.g., Draft → Approved). This is how you implement formal approval chains.

15.1 Example – Purchase Order Approval Workflow

States:

  • Draft – Created by Purchase User
  • Pending Approval – Waiting for Manager
  • Approved – Finalized by Purchase Manager
  • Rejected – Rejected by Manager

15.2 Workflow State Table

State Allow Edit Allowed Role
Draft Yes Purchase User
Pending Approval No Purchase Manager
Approved No Purchase Manager
Rejected No Purchase Manager

15.3 Steps – Configure Workflow

  1. Go to Workflow List → New.
  2. Set Document Type: Purchase Order.
  3. Define Workflow States as in the table.
  4. Define Transitions, for example:
    • Draft → Pending Approval (Action: “Submit for Approval”, Role: Purchase User)
    • Pending Approval → Approved (Action: “Approve”, Role: Purchase Manager)
    • Pending Approval → Rejected (Action: “Reject”, Role: Purchase Manager)
  5. Save and enable the workflow.

After this, only the correct roles will see the relevant buttons (Approve/Reject) based on the current state.

16. Scripted Permissions Using Server Scripts

Server Scripts in ERPNext allow you to inject Python logic for permission checks without creating a full custom app. This is useful for conditions like “Block cancellation after 7 days” or “Require manager role above a certain amount”.

16.1 Example – Allow Cancel Only for Managers

# Server Script (Script Type: Permission Script)
user_roles = frappe.get_roles(frappe.session.user)

if ptype == "cancel" and "Accounts Manager" not in user_roles:
    has_permission = False

Here, if the user doesn’t have the Accounts Manager role and tries to cancel, the operation will be blocked.

17. Client-Side Permission Control

Client scripts let you control the UI dynamically: hiding buttons, disabling save, or making fields readonly based on roles or document values. Note that this is UX control, not a security boundary, but it improves usability.

17.1 Example – Disable Save for Non-Accounts Managers

frappe.ui.form.on("Journal Entry", {
    refresh(frm) {
        if (!frappe.user.has_role("Accounts Manager")) {
            frm.disable_save();
        }
    }
});

Always combine client-side controls with proper server-side permissions (Role Permissions, workflows, etc.).

18. Using Permission Query Scripts

Permission Query Scripts restrict which records appear in list views and link field searches. They are evaluated at the database query level.

18.1 Example – Show Only Retail Customers

def get_permission_query_conditions(user):
    # Apply only for Sales User
    if "Sales User" in frappe.get_roles(user):
        return "customer_group = 'Retail'"
    return ""

This ensures that when a Sales User opens the Customer list, they see only customers with customer_group = "Retail".

19. Row-Level Security Techniques (Definition & Code)

Definition: Row-level security means restricting access to individual records in a doctype based on the logged-in user, their department, territory, or other fields.

19.1 Example – HR User Sees Only Their Own Employee Record

Goal: HR User should see only the Employee record where user_id = current_user.

def get_permission_query_conditions(user):
    if "HR User" in frappe.get_roles(user):
        return f"user_id = '{user}'"
    return ""

def has_permission(doc, ptype, user):
    if "HR User" in frappe.get_roles(user):
        return doc.user_id == user
    return True

This combination ensures restriction at both list view and document open level.

20. Common Permission Issues & Debugging (Practical)

Permission issues are one of the most common admin complaints: users seeing empty lists, missing buttons, or “Not permitted” errors. Most issues fall into a few patterns.

20.1 Common Problems

  • “I cannot see the document” – Missing Read permission or restrictive User Permission.
  • “I cannot submit” – Submit not allowed for that role or workflow not allowing that role in current state.
  • “The list is empty but data exists” – Overly restrictive Permission Query Script or User Permissions.

20.2 Debugging Steps

  1. Check Role Permissions Manager – Does the role have the required action?
  2. Check User Permissions – Temporarily remove them and test again.
  3. Check Workflow – Is the document in a state that allows this role to act?
  4. Check Permission Query Scripts – Temporarily disable to see if the list populates.
  5. Check Error Log – Look for “Not permitted” messages and their context.

For developers, using ignore_permissions=True in the console while testing can help confirm whether the issue is truly permission-related or data-related.

21. Best Practices for Secure ERPNext Deployments

A clean permission design is easier to maintain, safer to operate, and simpler to explain to auditors. Here are key practices:

  • Least Privilege – Give each role only what it needs, nothing more.
  • Avoid Overusing System Manager – Limit this to 1–2 senior admins.
  • Separate Duties – Different users should create, approve, and post critical documents.
  • Use Workflows for Approvals – Don’t give everyone Submit rights.
  • Use User Permissions for Sensitive Data – Finance, HR, multi-company scenarios.
  • Review Permissions Regularly – At least quarterly, especially after org changes.
  • Document Custom Scripts – Keep comments explaining permission logic.

When you design permissions with these principles in mind, ERPNext becomes a stable, auditable system rather than a collection of ad-hoc access tweaks.

22. Real-World Examples & Use Cases (With Config Hints)

22.1 Territory-Based Sales Access

Requirement: Sales executives should see only leads and customers in their territory.

  • Ensure territory exists on Lead and Customer.
  • Create User Permissions:
    • User: sales_north@example.com
    • Allow: Territory, For Value: North Region
    • Applicable For: Lead, Customer

22.2 Department-Wise HR Access

Requirement: HR users should handle employees only in their department.

  • Field: department on Employee.
  • User Permission:
    • User: hr_sales@example.com
    • Allow: Department, For Value: Sales
    • Applicable For: Employee

22.3 Multi-Company Finance Separation

Requirement: Each company’s finance team should see only its own vouchers.

  • Standard field: company on all accounting doctypes.
  • User Permission:
    • User: acc_comp1@example.com, Allow: Company, Value: Company A
    • Applicable For: Sales Invoice, Purchase Invoice, Journal Entry, etc.

These patterns cover 80% of the real-world segregation needs for ERPNext implementations.

23. Final Thoughts

Role-based permissions in ERPNext are far more than a simple access toggle. They represent a full governance framework that maps your organizational structure, responsibilities, and approval chains into the ERP system.

A good way to design your permission model is:

  1. Define clear roles based on responsibilities.
  2. Map roles to doctypes via Role Permissions Manager.
  3. Use Workflows for approvals instead of giving broad Submit rights.
  4. Add User Permissions and row-level security where data segregation is needed.
  5. Use server scripts and hooks only where configuration is not enough.

When all these layers are designed thoughtfully, ERPNext becomes a secure, predictable, and scalable platform where data access and actions are always controlled, auditable, and aligned with real-world business rules.


No comments yet.

Add a comment
Ctrl+Enter to add comment

NEXEVES Footer