# Yuvomi Modules
Yuvomi loads third-party modules from the repository-level `modules/` directory. Each module lives in its own folder and must include a `module.json` manifest. Modules are separate code: do not edit Yuvomi core files to install one.
## Folder Layout
```text
modules/
example-module/
module.json
index.js
style.css
```
The folder name must match the manifest `id`.
## Manifest
```json
{
"id": "example-module",
"name": "Example Module",
"version": "1.0.0",
"description": "Adds a small page to Yuvomi.",
"entry": "index.js",
"style": "style.css",
"icon": "box",
"accent": "#6366F1",
"menu": {
"show": true,
"label": "Example",
"icon": "box",
"order": 100
}
}
```
Required fields:
- `id`: lowercase letters, numbers and hyphens only. Must match the module folder.
- `entry`: a relative `.js` file exporting a `render(container, context)` function.
Optional fields:
- `style`: a relative `.css` file loaded only for this module page.
- `menu.show`: set to `false` if the module should not appear in the left menu.
- `menu.label`, `menu.icon`, `menu.order`: left-menu label, Lucide icon name, and order.
- `accent`: a `#RRGGBB` color used for menu highlighting.
## Client Entry
```js
import { api } from '/api.js';
import { esc } from '/utils/html.js';
export async function render(container, context) {
const me = await api.get('/auth/me');
container.replaceChildren();
container.insertAdjacentHTML('beforeend', `
Hello, ${esc(me.user.display_name)}
`);
}
```
Modules may import public Yuvomi browser libraries such as `/api.js`, `/i18n.js`, and utilities under `/utils/`. For calls to Yuvomi's built-in REST API, prefer `import { api } from '/api.js'`: it prefixes requests with `/api/v1`, sends the current session credentials, handles CSRF tokens, and uses non-cached fetches for user data.
If a module calls a separate backend service through a reverse proxy, expose that service on a same-origin `/api/...` path whenever the response is dynamic. Yuvomi's service worker deliberately bypasses `/api/` requests, while other same-origin GET requests may be handled by the app-shell caching strategy. A dynamic proxy path such as `/ext/myservice/...` can therefore return stale cached responses unless you also change the service-worker strategy.
Modules must follow the same frontend security rules as core Yuvomi:
- Use `replaceChildren()` and `insertAdjacentHTML()`.
- Escape untrusted values before inserting HTML.
- Do not use external CDNs.
- Do not use `innerHTML`.
- Do not bypass authentication, authorization, CSRF, or CSP.
## Loading And Failure Behavior
Yuvomi scans `modules/` and validates each `module.json`. Invalid modules are shown as errored in Settings and are not loaded. Disabled modules are not served to the browser and do not appear in navigation. If a module page fails while rendering, Yuvomi shows an error for that page without changing core application code.
Admins can enable, disable, and order modules in Settings -> Modules -> Navigation. Copying a new folder into `modules/` makes it appear there automatically.
## Docker / Podman
The default `docker-compose.yml` mounts `${MODULES_DIR:-./modules}` to `/app/modules`. To keep modules outside the Yuvomi checkout, set `MODULES_DIR=/absolute/path/to/yuvomi-modules` in `.env` and restart the compose service. New or changed module folders are scanned at runtime; rebuilding the image is not required.
On Podman (RHEL/Fedora/CentOS Stream) use `podman-compose.yml` instead — it mounts the same `/app/modules` path with the SELinux `:Z` relabel so the rootless container can read your modules.