Building a Custom ERPNext v15 Dashboard Theme
(Odoo-Style)
This guide walks you through creating a CSS-only custom theme app for ERPNext v15, focusing on restyling
the Desk dashboard to an Odoo-like look. We assume you have ERPNext/Frappe installed and know basic
terminal/Git. The process involves creating a Frappe app that bundles CSS assets and overrides core styles
safely. We’ll explain the app structure, how to configure assets, override styles, and ensure update
compatibility, plus provide example CSS for cards, charts, buttons, and layout.
Prerequisites
• Frappe/ERPNext v15 installed locally. You should have a running frappe-bench with ERPNext
v15.
• Bench CLI installed: If not, run pip install frappe-bench .
• Git & Python: Basic familiarity with cloning Git repos and Python.
• Text editor/IDE: For writing CSS and Python files.
No prior theming experience is needed; we’ll use plain CSS. For reference, the official Frappe docs show how
to create a new app with bench new-app 1 . We’ll build on that for our theme.
1. Project Setup
1. Open your bench folder:
cd ~/frappe-bench
2. Create a new Frappe app for the theme:
bench new-app my_theme
This scaffolds a new app named my_theme in apps/my_theme 1 . Follow the prompts (enter
app title, description, author, etc.).
3. Install the theme app on your site:
bench --site [your-site-name] install-app my_theme
This adds the app to your site and creates its database tables 2 .
4. Verify installation:
1
bench --site [your-site-name] list-apps
You should see my_theme listed under installed apps.
These commands set up the new theme app. It now exists in apps/my_theme and is registered on your
site (added to sites/apps.txt ). No code changes yet, just project scaffolding.
2. Understanding Frappe Theming Architecture
Frappe’s theming works by having each app supply CSS/JS assets that are automatically loaded into the
Desk. In practice:
• App public folder: Each app (including your theme app) has a public directory for static files (CSS,
images, etc.). This is symlinked into sites/assets/[appname] 3 . For example, a file
my_theme/public/css/style.css will be served at /assets/my_theme/css/style.css 3 .
• Hooks for assets: In your app’s hooks.py , you list any CSS/JS to include. Frappe reads these hooks
to inject your files. For example, setting app_include_css = "/assets/my_theme/css/
style.css" tells Frappe to load style.css for the Desk.
• Asset bundling: When you run bench build , Frappe compiles and bundles assets. All apps’ public
CSS/JS (plus your hooks) are processed into bundles under sites/assets . Bundled files (in
assets/[appname]/dist/ ) are what the browser actually uses 4 . Use bench watch during
development to auto-rebuild on changes 5 .
In summary, your theme app behaves like any Frappe app. Its public/css files become Desk CSS,
included via hooks, and served from the /assets folder 3 6 . We’ll leverage this pipeline to override
core styles safely.
3. Creating the Theme App
Treat your theme as a regular Frappe app. After bench new-app my_theme , the directory structure looks
like this:
frappe-bench/
└── apps/
└── my_theme/
├── hooks.py
├── my_theme/ (Python package folder)
│ ├── __init__.py
│ ├── config/
│ ├── public/
│ │ └── css/
│ │ └── style.css # (your custom CSS goes here)
│ └── templates/
│ └── ...
2
├── modules.txt
├── patches.txt
└── ...
This is the standard Frappe app layout 7 . Notably:
• hooks.py : This is where you register assets (CSS/JS) to include.
• public/css/ : Put your CSS files here. They’ll be served as /assets/my_theme/css/
[filename].css 3 .
• Other folders: You can also add images, fonts, or HTML templates if needed, but for a CSS-only
theme we focus on public/css/ .
When you run bench build , files in public/css/ are compiled (if SCSS) or copied to assets/
my_theme/dist/css . Since we use pure CSS, they’ll appear under assets/my_theme/css or dist/
css after building. The Frappe docs confirm that each app’s public folder is symlinked into sites/
assets/[appname] 3 , making static files accessible via the browser.
4. Configuring Assets & Bundling
Now let’s tell Frappe to load your CSS. Edit hooks.py in your app (apps/my_theme/my_theme/hooks.py)
and add:
app_name = "my_theme"
app_title = "My Theme"
app_publisher = "Your Name"
app_description = "Odoo-like dashboard theme"
# ...
# Load custom CSS into Desk (backend UI)
app_include_css = "/assets/my_theme/css/style.css"
This app_include_css hook injects your CSS globally in the Desk 6 . (You can list multiple files or use a
list, but one main CSS file is enough.) For example:
# my_theme/hooks.py
app_include_css = "/assets/my_theme/css/style.css"
# Optionally, include custom JS similarly:
# app_include_js = "/assets/my_theme/js/script.js"
Important: If you use multiple CSS files, all listed files will be loaded in the order given. Also ensure your
theme app is loaded after ERPNext in sites/apps.txt (Frappe processes apps in that list order) 8 .
After editing hooks, run:
3
bench build
to compile and bundle assets 9 . The console will rebuild your CSS and others. You can also use:
bench watch
which enters watch mode and recompiles on file changes 10 . (In v14+, the desk UI will auto-reload when
assets rebuild 11 .) This makes development much faster.
Once built, open your ERPNext Desk in a browser; your style.css will have been included. Frappe serves
it from /assets/my_theme/css/style.css 3 . You can now write CSS rules in style.css to
override or extend the default styling.
5. Writing Your Theme Styles
In my_theme/public/css/style.css , add your custom CSS. Since we’re using plain CSS, you can use
modern features like CSS variables. For example, to change the primary color and buttons:
:root {
/* Define a custom primary color for easier reuse */
--primary-color: #1e88e5;
}
/* Example: override primary button styles */
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: #fff;
border-radius: 4px;
transition: background 0.3s;
}
/* Hover state for buttons */
.btn-primary:hover {
background-color: #1669a3;
}
Use selectors to target the Desk elements you want to style. ERPNext’s Desk UI uses Bootstrap classes (like
.btn ) and custom classes (e.g. number cards use .widget.number-widget-box 12 ). For example, to
style the dashboard “Number Card” widgets, you might write:
4
.widget.number-widget-box {
background-color: #f0f8ff; /* pale blue background */
border: 1px solid var(--border-color);
box-shadow: 2px 2px 6px rgba(0,0,0,0.1);
border-radius: 8px;
padding: 12px;
transition: transform 0.2s;
}
.widget.number-widget-box:hover {
transform: translateY(-4px);
}
Here we use the existing class .widget.number-widget-box (from ERPNext code) to add backgrounds
and shadows 12 . You can do similar for charts or cards by inspecting their CSS classes (e.g. wrap chart
<div> s or use .chart container selectors in ERPNext).
Since we avoid SCSS, any variables or mixins are manual. You can use CSS custom properties ( :root {} )
or just hard-code colors/fonts. The key is specificity: more specific selectors (like my-theme .module-
button ) will override generic ones. Avoid using !important when possible; instead rely on selector
specificity and load order (your CSS loads after core CSS, so it wins if equal specificity).
6. Isolating Dashboard Styles
To avoid conflicts with other parts of ERPNext or future apps, scope your CSS whenever possible. For
example:
• Prefix with a unique parent class: Many themes wrap the body with a class. You could add a class
(e.g. my-theme-active ) on <body> and then prefix rules:
.my-theme-active .btn-primary { ... } . This ensures your styles only apply when that
class is present. (Injecting this class might require overriding a template or using a small JS snippet.)
• Target specific elements: Instead of styling generic tags (like button ), style known classes (like
.page-head h1 , .workspace .label , etc.) that appear on the Desk pages you want to change.
• Limit to Desk: Frappe’s desk and website have separate contexts. By default, app_include_css
affects the desk. If you also have a website, avoid naming classes that might appear there. Using a
unique namespace (e.g. prefix all your classes with erpnext- or similar) can help.
For example, in our CSS we might write:
/* Only affect elements inside the Desk workspace */
.desk .workspace {
background: #ffffff;
}
/* Or, if using a wrapper class: */
.my-theme .workspace {
5
background: #ffffff;
}
Even if you don’t add a wrapper class, ensure your selectors are as specific as needed. One useful tip:
always test your styles on a fresh site with ERPNext updates. Avoid deep selectors that may break if
ERPNext’s HTML structure changes. For number cards we saw .widget.number-widget-box in core,
which was stable enough to use 12 .
7. Integrating with ERPNext
With your CSS written and hooked, integration is automatic. Key steps:
• Rebuild assets after changes: Whenever you edit style.css , rerun bench build or keep
bench watch running. Then refresh the Desk.
• Install and update: On a production or other bench, deploy your app via Git or the marketplace.
Typically:
bench get-app https://github.com/your-org/my_theme.git
bench --site [site] install-app my_theme
bench build
This fetches your app and rebuilds assets.
• App order: As noted, ensure my_theme appears after erpnext in sites/apps.txt . This
ensures your CSS is included after the core CSS 8 .
• Theme Switching (Optional): ERPNext’s user settings allow switching between “Light” and “Dark”
themes, but custom themes aren’t listed by default. If you want a menu option, you’d extend
frappe.ui.ThemeSwitcher as shown in Frappe blogs 13 . (This involves JS and Python overrides,
which is more advanced and beyond our CSS-only scope.) For now, your theme CSS will apply
globally to the Desk once installed.
In summary, once app_include_css is set and your app is installed, ERPNext automatically loads your
CSS with every page load 6 . You don’t need to modify core files or use any hacks.
8. Testing & Debugging
During development:
• Use browser dev tools: Inspect elements in the ERPNext Desk to find class names and see CSS
impact. Modify your style.css and rebuild to try changes.
• bench watch : Run bench watch so that each save to your CSS auto-rebuilds assets 10 . In
modern Frappe, the Desk reloads automatically on rebuild (thanks to live-reload on v14+ 11 ).
• Check multiple views: Test your styles on different Desk pages (workspace, list view, form view) to
ensure nothing breaks. Try both light and dark modes if using ERPNext’s theme toggles.
6
• Cross-browser: Verify in at least Chrome and Firefox. Ensure responsive behavior on different
resolutions (ERPNext uses responsive grid, so your CSS should adapt or use media queries if
needed).
If styles aren’t showing, common issues include: forgetting bench build , wrong hook path (must match
/assets/app/css/filename.css ), or caching (try hard-refresh or clearing browser cache).
9. Best Practices & Update Compatibility
To ensure your theme remains maintainable:
• No core edits: Never modify ERPNext or Frappe source files. Keep all overrides in your theme app.
Future upgrades will overwrite core files, so local edits would be lost.
• Use stable selectors: Base your CSS on higher-level classes or data attributes rather than deeply
nested elements. For example, styling .btn-primary is safer than styling .btn.btn-
primary .icon .
• Namespace your CSS: If not already using a unique parent (like .my-theme ), at least prefix
custom class names (e.g. .dash-card , .mybtn ) to avoid collision with other apps.
• Minimize !important : Using !important can make future adjustments hard. Rely on Frappe’s
load order (your CSS is last) and specificity. Only use !important as a last resort.
• Document your overrides: Keep comments in your CSS (e.g. /* Changed button color for
sales graphs */ ) or a README in your theme repo. This helps when ERPNext updates change
class names.
• Test after upgrades: Whenever you update ERPNext to a new version, verify your theme still looks
correct. Pay special attention to renamed or removed classes. Frappe’s release notes sometimes
mention UI changes.
• Fallbacks for core changes: If a core selector you relied on changes, you may need to update your
CSS. For example, if ERPNext moves from .widget.number-widget-box to a new class, your rule
will break. Keep this in mind and track such overrides.
By following these practices, your theme will be more robust. The Frappe docs even suggest using variables
and partials for maintainability, but in our CSS-only setup you can mimic this with custom properties (as
shown) or reusable classes.
10. Reusable CSS Examples
Below are sample CSS snippets to improve the look of common Desk elements. You can adapt these to your
color scheme and style:
• Cards/Widgets: Add background, borders, and shadows to number cards and panels:
/* Style Number Card widgets */
.widget.number-widget-box {
background-color: #f0f8ff;
border: 1px solid var(--border-color);
box-shadow: 2px 2px 6px rgba(0,0,0,0.1);
7
border-radius: 8px;
padding: 12px;
}
/* Hover effect */
.widget.number-widget-box:hover {
background-color: #e6f2ff;
transform: translateY(-4px);
}
This uses the .widget.number-widget-box selector (ERPNext’s class for the stat cards 12 ) and
enhances it with a colored background and shadow.
• Charts: Target chart containers (you may need to inspect ERPNext’s HTML to find a class, here we
use a hypothetical .dashboard-chart ):
/* Style dashboard charts */
.dashboard-chart {
background: #ffffff;
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 8px;
}
.dashboard-chart canvas {
max-width: 100%;
}
This gives charts a white card look with rounded corners. Replace .dashboard-chart with the
actual selector (e.g., if charts are in .flex-chart or similar).
• Buttons: Change default button styles to match your theme color:
/* Customize primary buttons */
.btn-primary {
background-color: #ff6600;
border-color: #e65c00;
color: #ffffff;
padding: 6px 14px;
border-radius: 4px;
}
.btn-primary:hover {
background-color: #e65c00;
}
This example sets a bright orange theme color for primary actions.
8
• Layout: Adjust spacing or grid on Dashboard pages. For instance, to add gap between dashboard
widgets:
/* Add gap between cards in dashboard grid */
.row.dashboard-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 16px;
}
Here, .row.dashboard-container is a guess; replace with the container class for your
dashboard cards or panels. Using CSS grid adds consistent spacing.
Each example above demonstrates overriding core visuals with simple CSS. You can reuse these patterns:
apply a background, border, and shadow to cards; change .btn-primary colors; and use modern CSS
layouts for grid spacing. By tweaking the colors and sizes, you can achieve a clean, modern (Odoo-like) look.
11. Deployment & Distribution
Once your theme app is ready:
• Version control: Push your my_theme app to a GitHub repository. Include a README.md with
install/build instructions.
• Marketplace (optional): You can publish your theme on the Frappe/ERPNext Marketplace for others
to use.
• Deploy to servers: On production servers, use Git and bench:
cd ~/frappe-bench
git clone https://github.com/your-org/my_theme.git apps/my_theme
bench --site [your-site] install-app my_theme
bench build
or with bench get-app as shown earlier.
• Reset assets cache: After deployment, you may also run bench --site [your-site] clear-
cache and restart Bench to ensure fresh assets.
With these steps, your custom theme will be active in ERPNext.
9
12. Conclusion
You’ve now created a reusable ERPNext v15 theme app that injects custom CSS into the Desk. Key points
covered:
• Frappe app structure: Your theme is a Frappe app with a public/css folder and hooks.py 7
6 .
• Asset pipeline: Use app_include_css to load your styles; run bench build/watch to compile
assets 9 4 .
• CSS overrides: Write targeted CSS (using existing classes) to override core styles. Prefix or scope
rules to avoid unwanted effects.
• Examples: We provided code for styling cards, charts, buttons, and layout to achieve a modern look.
Following this guide, even beginners can create sophisticated themes without SCSS or core hacking. Always
test after ERPNext updates and document your changes for future maintenance. With a well-structured
theme app, you can easily update ERPNext without losing your custom dashboard style.
Resources
• Frappe Documentation – Apps and Asset Bundling (see Creating an App and Asset Bundling
sections) 1 9 .
• Frappe Forum – discussions on custom CSS theming and overrides 8 12 .
• ERPNext Dashboard and Widget docs (for class references).
• ERPNext v15 code on GitHub – to inspect HTML/CSS structure for precise selectors.
By building on these principles and examples, you can iteratively refine your theme to perfectly match your
desired (e.g. Odoo-like) dashboard appearance. Good luck!
1 2 7 Apps
https://docs.frappe.io/framework/v15/user/en/basics/apps
3 4 Static Assets
https://docs.frappe.io/framework/user/en/basics/static-assets
5 9 10 11 Asset Bundling
https://docs.frappe.io/framework/user/en/basics/asset-bundling
6 8 Override some CSS rules in desk - Frappe Framework - Frappe Forum
https://discuss.frappe.io/t/override-some-css-rules-in-desk/7839
12 Number cards - very bland - how can we introduce background color - ERPNext - Frappe Forum
https://discuss.frappe.io/t/number-cards-very-bland-how-can-we-introduce-background-color/132686
13 Customising Frappe ERPNext UI: Part I — Adding a new theme | by Pratheesh Russell | Medium
https://medium.com/@pratheeshrussell/customising-frappe-erpnext-ui-part-i-adding-a-new-theme-74d7103df275
10