The manifest
Every field of hanabi.module.json: identity, entrypoints, permissions, filesystem, window.
hanabi.module.json requiredui/index.html requiredworker/main.py optionaltests/sample-inputs/ requiredREADME.md requiredThe manifest is the single contract between a module and the Hanabi platform. Schema version 5.0. The canonical validator is module_manifest.py; the CLI and SDK mirror it so you get the same verdict locally and on upload.
Full example #
{ "schema_version": "5.0", "id": "example-module", "name": "Example Module", "version": "0.1.0", "summary": "Describe what this module does.", "entrypoints": { "ui": "ui/index.html", "worker": "worker/main.py" }, "permissions": ["files.read", "files.write", "jobs.create", "jobs.emit_progress"], "filesystem": { "root": "Modules/Example Module", "folders": [ { "id": "imports", "name": "Imports", "role": "input", "path": "Modules/Example Module/Imports", "create_on_install": true, "allowed_extensions": ["xlsx", "csv"], "description": "Source inputs." }, { "id": "exports", "name": "Exports", "role": "output", "path": "Modules/Example Module/Exports", "create_on_install": true, "description": "Finished outputs." }, { "id": "workspace", "name": "Workspace", "role": "workspace", "path": "Modules/Example Module/Workspace", "create_on_install": true, "description": "Scratch space." } ] }, "dependencies": { "runtimes": [ { "name": "browser", "version": "modern", "required": true }, { "name": "python", "version": ">=3.11", "required": false } ], "python": ["pillow>=10.0"], "node": [], "office": { "word": false, "excel": false, "powerpoint": false }, "services": [] }, "ui": { "window": { "default_width": 960, "default_height": 620, "min_width": 680, "min_height": 460, "resizable": true, "allow_multiple": false }, "popouts": [] }, "desktop": { "file_associations": ["md", "txt"], "jump_list": [{ "id": "new-note", "label": "New note" }] }, "portal": { "tags": ["documents"], "icon_data_url": null, "banner_url": null, "screenshot_urls": [] } }
Field reference #
Identity
| Field | Type | Rules |
|---|---|---|
schema_version | string | Must equal "5.0". |
id | string | Lowercase slug: ^[a-z0-9]+(?:-[a-z0-9]+)*$. Stable; this is the module's identity across versions. |
name | string | Required, non-empty. Display name; also derives the default filesystem root. |
version | string | Required, non-empty. Use semver (MAJOR.MINOR.PATCH). |
summary | string | Optional one-line description. |
entrypoints
| Field | Type | Rules |
|---|---|---|
entrypoints.ui | string | Required. Relative path to the UI HTML inside the package (e.g. ui/index.html). Must stay inside the package (no leading /, no ..). |
entrypoints.worker | string | Optional. Relative path to server-side worker code. Declares that the module has a backend half. A third-party worker runs only when it holds the admin-approved worker.execute permission and a worker-sandbox backend is enabled; otherwise it is not executed (see limitations.md and worker-sandbox-design.md). |
permissions
A list drawn only from this allowlist (anything else fails validation):
| Permission | Grants |
|---|---|
files.read | Read files the user picks from the module's input folders. |
files.write | Write outputs into the module's output folder. |
files.manage | Organize the module's own folders: make subfolders and rename, move, copy, and delete its own files and folders (hanabi.fs.*). Mutations only — reading still needs files.read, creating a file still needs files.write. Deletes go to the Trash. |
jobs.create | Queue a background job via the runtime contract. |
jobs.emit_progress | Emit progress events from a running job. |
modules.use | Baseline "this is an interactive module" capability (no file/job access). |
settings.self | Read/write the module's own durable namespaced settings — per-user and server-backed (they persist across devices and a browser cache clear), with a local write-through cache. |
notifications | Raise desktop notifications (toast + notification centre) and set a count badge on the module's own taskbar icon. Baseline. |
files.read.all | Elevated — requires user consent. Read any of the user's files outside other modules' folders (Desktop, Documents, Downloads, …). For modules that need to open files from the broader workspace. |
files.write.all | Elevated — requires user consent. Broad write outside the module's own folders — also widens hanabi.fs.* management to the whole workspace (never other modules' folders). |
desktop.read | Elevated — requires user consent. Read what's on the user's Desktop (app icons + Desktop files/folders) via hanabi.desktop.list. |
desktop.workspace | Elevated — requires user consent. Place the module's own files/folders onto the user's Desktop and remove ones it placed (hanabi.desktop.place / desktop.remove). The first of the extensible desktop.* namespace. |
desktop.shortcuts | Elevated — requires user consent. Pin a launcher on the Desktop that reopens the module (optionally to a jump-list action / a file as openedFile), and remove its own shortcuts (hanabi.desktop.createShortcut / removeShortcut). |
desktop.personalize | Elevated — requires user consent. Read and set the workspace wallpaper (built-in names only), accent colour (#rrggbb), and light/dark theme (hanabi.desktop.getAppearance / setAppearance). |
network.fetch | Requires admin approval (granted by publishing). Lets the module UI fetch() the origins listed in dependencies.services; all other network is blocked by the served CSP connect-src. Changing the origins re-triggers manual review. |
worker.execute | Requires admin approval (granted by publishing). Lets the platform run the module's entrypoints.worker server code in the worker sandbox (a pure bytes → bytes transformation — no db/VFS/session access). Runs only when a sandbox backend is enabled; changing the worker code re-triggers manual review. See worker-sandbox-design.md. |
storage.read | Read the signed-in user's storage usage/quota (used / limit / remaining bytes) — read-only aggregates. Baseline. |
data.store | A per-module structured data store: collections of JSON documents scoped to (user, module), quota-bound. Baseline. See examples/modules/inventory-tracker. |
jobs.queue | Enqueue a background job (vs the in-request jobs.create), then poll or cancel it. Baseline + in-flight quota. |
jobs.schedule | Admin-approved. Create recurring schedules (interval/cron) that fire a background job even while the UI is closed. Min-interval + max-schedules quota. |
client.webgl · client.gamepad · client.pointerlock · client.fullscreen · client.webcodecs | Consent-gated browser-runtime flags surfaced in the served Permissions-Policy / iframe allow for richer UIs (games, editors). |
client.wasm | Admin-approved. Relaxes the served CSP ('wasm-unsafe-eval') so the module may run WebAssembly. |
worker.native | Admin-HIGH (default-deny). Runs the worker on the native-tools image (ffmpeg / imagemagick / libvips / ghostscript) for transcode/render/compress. |
network.fetch.broad | Admin-HIGH (default-deny). A worker may ctx.fetch any public origin allowed by the admin policy (not just declared dependencies.services), with a per-job byte budget + rate limit. Still host-mediated, no LAN/SSRF, audited. |
worker.service | Admin-HIGH (default-deny). Run the worker as a persistent background service (long-lived container, supervised). Off platform-wide unless the server enables it. |
Permissions sit in four tiers (full table in capability-api.md):
- baseline — implied by install (
files.read/write/manage,jobs.create/queue,settings.self,notifications,storage.read,data.store,modules.use). - consent-gated — the
*.allfile perms and theclient.*flags: declaring isn't enough, the user must consent (a prompt at install or first use); they never reach other modules' folders. - admin-approved — granted by publishing review (
network.fetch,worker.execute,jobs.schedule,client.wasm). - admin-HIGH — default-deny; an admin must grant each one per module and set its quota at review (
worker.native,worker.service,network.fetch.broad, the bigworker.executetiers). Seeexamples/modules/media-toolkit-worker.
Permissions are declared here, granted at install, and enforced at runtime by the capability bridge — see capability-api.md.
filesystem
The module's entire view of the VFS. It can never see anything outside this tree.
| Field | Type | Rules |
|---|---|---|
filesystem.root | string | Required. Must be a safe path under `Modules/` (e.g. Modules/Example Module). |
filesystem.folders[] | array | Each folder is created per user on install (when create_on_install). |
folders[].id | string | Lowercase slug, unique within the module. |
folders[].name | string | Display name. |
folders[].role | enum | One of input, output, workspace, cache, config. |
folders[].path | string | Must be a safe path under `filesystem.root`. |
folders[].create_on_install | bool | Default true. |
folders[].allowed_extensions | string[] | Optional hint of acceptable file types. |
folders[].description | string | Optional. |
Folder role conventions: input = imports the module reads; output = generated files (the runtime writes finished outputs here); workspace = scratch state; cache = disposable; config = reusable settings/templates.
dependencies
How a module declares its requirements. Surfaced at validation and review.
| Field | Type | Notes |
|---|---|---|
dependencies.runtimes[] | array | Each { name, version?, required? }. name ∈ browser, python, node, office. |
dependencies.python[] | string[] | Python package requirements (e.g. "pillow>=10.0"). |
dependencies.node[] | string[] | Node package requirements. |
dependencies.office | object | { word: bool, excel: bool, powerpoint: bool } — declares Microsoft Office desktop needs (Windows host). |
dependencies.services[] | string[] | Named platform/external services the module needs. http(s) URL entries become the module's allowed network origins (the CSP connect-src allowlist) once network.fetch is approved. |
ui.window
| Field | Type | Rules |
|---|---|---|
default_width / default_height | int | Must be ≥ the minimums; default size capped at 2200 × 1400. |
min_width / min_height | int | min_width ≥ 320, min_height ≥ 240. |
resizable | bool | |
allow_multiple | bool | Whether multiple instances can open at once. |
lock_aspect_ratio | bool | Optional. When true, aspect_ratio must be > 0. |
aspect_ratio | number | Optional. Width/height ratio enforced when locked. |
ui.popouts[] | array | Optional secondary windows. |
desktop (optional — desktop integration)
Opt into shell integration. Both keys are optional; invalid entries are dropped during normalization.
| Field | Type | Rules |
|---|---|---|
desktop.file_associations | string[] | File extensions (no leading dot, lowercase alphanumeric), max 24. The module appears in File Explorer's Open with menu for matching files; choosing it launches the module with the file delivered as openedFile in its context — a launch-scoped read grant for that one file (see capability-api.md). |
desktop.jump_list | array | Start-menu quick actions, each { "id": string, "label": string }. Max 6; ids clamped to 60 chars, labels to 40. They render as indented rows under the module in All programs; choosing one launches the module with launchAction set to the action id in its context. No permission required — a jump-list only ever launches the declaring module. |
"desktop": { "file_associations": ["md", "txt"], "jump_list": [ { "id": "new-note", "label": "New note" }, { "id": "search", "label": "Search notes" } ] }
portal (metadata, not validated as contract)
Free-form listing metadata: tags, icon_data_url, banner_url, screenshot_urls. Used by the Module Store / Developer Portal for presentation. The first tag becomes the catalog category for published modules.
Validation behavior #
validate_module_manifest()returns{ status: "valid" | "needs_review", issues: [...] }.- The manifest is normalized first (missing fields filled with safe defaults), then validated, so a partial manifest still produces useful issues rather than crashing.
- Path safety (
.., leading/, backslashes) is checked for every entrypoint and filesystem path.
Planned additions (backward-compatible) #
See `architecture.md` §3.1. Designed but not yet live: runtime.kind, entrypoints.worker_handler, capabilities.requests (folder-scoped grants), platform.min_version / platform.api.