Getting started
The current app workflow is repository-based: copy the minimal app, run its npm workspace scripts, then either include the resulting archive in a PocketIC deployment config or install it through Neutron's reviewed browser flow.
Prerequisites
From the repository root, install the npm workspaces from the root lockfile:
npm install
The app scripts also require:
- Bun, which runs the TypeScript build, packaging, and unit-test scripts;
- Mops on
PATH, becausemopackexecutesmops sourcesto resolve the packages declared bymops.toml; and - Node/npm, including the root workspace installation above.
You do not need a host moc. Packaging and install-time compilation use the
vendored neutron-motoko-wasm compiler, and a moc on PATH is not consulted.
The optional repository shell is the easiest way to obtain the pinned runtime tools:
nix develop
It supplies Bun, Node/npm, Git, and curl. On Linux it also supplies Chromium and
sets the Playwright executable and --stack-size=16384 launch argument used by
the browser compiler tests. It does not supply Mops, so install Mops
separately and confirm that mops --version works before packaging.
Local deployment does not use a dfx project. The provisioner downloads and
verifies its pinned PocketIC server on first use, then drives that server
directly.
Copy the minimal app
There is no generator command in the repository. Start with apps/hello; use
apps/kitchensink as the richer capability and design reference, not as a
production starter template.
Copy Hello to a destination that does not already exist, then remove the copied generated state:
cp -R apps/hello apps/my_app
rm -rf apps/my_app/.mops apps/my_app/dist apps/my_app/node_modules
rm -f apps/my_app/*.neutron apps/my_app/*.tsbuildinfo
rm -f apps/my_app/neutron.lock.json
Removing neutron.lock.json is required when choosing a new app id: the copied
lock belongs to hello. The first package build creates a new lock for an app
that declares managed memory; commit that new lock once the initial schema is
intentional.
Update at least:
package.json: give the npm workspace a uniquename;neutron.json: changeid,name, releaseversion, tiles, memory roots, capabilities, and dependencies; remove Hello'supdate_sourceunless you operate that source;backend/main.moandbackend/memory/: rename the Hello memory root and write the new backend;src/,public/, andbuild.ts: replace the frontend and its assets; andtest/andREADME.md: replace Hello-specific assertions and instructions.
Do not maintain neutron.json.func or the generated type-alias block in the
Motoko module by hand. mogen rewrites both from annotated backend functions.
After changing the workspace, run npm install again at the repository root so
the root lockfile and workspace links include it.
Choose the app id once
The id is the installed identity of the app. It scopes assets, physical method names, managed-memory names, app origins, and update matching. Changing it makes a different app rather than an update.
| Rule | Meaning |
|---|---|
| 4–30 characters | Enforced by the shared manifest and installer validator |
| Lowercase alphanumeric segments | The accepted pattern is ^[a-z0-9]+(?:_[a-z0-9]+)*$ |
| Single underscores only | Leading, trailing, and repeated underscores are rejected |
Do not use kernel for an app | kernel selects the privileged kernel-replacement path |
The double underscore remains compiler-owned. An app method such as read
becomes the physical Candid method app_<id>__read, so the app/method boundary
cannot collide with an authored id.
The display name is separate: it must contain 3–20 ASCII letters, digits, or
spaces.
Start at 0.1.0
Set the new app's manifest to:
{ "version": 100 }
App versions are packed as major * 10_000 + minor * 100 + patch, with minor
and patch in 0..99. Thus 100 is 0.1.0, and the archive will be named
<id>.v0.1.0.neutron. The version in package.json does not name a Neutron
release; the packer reads neutron.json.
A browser update of an already installed id rejects an equal version or a downgrade. A local whole-canister reinstall may rebuild the same release because it discards the installed app set. See Versioning.
Build, package, and test
The copied Hello scripts can be run either inside the app directory or through
the npm workspace name. For a workspace named neutron-my-app:
npm --workspace neutron-my-app run validate
npm --workspace neutron-my-app run build
npm --workspace neutron-my-app run package
npm --workspace neutron-my-app test
npm run package produces the archive. In the copied script, npm test runs
that complete package command and then the Bun tests, so update the copied tests
and use npm test as the pre-install check.
The package command has this exact order:
validate checks the source neutron.json against the closed format-3
schema and its semantic rules, including display metadata, dependencies, tile
paths, resident surfaces, capabilities, and managed memory.
build first runs the app's build.ts; Hello bundles the frontend into
dist/web/ and copies public/. It then runs mogen, which rewrites the source
manifest's func map and the backend's generated input/output aliases.
mopack re-reads the generated manifest, executes mops sources, and walks
the app entry plus every declared memory schema and migration. It applies the
Motoko text/AST policy, rewrites imports to content hashes, writes
dist/mo/<sha256>.mo, and writes the packaged dist/neutron.json. For managed
memory it also creates or merges neutron.lock.json, rejecting changes to
already locked schemas or migrations. Prohibited Motoko APIs fail here, not in
the initial validate step.
schema reads the generated aliases and emits wrapper-accurate JSON Schemas
for public app methods in dist/schema.json.
pack walks the complete dist/ tree, gzips every regular file, and
MessagePack-encodes the flat path-to-bytes map into
<id>.v<major>.<minor>.<patch>.neutron. After a successful write it removes
older local archives for the same id.
Run it with the local provisioner
The provisioner consumes archives; it never builds an app workspace. Package
the app first, copy local.ndeploy.json to a separately named config, and edit
that config's artifacts.packages list to contain the exact app archives you
want in the local Neutron:
cp local.ndeploy.json my-app.ndeploy.json
Keep the server command running in one terminal:
npm run provision -- my-app.ndeploy.json serve
From another terminal in the same development environment, install the complete configured set and print its status:
npm run provision -- my-app.ndeploy.json reinstall
npm run provision -- my-app.ndeploy.json status
serve owns the supervised PocketIC process. reinstall compiles the kernel
and every configured app into one actor, installs it on first use, and performs
a destructive whole-canister reinstall on later runs. Treat every rerun as loss
of local application state.
Open the node URL printed by status, not the bare gateway. With the default
gateway it has the form
http://<canister-id>.localhost:8000/; the canister id is recorded in the
config's .ndeploy.session.json and must not be hardcoded.
There is no provisioner command for a state-preserving per-app install, update, or uninstall.
Exercise the reviewed browser install
Use this path when testing the experience an owner sees. Provision a baseline
whose config does not already include your app, open the URL printed by
status, then use the launcher’s install controls:
- Choose File and select the
.neutronarchive, or choose URL. - For URL installs, use HTTPS with CORS enabled and no redirect. A local Neutron also accepts loopback HTTP.
- Review the decoded package identity, version, capability disclosures, and permission consequences while browser compilation completes.
- Approve the final request to commit the install.
File and URL acquisition feed the same package preparation, compile, review, and checked deployment flow. If the app is already installed, that same manual flow is an update and therefore requires a strictly higher release version.
Release an update
For each release:
- change the app and increment
versioninneutron.json; - add immutable managed-memory schemas and forward migration edges when state types change—never edit a locked historical schema;
- run the workspace's package and test workflow; and
- update deployment-config archive paths or distribute the new archive through File/URL install.
If the manifest omits update_source, updates are manual. To make a release
discoverable in Settings, publish the exact archive through a certified update
source that you operate and set its canister principal in update_source.
Publishing does not install anything automatically, and an equal-version
different archive is not an in-place republish. See
Package updates.
What to read next
- Project layout — what every file is for
- The Motoko backend — the required module and
Initshape - Managed memory — read before designing persistent Motoko state