Building Role-Based Login Redirection in a Frappe App
In many ERP systems, redirecting every user to the same page after login creates unnecessary friction.
Building Role-Based Login Redirection in a Frappe App

In many ERP systems, redirecting every user to the same page after login creates unnecessary friction.
A cashier may need the POS screen immediately. A warehouse operator may need stock operations. An accounts user may need financial dashboards.
Frappe normally redirects users to /desk, but in production systems, different roles often require completely different landing experiences.
This article explains how to build a Role Profile–based login redirection system in a Frappe app, including:
- configurable role-wise landing pages
- backend redirect APIs
- frontend login interception
- automatic
/desklink patching - dynamic DOM handling
- third-party app override support
The Problem
By default, Frappe sends all users to:
/desk
This works for generic use cases, but becomes inefficient in operational workflows where users repeatedly navigate to the same module after every login.
Examples:
User TypePreferred Landing PageCashier/posWarehouse User/app/stock-entryAccounts User/app/accountsDelivery Operator/app/delivery-trip
Instead of forcing users to navigate manually, we can automate the experience using Role Profiles.
Configuration Structure
Create a settings doctype such as:
App Settings
Inside it, add a child table:
Role Wise Redirection
Child Table Fields
FieldTypePurposerole_profileLink → Role ProfileRole profile to matchlanding_pathDataRedirect URL
Example Configuration
Role ProfileLanding PathCashier/posManager/app/dashboard-viewInventory User/app/stock-entry
This allows administrators to configure routing without changing code.
Overall Architecture
The flow works in two layers:
- Initial login redirection
- Post-login navigation patching
The second layer is important because Frappe still contains many internal links pointing to /desk.
Complete Flow
User submits login form
│
▼
HTTP 200 "Logged In"
│
▼
Verify session
│
▼
Show splash screen
│
▼
Call redirect API
│
▼
Fetch user's Role Profile
│
▼
Lookup configured landing path
│
▼
Return redirect URL
│
▼
window.location.href = path
Backend Redirect API
Create a whitelisted API method.
Example
import frappe
@frappe.whitelist()
def get_redirect_url_for_user():
if frappe.session.user == "Guest":
return {"redirect": "/login"}
role_profile = frappe.db.get_value(
"User Role Profile",
{"parent": frappe.session.user},
"role_profile"
)
if not role_profile:
return {"redirect": "/desk"}
landing_path = frappe.db.get_value(
"Role Wise Redirection",
{
"parent": "App Settings",
"role_profile": role_profile
},
"landing_path"
)
return {
"redirect": landing_path or "/desk"
}
Backend Logic Breakdown
The API follows a simple sequence.
Step 1 — Prevent Guest Access
if frappe.session.user == "Guest":
Unauthenticated users are redirected to:
/login
Step 2 — Fetch User Role Profile
Query:
User Role Profile
using the current logged-in user.
Step 3 — Match Configuration
Search the child table for:
role_profile = user's role profile
Step 4 — Return Redirect URL
If a mapping exists:
return {"redirect": landing_path}
Otherwise:
/desk
becomes the fallback.
Frontend Login Interception
After login succeeds, call the backend API before redirecting the user.
Example Frontend Logic
function redirect_based_on_role_profile(fallback_url) {
frappe.call({
method: "your_app.api.get_redirect_url_for_user",
callback: function(r) {
const redirect =
(r.message && r.message.redirect)
|| fallback_url;
window.location.href = redirect;
},
error: function() {
window.location.href = fallback_url;
}
});
}
Why Frontend Handling Is Needed
Frappe’s default login flow redirects quickly after authentication.
Intercepting the flow allows you to:
- validate session state
- show loaders/splash screens
- fetch role configuration
- support dynamic routing
before final navigation occurs.
The Hidden Problem: /desk Links
Even after successful role-based redirection, users can still accidentally return to /desk.
Why?
Because Frappe contains many built-in references to /desk.
Examples include:
- sidebar items
- breadcrumbs
- dynamically rendered menu links
- route handlers
This creates inconsistent navigation behavior.
Solution: Patch Navigation Globally
Include a global JS file using:
app_include_js
inside hooks.py.
Example:
app_include_js = [
"/assets/your_app/js/role_homes.js"
]
Caching Redirect URLs
Avoid repeated backend calls by caching the redirect path.
Example
var _cached_redirect = null;
function get_cached_redirect(cb) {
if (_cached_redirect) {
cb(_cached_redirect);
return;
}
frappe.call({
method: "your_app.api.get_redirect_url_for_user",
callback: function(r) {
_cached_redirect =
r.message.redirect || "/desk";
cb(_cached_redirect);
}
});
}
Patching /desk Links
Search the DOM for:
<a href="/desk">
and replace them dynamically.
Example
function patch_desk_links() {
get_cached_redirect(function(path) {
document.querySelectorAll('a[href="/desk"]')
.forEach(link => {
link.href = path;
});
});
}
Handling Sidebar Menu Clicks
Some Frappe menu items use:
frappe.set_route()
instead of anchor tags.
Intercept those clicks manually.
Dynamic DOM Rendering Problem
Frappe frequently re-renders parts of the interface dynamically.
That means newly added links may bypass your patches.
MutationObserver Solution
Use MutationObserver to continuously monitor DOM changes.
Example
const observer = new MutationObserver(() => {
patch_desk_links();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
This ensures:
- newly rendered links are patched
- sidebar changes remain consistent
- SPA navigation stays controlled
Supporting Third-Party Overrides
In enterprise apps, another installed app may want full control over landing behavior.
A clean architecture should allow:
- disabling role-wise redirects
- hiding settings UI
- delegating redirect logic externally
This avoids conflicting routing systems.
Benefits of This Architecture
Better User Experience
Users land directly inside their workflow.
Centralized Administration
Admins configure redirects without touching code.
Lower Friction
Operational teams save clicks on every login.
Extensible Design
The same architecture can support:
- branch-wise routing
- company-wise redirects
- shift-based landing pages
- device-aware routing
- permission-based workspaces
Recommended File Structure
your_app/
│
├── api/
│ └── login.py
│
├── public/js/
│ └── role_homes.js
│
├── doctype/
│ └── role_wise_redirection/
│
├── hooks.py
│
└── settings/
Final Thoughts
Role-based login redirection in Frappe is more than a simple redirect.
A production-ready implementation requires coordination between:
- authentication flow
- frontend routing
- backend APIs
- dynamic DOM rendering
- navigation interception
When implemented correctly, it creates a significantly smoother workflow experience for operational users and makes Frappe applications feel purpose-built for each department instead of generic for everyone.
메타데이터
- post_id
- e46caf078b45
- slug
- building-role-based-login-redirection-in-a-frappe-app-e46caf078b45
- url
- https://medium.com/@devlprnitish/building-role-based-login-redirection-in-a-frappe-app-e46caf078b45
- canonical_url
- https://medium.com/@devlprnitish/building-role-based-login-redirection-in-a-frappe-app-e46caf078b45
- author_url
- https://medium.com/@devlprnitish
- status
- ok
- fetched_at
- 2026-06-09 15:37:30