Skip to main content

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

FieldRule
formatInteger literal 3
id4–30 chars, ^[a-z0-9]+(?:_[a-z0-9]+)*$
name3–20 chars, letters, digits, and spaces
versionPacked 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

FieldWhat declaring it means
srcThe Motoko entry source under backend/; required by the standard Motoko packager
entryGenerated package field: the final content hash of the entry module; rejected in a source manifest
update_sourceOpts into owner-triggered checks against a canonical, non-anonymous, non-management canister principal
init_argKernel only. An ordinary app declaring this is rejected
funcThe app's generated methods
memoryPersistent memory roots and their schema lineage
dependenciesBackend functions consumed from other installed apps
backendWhich capability interfaces are injected into the constructor
tilesLauncher tile pages
backgroundThe single optional resident process
trayThe single optional tray icon (requires background)
descriptionUntrusted short description, 1–280 chars
capabilitiesThe closed declaration of maximum authority

func — generated methods

Each entry is a closed object:

FieldMeaning
typeupdate, query, or internal
allowKernel-only unauthorized. Ordinary apps rejected; the legacy any is rejected
asynctrueawait; "async*"await*; false → synchronous call
argControlled 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:

CallerName to use
The app's own frontend, via self-call toolsLogical
An authorized direct client (CLI, script)Physical
A public client or another canisterNeither — 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" }
]
}
}
FieldRule
versionThe current schema version, minimum 1
schemasAt most 64 versions, each { src } (packages add hash and entry)
migrationsAt most 128 forward-only, unique edges
retiredDeclares 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.X bounds what the app may ever do with X.
  • backend.capabilities.X delivers 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." }
]
SurfaceCardinalityNotes
tiles0–32Unique ids and safe relative paths; each declared tile defaults its path to index.html and icon to static/icon.png
background0 or 1{ path, description? } — a hidden resident iframe
tray0 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.

DeclarationBounds
backend_callsOutbound canister calls: reservation scopes, concurrency, cycle ceilings per call and per UTC day
randomnessAccess to a kernel-brokered raw_rand
chain_key_signing1–4 assertion slots, each with algorithm and byte ceiling
stable_store1–8 stores, each with schema version and entry/key/value/byte ceilings
https_outcalls1–8 endpoints, each fixing URL prefix, methods, header allowlist, and bounds
vetkeys1–4 key slots
public_ingress1–32 exact public Candid routes
http_routesAPI 1 bounded POST update-handler mounts
certified_assetsAPI 2 scoped publication plus immutable and mutable blobs; derives certified read routes
connectionsExternal credential providers and scopes
scheduled_tasksAt most 2 fixed callbacks, with interval and call budget
preapproved_self_callsUp to 32 of the app's own methods, callable without a dialog
agent_entrypointsUp to 4 resident tools eligible for Agent Mode
background_ui_requestsWhich request classes a background may raise
ethereum_providerDeclared EVM chains and an exact method subset
persistent_browser_storageA persistent dedicated origin for the background
dedicated_resident_originA 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.