# WebUI Extensions Hermes WebUI supports a small, opt-in extension surface for self-hosted installs. It lets an administrator serve local static assets and inject same-origin CSS or JavaScript into the app shell without editing the WebUI source tree. > **Trust model — read this first.** Extensions execute with full WebUI session > authority. An extension JS file can call any API the logged-in user can call, > including reading conversation history, sending messages, modifying settings, > and triggering tool actions. **Only enable extensions you wrote yourself or > from sources you trust as much as the WebUI source itself.** If your WebUI is > shared with users you do not fully trust, do not enable extensions. > If you set `HERMES_WEBUI_EXTENSION_DIR` yourself, do not point it at a > user-writable directory on a shared host. This is intentionally not a plugin marketplace or dependency system. It is a safe escape hatch for local dashboards, internal tooling, and workflow-specific panels that should not live in core Hermes WebUI. > **The vetted extension library.** The curated, one-click-installable extensions > that appear in the gallery live in a separate public repo: > **[hermes-webui/hermes-webui-extensions](https://github.com/hermes-webui/hermes-webui-extensions)**. > "In the registry == vetted." That repo holds the entries, the authoring > conventions ([`docs/extension-entry.md`](https://github.com/hermes-webui/hermes-webui-extensions/blob/main/docs/extension-entry.md)), > the JSON schema, and the CI safety gates. This document covers the WebUI-side > *infrastructure* (loader, manifest contract, capabilities, install client); > see the library repo to browse existing extensions or contribute a new one. ## What extensions can do Extensions can: - serve files from one configured local directory at `/extensions/...` - inject configured same-origin stylesheets into `
` - inject configured same-origin scripts before `` - read a local JSON manifest that lists bundled scripts/styles to inject - call the normal WebUI APIs available to the browser session - call trusted local loopback sidecars directly from extension JavaScript when the browser Content Security Policy allows that origin Extensions cannot, by themselves: - bypass WebUI authentication - serve files outside the configured extension directory - load third-party scripts/styles through the built-in injection config - register new WebUI backend routes or proxy arbitrary sidecar/backend traffic outside the fixed consented extension sidecar path described below - change Hermes Agent permissions, models, memory, or tools unless they call existing authenticated APIs that already allow those changes ## Configuration ### One-click install (no configuration required) For a single-user self-hosted instance you do not need to configure anything. Open **Settings → Extensions**, pick an extension from the gallery, and click **Install** — it just works. The first install creates a WebUI-managed extension directory under your state dir (`STATE_DIR/extensions`, e.g. `~/.hermes/webui/extensions/`) and installs into it; gallery-installed extensions load automatically on the next app-shell render with no environment variables and no restart of your shell. The managed directory lives alongside your sessions and settings in the WebUI-owned state dir. That is a different trust domain from "a world-writable directory on a shared box": only the WebUI process (and whoever can already write your `~/.hermes` state) can place code there. The trust model below still applies — installed extension code runs with full session authority — so only install extensions from the vetted gallery or sources you trust as much as the WebUI source itself. Some gallery entries need more than WebUI assets. If an extension declares post-install guidance or lifecycle requirements such as a loopback sidecar or a native host, Settings -> Extensions shows a **Next step** note on the card after install. For example, Desktop Companion can install the WebUI bridge from the gallery, but the desktop pet is only visible after the local Desktop Companion app is started. ### Manual / advanced configuration (optional) `HERMES_WEBUI_EXTENSION_DIR` is **optional** and overrides the managed default. Set it when you want extensions to live in a specific directory you control (e.g. a checked-out bundle, or a path mounted into a container). When set it must point to an existing directory before any script or stylesheet URLs are injected; WebUI never auto-creates an admin-specified path: ```bash export HERMES_WEBUI_EXTENSION_DIR=/path/to/my-extension/static export HERMES_WEBUI_EXTENSION_SCRIPT_URLS=/extensions/app.js export HERMES_WEBUI_EXTENSION_STYLESHEET_URLS=/extensions/app.css ./start.sh ``` Multiple URLs may be comma-separated: ```bash export HERMES_WEBUI_EXTENSION_SCRIPT_URLS=/extensions/runtime.js,/extensions/app.js export HERMES_WEBUI_EXTENSION_STYLESHEET_URLS=/extensions/base.css,/extensions/theme.css ``` For bundled or multi-extension installs, you may list assets in a manifest file inside `HERMES_WEBUI_EXTENSION_DIR` instead of maintaining long comma-separated environment variables: ```bash cat > ~/.hermes/webui-extension-bundle/extensions.json <<'JSON' { "extensions": [ { "id": "templates", "scripts": ["templates/templates.js"], "stylesheets": ["templates/templates.css"] }, { "id": "sidebar-tools", "scripts": ["sidebar-tools/sidebar-tools.js"], "stylesheets": ["sidebar-tools/sidebar-tools.css"] } ] } JSON HERMES_WEBUI_EXTENSION_DIR=~/.hermes/webui-extension-bundle \ HERMES_WEBUI_EXTENSION_MANIFEST=extensions.json \ ./start.sh ``` Manifest entries use the same URL safety rules as the environment variables. Bare relative entries such as `templates/templates.js` resolve to `/extensions/templates/templates.js`; absolute same-origin entries such as `/extensions/shared.js` or `/static/theme.css` are also accepted. A manifest may be an object with top-level `scripts` / `stylesheets`, an object with an `extensions` array, or a top-level array of extension objects. Disabled entries may be kept in the manifest with the JSON boolean `"enabled": false`. Explicit `HERMES_WEBUI_EXTENSION_SCRIPT_URLS` and `HERMES_WEBUI_EXTENSION_STYLESHEET_URLS` still work and are appended after manifest assets, with duplicates ignored. When an extension is installed from Settings -> Extensions, WebUI records the installed package and loads that package's `manifest.json` automatically on the next app-shell render. In this gallery-installed mode, a manifest located at `HERMES_WEBUI_EXTENSION_DIR/