manifest.json
Every module declares itself in a manifest.json at the root of its bundle. It's validated on install and
again on load; an invalid bundle is shown in Settings with the reason rather than running.
{
"id": "com.you.weather",
"name": "Weather",
"description": "Local weather in a widget.",
"version": "1.0.0",
"author": "You",
"permissions": ["net:fetch"],
"allowedDomains": ["api.open-meteo.com"],
"entry": "index.html",
"surfaces": [
{ "kind": "widget", "id": "badge", "gated": false },
{ "kind": "settings", "id": "prefs", "label": "Weather", "icon": "gauge", "order": 220 }
]
}
Fields
| Field | Required | Notes |
|---|---|---|
id | yes | Lowercase, reverse-DNS-ish: ^[a-z0-9]+([._-][a-z0-9]+)*$, 3-64 chars. May not start with playstatus. (reserved for built-ins). |
name | yes | Display name, ≤ 60 chars. |
version | yes | Free-form, ≤ 20 chars (semver recommended). |
description | no | One line shown in the consent prompt and Settings. |
author | no | Shown in the consent prompt. |
permissions | no | Array from the permission set. Only request what you use. |
allowedDomains | with net:fetch | Bare hosts (e.g. api.example.com) — no scheme, no path. net:fetch requires at least one. |
entry | yes | Relative path inside the bundle (no .., no absolute/rooted paths). |
surfaces | yes | One or more surfaces. At least one is required. |
Surface entries
Each entry in surfaces describes one place your module renders:
{ "kind": "widget" | "window" | "settings", "id": "string",
"label": "string?", "icon": "string?", "order": 0, "gated": true }
kind+idpair with aregister*call in your code (for windows, theidis used as the windowkind).label,icon,orderdrive the dock button (windows) or the sidebar entry (settings).gated: falsekeeps a widget visible while the overlay panel is closed.
See Surfaces for the full behaviour of each kind.