Skip to main content

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, because mopack executes mops sources to resolve the packages declared by mops.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 unique name;
  • neutron.json: change id, name, release version, tiles, memory roots, capabilities, and dependencies; remove Hello's update_source unless you operate that source;
  • backend/main.mo and backend/memory/: rename the Hello memory root and write the new backend;
  • src/, public/, and build.ts: replace the frontend and its assets; and
  • test/ and README.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.

RuleMeaning
4–30 charactersEnforced by the shared manifest and installer validator
Lowercase alphanumeric segmentsThe accepted pattern is ^[a-z0-9]+(?:_[a-z0-9]+)*$
Single underscores onlyLeading, trailing, and repeated underscores are rejected
Do not use kernel for an appkernel 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:

  1. Choose File and select the .neutron archive, or choose URL.
  2. For URL installs, use HTTPS with CORS enabled and no redirect. A local Neutron also accepts loopback HTTP.
  3. Review the decoded package identity, version, capability disclosures, and permission consequences while browser compilation completes.
  4. 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:

  1. change the app and increment version in neutron.json;
  2. add immutable managed-memory schemas and forward migration edges when state types change—never edit a locked historical schema;
  3. run the workspace's package and test workflow; and
  4. 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.