How to Extend Hermes WebUI with Custom Extensions and Static Assets
Problem
I wanted to add a custom panel to my self-hosted WebUI, but forking the repo meant maintaining a custom branch across releases. Most projects do not offer a clean escape hatch for local customization without touching core code.
What the Extension System Provides
Hermes WebUI supports a small, opt-in extension surface. You can serve local static assets from a configured directory at /extensions/... and inject same-origin CSS or JavaScript into the app shell without editing the WebUI source tree. Extensions are disabled by default and configured entirely through environment variables.
Trust Warning
Extensions execute with full WebUI session authority. Only enable extensions you wrote yourself or from sources you trust as much as the WebUI source. This is not a plugin marketplace — it is a secure escape hatch for local customization.
Configuration
Enable extensions by setting environment variables before starting the server:
export HERMES_WEBUI_EXTENSION_DIR=/path/to/my-extension/staticexport HERMES_WEBUI_EXTENSION_SCRIPT_URLS=/extensions/app.jsexport HERMES_WEBUI_EXTENSION_STYLESHEET_URLS=/extensions/app.css./start.shURL validation is strict. It rejects schemes, hosts, fragments, quotes, angle brackets, newlines, NUL bytes, and backslashes. Paths must start with /extensions/ or /static/.
Security Sandbox
The static handler is sandboxed:
- Path traversal is rejected
- Dotfiles are not served
- Symlinks outside the extension directory are rejected
Minimal Extension Example
Here is a minimal example that adds a custom panel:
(() => { if (document.getElementById('my-extension-panel')) return;
const panel = document.createElement('section'); panel.id = 'my-extension-panel'; panel.className = 'main-view my-extension-panel'; panel.hidden = true; panel.textContent = 'My extension page';
document.querySelector('main')?.appendChild(panel);
function showPanel() { document.querySelectorAll('main > .main-view').forEach((view) => { view.hidden = view !== panel; }); }})();Authoring guidance recommends additive, reversible DOM changes with unique IDs and class prefixes. Avoid destructive mutations like replacing document.body.innerHTML.
Common Mistakes
I learned about these pitfalls from the documentation:
- Pointing
HERMES_WEBUI_EXTENSION_DIRat a user-writable directory on a shared install — any user with write access can inject code. - Using destructive DOM mutations — additive changes are safer and easier to debug.
- Trying to load third-party scripts — external scripts are deliberately blocked by URL validation.
Summary
In this post, I showed how to extend Hermes WebUI with custom extensions and static assets. The key point is that the extension system is intentionally minimal and safe. It lets you serve files and inject scripts or styles without touching core code, but it demands the same trust level as the WebUI source itself.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments