Hanabi Developer Hub
/ Build / The manifest
Build

The manifest

Every field of hanabi.module.json: identity, entrypoints, permissions, filesystem, window.

my-module/
hanabi.module.json required
ui/index.html required
worker/main.py optional
tests/sample-inputs/ required
README.md required
The anatomy of a module package.

The 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 #

json
{
  "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

FieldTypeRules
schema_versionstringMust equal "5.0".
idstringLowercase slug: ^[a-z0-9]+(?:-[a-z0-9]+)*$. Stable; this is the module's identity across versions.
namestringRequired, non-empty. Display name; also derives the default filesystem root.
versionstringRequired, non-empty. Use semver (MAJOR.MINOR.PATCH).
summarystringOptional one-line description.

entrypoints

FieldTypeRules
entrypoints.uistringRequired. Relative path to the UI HTML inside the package (e.g. ui/index.html). Must stay inside the package (no leading /, no ..).
entrypoints.workerstringOptional. 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):

PermissionGrants
files.readRead files the user picks from the module's input folders.
files.writeWrite outputs into the module's output folder.
files.manageOrganize 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.createQueue a background job via the runtime contract.
jobs.emit_progressEmit progress events from a running job.
modules.useBaseline "this is an interactive module" capability (no file/job access).
settings.selfRead/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.
notificationsRaise desktop notifications (toast + notification centre) and set a count badge on the module's own taskbar icon. Baseline.
files.read.allElevated — 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.allElevated — 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.readElevated — requires user consent. Read what's on the user's Desktop (app icons + Desktop files/folders) via hanabi.desktop.list.
desktop.workspaceElevated — 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.shortcutsElevated — 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.personalizeElevated — 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.fetchRequires 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.executeRequires 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.readRead the signed-in user's storage usage/quota (used / limit / remaining bytes) — read-only aggregates. Baseline.
data.storeA per-module structured data store: collections of JSON documents scoped to (user, module), quota-bound. Baseline. See examples/modules/inventory-tracker.
jobs.queueEnqueue a background job (vs the in-request jobs.create), then poll or cancel it. Baseline + in-flight quota.
jobs.scheduleAdmin-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.webcodecsConsent-gated browser-runtime flags surfaced in the served Permissions-Policy / iframe allow for richer UIs (games, editors).
client.wasmAdmin-approved. Relaxes the served CSP ('wasm-unsafe-eval') so the module may run WebAssembly.
worker.nativeAdmin-HIGH (default-deny). Runs the worker on the native-tools image (ffmpeg / imagemagick / libvips / ghostscript) for transcode/render/compress.
network.fetch.broadAdmin-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.serviceAdmin-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 *.all file perms and the client.* 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-HIGHdefault-deny; an admin must grant each one per module and set its quota at review (worker.native, worker.service, network.fetch.broad, the big worker.execute tiers). See examples/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.

FieldTypeRules
filesystem.rootstringRequired. Must be a safe path under `Modules/` (e.g. Modules/Example Module).
filesystem.folders[]arrayEach folder is created per user on install (when create_on_install).
folders[].idstringLowercase slug, unique within the module.
folders[].namestringDisplay name.
folders[].roleenumOne of input, output, workspace, cache, config.
folders[].pathstringMust be a safe path under `filesystem.root`.
folders[].create_on_installboolDefault true.
folders[].allowed_extensionsstring[]Optional hint of acceptable file types.
folders[].descriptionstringOptional.

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.

FieldTypeNotes
dependencies.runtimes[]arrayEach { name, version?, required? }. namebrowser, python, node, office.
dependencies.python[]string[]Python package requirements (e.g. "pillow>=10.0").
dependencies.node[]string[]Node package requirements.
dependencies.officeobject{ 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

FieldTypeRules
default_width / default_heightintMust be ≥ the minimums; default size capped at 2200 × 1400.
min_width / min_heightintmin_width ≥ 320, min_height ≥ 240.
resizablebool
allow_multipleboolWhether multiple instances can open at once.
lock_aspect_ratioboolOptional. When true, aspect_ratio must be > 0.
aspect_rationumberOptional. Width/height ratio enforced when locked.
ui.popouts[]arrayOptional secondary windows.

desktop (optional — desktop integration)

Opt into shell integration. Both keys are optional; invalid entries are dropped during normalization.

FieldTypeRules
desktop.file_associationsstring[]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_listarrayStart-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.
json
"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.

Live manifest validator valid
Edit the manifest
Checks (same as the backend)
V002 schema_version is "5.0"
V003 id is a valid slug
V004 name, version, and entrypoints.ui are present
V005 all permissions are recognised
V006 filesystem.root is under Modules/
V011 ui.window sizes are consistent