Manifest reference
Every field in imperal.json — what it does, what's required, what it accepts
imperal.json is the manifest the marketplace and web-kernel both read. You don't author it by hand — imperal build ./extension generates it from your app.py. This page documents every field so you know what's in there and why.
All field names track generate_manifest() in the current imperal-sdk source — see the changelog for version-pinned history.
Top-level shape
{
"manifest_schema_version": 3,
"sdk_version": "4.1.8",
"app_id": "tasks",
"version": "1.2.3",
"name": "Tasks (Mini)",
"description": "A task manager Webbee can drive in plain language. ≥40 chars.",
"icon": "icon.svg",
"icon_size_bytes": 4096,
"actions_explicit": true,
"system": false,
"capabilities": ["tasks:read", "tasks:write"],
"tools": [...],
"signals": [...],
"schedules": [...],
"required_scopes": [...],
"webhooks": [...],
"events": {
"subscribes": [...],
"emits": [...]
},
"exposed": [...],
"lifecycle": {...},
"lifecycle_hooks": {...},
"tray": [...]
}Top-level fields
Prop
Type
How icons are served
The icon field is a path in your extension repo (icon.svg next to app.py). Commit the file, then publish your extension once — the platform persists the SVG bytes alongside the rest of your manifest and streams them back to every surface (marketplace, sidebar, chat) at request time. There is no filesystem sync to configure; one publish propagates the icon everywhere.
The marketplace exposes a public, cached endpoint per app that returns image/svg+xml. Browsers cache aggressively because icon bytes change only when you re-publish.
tools[] (ToolDef)
One entry per @chat.function and per @ext.tool (non-synthetic). The web-kernel chain planner reads tools[] directly.
{
"name": "create_task",
"description": "Add a task to the user's pending list.",
"action_type": "write",
"chain_callable": true,
"effects": ["task.create"],
"id_projection": null,
"params_schema": {
"$defs": {...},
"properties": {
"title": { "description": "The task — what needs to be done", "type": "string" }
},
"required": ["title"],
"title": "CreateTaskParams",
"type": "object"
},
"return_schema": {},
"event": "task.created",
"scopes": [],
"owner_chat_tool": "my-app"
}Prop
Type
schedules[] (ScheduleDef)
One entry per @ext.schedule.
{
"name": "nightly_archive",
"cron": "0 3 * * *"
}Prop
Type
webhooks[] (WebhookDef)
One entry per @ext.webhook.
{
"path": "/incoming",
"method": "POST",
"secret_header": "X-Hub-Signature-256"
}Prop
Type
events.subscribes[]
One entry per @ext.on_event.
{"type": "email.received", "handler": "handle_email_received"}events.emits[]
One entry per @ext.emits.
{"type": "my-app.item.created", "schema_ref": "ItemCreatedEvent"}lifecycle
{
"on_install": true,
"on_uninstall": true,
"on_upgrade": ["2.0.0"],
"health_check": {"interval_sec": 60}
}Prop
Type
lifecycle_hooks
Added in v4.0.0 (V22). Records Python signature strings so the web-kernel knows which kwargs to pass to each lifecycle hook.
{
"on_install": {"signature": "(ctx)"},
"on_upgrade:2.0.0": {"signature": "(ctx)"}
}tray[] (TrayDef)
One entry per @ext.tray.
{
"tray_id": "unread",
"icon": "Mail",
"tooltip": "Unread messages"
}exposed[] (ExposedMethod)
One entry per @ext.expose.
{"name": "get_summary", "action_type": "read"}secrets[] (SecretDecl) — EXT-SECRETS-V1 (v4.2.2+)
One entry per @ext.secret. Optional field; manifest schema v3 stays — secrets[] is additive and back-compatible. Extensions without @ext.secret declarations omit the field entirely.
The Pydantic schema for this entry is SecretDecl in imperal_sdk.manifest_schema (added in v4.2.8 to close the I-MANIFEST-EMITTER-SCHEMA-SYMMETRIC drift gap). It mirrors the runtime SecretSpec dataclass after to_manifest_dict(). Since v4.2.8 validate_manifest_dict() rejects malformed secret entries at publish time — uppercase name, invalid write_mode, max_bytes outside [1, 65536], empty description. Earlier versions silently accepted these.
{
"secrets": [
{
"name": "spotify_api_key",
"description": "Your Spotify API key (from developer.spotify.com).",
"required": true,
"write_mode": "user",
"max_bytes": 200
},
{
"name": "spotify_refresh_token",
"description": "OAuth refresh token written by extension after authorize.",
"required": false,
"write_mode": "extension",
"max_bytes": 4096,
"rotation_hint_days": 30
}
]
}Prop
Type
The Panel UI at /ext/[extId]/secrets lists every entry, lets the user set/rotate write_mode='user' and write_mode='both' secrets, and shows read-only helper text for write_mode='extension' secrets. See @ext.secret reference for the full federal contract.
Generated, not authored
You don't write imperal.json by hand:
imperal build ./extension
# or
python3 -c "
from imperal_sdk import save_manifest
import app
save_manifest(app.ext, '.')
"This walks your app.py, introspects every decorated function, and produces the JSON. Editing it manually after build is counterproductive — the web-kernel and your code will drift and validators stop catching things.
Common mistakes
Wrong field name: The top-level version field is manifest_schema_version, not schema_version. Several tools in older docs and LLM completions use the wrong name — the web-kernel will reject a manifest with schema_version.
Wrong array name: Chat function entries are in tools[], not functions[]. functions is not a valid manifest field.