The manifest
neutron.json is validated against a closed JSON Schema (draft-07) that
rejects unknown top-level properties. The source validator and Motoko
packager validate authored manifests; install preparation validates the
packaged manifest; the compiler validates it again before assembly; and the
assembler enforces cross-package and generated-Motoko injection invariants.
Required fields
| Field | Rule |
|---|---|
format | Integer literal 3 |
id | 4–30 chars, ^[a-z0-9]+(?:_[a-z0-9]+)*$ |
name | 3–20 chars, letters, digits, and spaces |
version | Packed release integer, minimum 100 (= 0.1.0) |
The id rule matters more than it looks. Leading, trailing, and repeated
underscores are rejected so that __ stays compiler-owned — it is the
unambiguous separator in generated physical method names. The id also cannot be
changed later, because install paths are built from it.
Optional fields
| Field | What declaring it means |
|---|---|
src | The Motoko entry source under backend/; required by the standard Motoko packager |
entry | Generated package field: the final content hash of the entry module; rejected in a source manifest |
update_source | Opts into owner-triggered checks against a canonical, non-anonymous, non-management canister principal |
init_arg | Kernel only. An ordinary app declaring this is rejected |
func | The app's generated methods |
memory | Persistent memory roots and their schema lineage |
dependencies | Backend functions consumed from other installed apps |
backend | Which capability interfaces are injected into the constructor |
tiles | Launcher tile pages |
background | The single optional resident process |
tray | The single optional tray icon (requires background) |
description | Untrusted short description, 1–280 chars |
capabilities | The closed declaration of maximum authority |
func — generated methods
Each entry is a closed object:
| Field | Meaning |
|---|---|
type | update, query, or internal |
allow | Kernel-only unauthorized. Ordinary apps rejected; the legacy any is rejected |
async | true → await; "async*" → await*; false → synchronous call |
arg | Controlled compiler-injected arguments |
expose | "apps" on internal functions — makes them available to declared consumers |
Keys are app-local logical names: a Motoko identifier bounded to 128 ASCII characters.
Logical vs. physical names
A non-kernel public wrapper receives a deterministic physical Candid name:
app_<app-id>__<logical-method>
Kernel methods stay unmangled. Because canonical app ids cannot contain or
produce __, the separator identifies the app boundary exactly — so two apps
may safely use the same logical method name.
Which name you use depends on who you are:
| Caller | Name to use |
|---|---|
| The app's own frontend, via self-call tools | Logical |
| An authorized direct client (CLI, script) | Physical |
| A public client or another canister | Neither — use a public-ingress dispatcher |
The installed registry records both and the kernel verifies their exact relationship. V1 creates no friendly global aliases.
memory — managed persistent state
"memory": {
"my_app": {
"version": 2,
"schemas": {
"1": { "src": "memory/my_app/v1.mo" },
"2": { "src": "memory/my_app/v2.mo" }
},
"migrations": [
{ "from": 1, "to": 2, "src": "memory/my_app/v1_to_v2.mo" }
]
}
}
| Field | Rule |
|---|---|
version | The current schema version, minimum 1 |
schemas | At most 64 versions, each { src } (packages add hash and entry) |
migrations | At most 128 forward-only, unique edges |
retired | Declares commit-atomic staged retirement, keeping an ownership tombstone |
Memory ids are local to their owning app and bounded to 128 characters.
Generated stable fields use a length-delimited (app id, memory id) stem, so
two apps using the same local id are independent rather than conflicting.
See Managed memory.
dependencies — typed backend composition
"dependencies": {
"contacts": {
"app": "contacts",
"min_version": 100,
"functions": ["lookup_destination"]
}
}
At most 32 dependencies, 64 functions each. The alias matches
^[a-z][a-z0-9_]{0,29}$. The assembler derives the matching app_calls group
automatically — no constructor token is authored.
See Backend dependencies.
backend — which interfaces get injected
"backend": {
"capabilities": {
"deferred_timers": { "api": 1 },
"backend_calls": { "api": 1 },
"randomness": { "api": 1 },
"chain_key_signing": { "api": 1 },
"stable_store": { "api": 1 },
"https_outcalls": { "api": 1 },
"vetkeys_public": { "api": 1 },
"certified_assets": { "api": 2 }
}
}
Except for the structural deferred_timers service, every selection here
requires its corresponding declaration under capabilities. The
distinction is deliberate:
capabilities.Xbounds what the app may ever do with X.backend.capabilities.Xdelivers a long-lived Motoko handle.
deferred_timers is AppScope-bound infrastructure with no separate authored
authority declaration.
Many combinations use only the first. A browser-only vetKeys app declares
capabilities.vetkeys and omits vetkeys_public. An app whose only backend
calls happen inside a scheduled task declares backend_calls without selecting
the foreground interface. A POST-route app declares http_routes and receives
no capability field at all.
Certified Assets is stricter: its API-2 declaration and API-2 backend selector are required as a pair.
That declaration derives a separate API-1 certified_read_routes capability
plan entry. Publication mounts use exact-Neutron-host GET/HEAD; portable
immutable_blob and mutable_blob mounts use canister-gateway GET.
tiles, background, tray
"tiles": [
{ "id": "main", "title": "Notes", "path": "index.html", "icon": "static/icon.png",
"description": "Take notes." }
]
| Surface | Cardinality | Notes |
|---|---|---|
tiles | 0–32 | Unique ids and safe relative paths; each declared tile defaults its path to index.html and icon to static/icon.png |
background | 0 or 1 | { path, description? } — a hidden resident iframe |
tray | 0 or 1 | { title, path, icon } — requires background |
Path validation is semantic, beyond raw JSON Schema: relative, no leading slash,
no backslash, no empty segment, no . or ... The installer rejects a package
declaring a tile, background, or tray entrypoint that is absent from web/.
A tray declaration is normalised app UI metadata, not a permission. It
grants no dedicated background origin, and its frame is always transient,
credentialless, and opaque even when the app's background has approved
persistent storage.
capabilities — the closed authority declaration
This is the heart of the manifest. It is a closed object: unknown capability ids, unsupported API versions, unknown nested fields, and removed legacy fields are all rejected.
| Declaration | Bounds |
|---|---|
backend_calls | Outbound canister calls: reservation scopes, concurrency, cycle ceilings per call and per UTC day |
randomness | Access to a kernel-brokered raw_rand |
chain_key_signing | 1–4 assertion slots, each with algorithm and byte ceiling |
stable_store | 1–8 stores, each with schema version and entry/key/value/byte ceilings |
https_outcalls | 1–8 endpoints, each fixing URL prefix, methods, header allowlist, and bounds |
vetkeys | 1–4 key slots |
public_ingress | 1–32 exact public Candid routes |
http_routes | API 1 bounded POST update-handler mounts |
certified_assets | API 2 scoped publication plus immutable and mutable blobs; derives certified read routes |
connections | External credential providers and scopes |
scheduled_tasks | At most 2 fixed callbacks, with interval and call budget |
preapproved_self_calls | Up to 32 of the app's own methods, callable without a dialog |
agent_entrypoints | Up to 4 resident tools eligible for Agent Mode |
background_ui_requests | Which request classes a background may raise |
ethereum_provider | Declared EVM chains and an exact method subset |
persistent_browser_storage | A persistent dedicated origin for the background |
dedicated_resident_origin | A credentialless-ephemeral dedicated origin |
Each is documented with its enforcement in Capabilities.
The compiler normalises declared capabilities together with derived memory, dependency, function, and frontend-endpoint entries into one versioned CapabilityPlan carrying a deterministic SHA-256 fingerprint. Capability projections in the registry and Settings carry that closed plan wire rather than copying raw capability declarations.
Text safety
Every app-controlled string that reaches a trusted surface rejects Unicode control and formatting characters after normalisation: bidi embeddings, overrides, isolates and marks; zero-width spaces and joiners; word joiners; byte order marks; line and paragraph separators; other default-ignorable code points.
Stored legacy metadata is neutralised when projected into the registry, so it cannot reorder or conceal kernel UI text. These checks prevent visual spoofing — they do not make app text trusted.