Real-time, point-and-click UI theming for Frappe Desk — no CSS overrides, no restarts.
Frappe Theme is an open-source Frappe app that gives System Managers a single, GUI-driven
settings page to fully brand the Frappe Desk interface. Pick colors with a color-picker, toggle
layout options with checkboxes, set a custom logo and app name, and watch every open browser tab
update instantly — no page reload, no bench build, no CSS file to maintain.
Built on top of Frappe's real-time WebSocket infrastructure, changes propagate to all connected sessions the moment you hit Save.
| Category | What you can control |
|---|---|
| Sidebar colors | Background, icon-area background, text & sub-text colors |
| Active item | Gradient start / end colors, box-shadow |
| Sidebar branding | Custom logo image, app title, and app subtitle in the sidebar header |
| Brand palette | Primary & secondary colors + hover states (exported as CSS variables) |
| Text | Primary, inverse, muted colors |
| Title area | Breadcrumb and page-title bar text/icon color (via text_primary) |
| Surfaces & borders | Canvas, surface, muted backgrounds; default border color |
| Typography | Font size preset: Small (16 px) / Medium (20 px) / Large (24 px) |
| Layout | Hide left sidebar, right sidebar, form footer, and desktop search bar independently |
| Search bar | Hide .desktop-search-wrapper and .navbar-search-bar; block Ctrl+K / Ctrl+G shortcuts globally |
| Form toolbar | Inject Print / Assign To / Attachments / Share buttons when the right sidebar is hidden |
| Real-time sync | All open sessions update instantly — toggle values are carried directly in the WebSocket payload, zero extra AJAX round-trip |
You can independently hide the left sidebar, right sidebar, form footer, and desktop search bar from the Sidebar → Layout section of Theme Settings.
Tick Hide Search Bar to:
- Suppress the search wrapper elements (
.desktop-search-wrapperand.navbar-search-bar) via a persistent CSS rule active on every desk page. - Override
DesktopPage.prototype.setup_awesomebarso the Awesomebar is never initialised on the home page — preventing any flash of the bar before CSS takes effect. - Block the Ctrl+K and Ctrl+G keyboard shortcuts globally (capture-phase
windowlistener that fires before Frappe's own handlers on all routes).
All three layers are removed the instant you untick the checkbox and save — no page reload needed.
When the right sidebar is hidden, the standard action buttons (Assign, Attachments, Share, etc.) become inaccessible. Frappe Theme solves this by letting you move any combination of those buttons into the form's top navbar — so users never lose access to them.
Enable Hide Right Sidebar, then tick which buttons should appear in the navbar:
| Button | What it does |
|---|---|
| Opens the print dialog for the current document | |
| Assign To | Opens the Assign To dialog |
| Attachments | Opens the Attachments dialog |
| Share | Opens the Share dialog |
The buttons appear automatically on every form the moment you save — no page reload needed.
The sidebar header (the top block that shows the app logo and name) is fully overridable from Theme Settings → Sidebar → Background section:
| Field | What it overrides | Notes |
|---|---|---|
| Left Sidebar App Logo | The <img> or SVG icon in .header-logo |
Upload a file or paste a URL. Supports both Frappe logo variants: an existing <img> tag (src is swapped) and the SVG icon-container (hidden, custom <img> injected). Cleared value restores the original icon. |
| Left Sidebar App Title | .sidebar-item-label.header-title text |
e.g. your company name |
| Left Sidebar App Subtitle | .sidebar-item-label.header-subtitle text |
e.g. a tagline or environment name |
Changes apply instantly via the WebSocket event; the branding is also re-applied on every SPA route change in case Frappe re-renders the sidebar.
Setting a Primary Text color in the Brand & Surfaces tab now also colorises the entire
.flex.title-area bar — breadcrumb links, disabled breadcrumb items, page/form title text, the
⋯ more button, SVG home icon, and the "Not Saved" indicator pill — so the top bar always matches
your brand palette.
Theme Settings — Sidebar color configuration

Theme Settings — Layout toggles & Navbar button shortcuts
Hide the right sidebar and choose which buttons move to the top navbar.
frappe_theme/
├── hooks.py # App entry-point; registers JS assets & boot hook
├── boot.py # boot_session hook — overrides app_title / app_logo_url
├── frappe_theme/
│ └── doctype/
│ └── theme_settings/
│ ├── theme_settings.json # Single DocType — one row, no list view
│ └── theme_settings.py # on_update → publish_realtime("theme_settings_changed", …)
└── public/
├── images/
│ └── logo.svg # Placeholder logo — replace with your own
└── js/
├── theme_desk.js # Core: CSS injection, layout toggles, sidebar branding,
│ # keyboard blocker, real-time reload (app_include_js)
├── desktop.js # Desktop-page only: DesktopPage prototype patch (page_js)
└── form_sidebar_buttons.js # Form toolbar shortcuts when sidebar is hidden
Data flow:
System Manager saves Theme Settings
│
▼
ThemeSettings.on_update()
│ frappe.publish_realtime("theme_settings_changed", {
│ hide_left_sidebar, hide_right_sidebar,
│ hide_form_footer, hide_search_bar,
│ left_sidebar_app_logo, left_sidebar_app_title,
│ left_sidebar_app_subtitle
│ })
▼
All browser tabs receive "theme_settings_changed" WebSocket event
│
├─▶ theme_desk.js merges payload into local cache → applies toggles & branding instantly
│ then reloads full settings in background for sidebar color CSS
│
├─▶ desktop.js merges payload → hides/shows search wrapper
│
└─▶ form_sidebar_buttons.js re-injects toolbar buttons
| File | Loaded via | Scope |
|---|---|---|
theme_desk.js |
app_include_js |
Every desk page — guaranteed to run on any entry route |
desktop.js |
page_js {"desktop": …} |
Desktop home page only — handles the DesktopPage prototype override which is only meaningful on /app/ |
The keyboard shortcut blocker (Ctrl+K / Ctrl+G) lives in theme_desk.js (not desktop.js)
so it is always registered regardless of which page the user lands on first.
CSS is injected as <style> tags directly into document.head with fixed IDs so they are
idempotent — re-injecting a tag replaces its previous content, never duplicates it.
| Style tag ID | Covers |
|---|---|
frappe-theme-sidebar |
All sidebar + brand + title-area rules |
frappe-theme-icons |
Sidebar SVG icon colors |
frappe-theme-font-size |
Global font-size override |
frappe-theme-left-sidebar |
Toggle: hide left sidebar |
frappe-theme-right-sidebar |
Toggle: hide right sidebar |
frappe-theme-form-footer |
Toggle: hide form footer |
frappe-theme-search-bar |
Toggle: hide search bar |
Default brand colors are applied immediately on page load (before the server round-trip) to prevent a flash of unstyled content.
- Frappe / ERPNext v16
- Python ≥ 3.10
- Node.js ≥ 18 (for
bench build)
cd /path/to/your/bench
bench get-app https://github.com/devlpr-nitish/frappe-theme --branch version-16
bench --site <your-site-name> install-app frappe_theme
bench build --app frappe_themeOpen Frappe Desk → search for "Theme Settings" → the settings form should appear.
All configuration lives in Frappe Desk → Theme Settings (search bar or
/app/theme-settings). Only users with the System Manager role can modify settings; all
other users automatically inherit the active theme.
| Field | Description |
|---|---|
| Left Sidebar BG | Main background of the left navigation panel |
| Left Sidebar App Title | Replaces the app name text in the sidebar header (leave blank to keep Frappe default) |
| Left Sidebar App Subtitle | Replaces the subtitle text in the sidebar header |
| Left Sidebar Main Icon BG | Background behind the app icon at the top of the sidebar |
| Left Sidebar App Logo | Custom logo for the sidebar header — upload or paste URL. Supports both <img> and SVG icon-container variants |
| Field | Description |
|---|---|
| Left Sidebar Text Color | Primary link / label color |
| Left Sidebar Subtext Color | Secondary / hint text |
| Left Sidebar Sub Icons Color | SVG icon fill color |
| Field | Description |
|---|---|
| Gradient Start / End | Active sidebar item gradient (left → right) |
| Field | Description |
|---|---|
| Hide Left Sidebar | Collapse the left panel globally |
| Hide Right Sidebar | Collapse the form sidebar globally |
| Hide Form Footer | Remove the save/discard bar at the bottom of forms |
| Hide Search Bar | Hide the desktop search bar and block Ctrl+K / Ctrl+G shortcuts on all routes |
Show in Navbar (when right sidebar is hidden)
| Field | Description |
|---|---|
| Show Print button in form top-bar | |
| Assign To | Show Assign To button in form top-bar |
| Attachments | Show Attachments button in form top-bar |
| Share | Show Share button in form top-bar |
| Field | Description |
|---|---|
| Primary / Secondary | Brand accent colors (exported as --ft-primary / --ft-secondary CSS vars) |
| Primary Hover / Secondary Hover | Hover-state variants |
| Primary Text | Global text color — also applied to the breadcrumb / title area bar |
| Inverse Text / Muted Text | Inverse and muted text palette |
| Section Header Accent | Color of section-header rule lines inside forms |
| Canvas / Surface / Muted Background | Page and card background layers |
| Default Border | Border color used across the UI |
| Font Size | Global base font size: Small / Medium (default) / Large |
Drop your own SVG (or PNG) at frappe_theme/public/images/logo.svg, then:
bench build --app frappe_themeAlternatively, upload or paste any URL directly in Theme Settings → Left Sidebar App Logo — no rebuild required for URL-based logos.
# 1. Clone into your bench apps directory
cd /path/to/bench
git clone https://github.com/devlpr-nitish/frappe-theme apps/frappe_theme
# 2. Install the app on your dev site
bench --site <dev-site> install-app frappe_theme
bench build --app frappe_theme
# 3. Enable pre-commit hooks
cd apps/frappe_theme
pip install pre-commit
pre-commit install
# 4. Start the dev server
cd /path/to/bench
bench start# Frappe v16 only
bench get-app https://github.com/devlpr-nitish/frappe-theme --branch version-16
bench --site <prod-site> install-app frappe_theme
bench build --app frappe_theme --production
bench restartMulti-tenant note: Theme Settings is a Single DocType — it is stored per-site, so each site in a multi-tenant bench has its own independent theme.
cd /path/to/bench
bench update --pull
bench --site <site-name> migrate
bench build --app frappe_theme
bench restartContributions are welcome! Please read CONTRIBUTING.md before opening a pull request.
Quick start:
git clone https://github.com/devlpr-nitish/frappe-theme
cd frappe-theme
pre-commit installFound a vulnerability? Please do not open a public issue. See SECURITY.md for the responsible disclosure process.
MIT © 2026 Nitish Kumar

