Mops CLI
Opinionated guide for Motoko projects. Covers project config, dependency management, type-checking, building, and linting.
Key Principles
- No dfx — always pin
mocin[toolchain]. Use the newestmocversion. - No
mo:base— it is deprecated. Always usemo:core(import Array "mo:core/Array"). - All config in
mops.toml— canisters, moc flags, toolchain versions, build settings. - Canister-centric workflow — define all canisters in
[canisters]; never pass file paths tomops check. Exception: library packages (no[canisters]) use file paths directly:mops check src/**/*.mo.
Project Setup
Minimal mops.toml
[toolchain]
moc = "1.7.0"
lintoko = "0.10.0"
[dependencies]
core = "2.5.0"
[moc]
args = ["--default-persistent-actors", "-W=M0223,M0236,M0237"]
[canisters.backend]
main = "src/backend/main.mo"
[canisters.backend.migrations]
chain = "src/backend/migrations"
check-limit = 10 # optional — speeds up `mops check` when the chain gets long
[canisters.backend.check-stable]
path = ".old/src/backend/dist/backend.most"
[build]
outputDir = "src/backend/dist"
args = ["--release"]
check-stable verifies stable variable compatibility against a .most file from the deployed version. For a new project with no prior deployment, create a trivial .most file representing an empty actor:
// Version: 1.0.0
actor {
};
Optional canister fields: candid (path to .did for compatibility checking), initArg (Candid-encoded init args).
Warning Flags
-W=M0223,M0236,M0237 — redundant type instantiation (M0223), suggest contextual dot notation (M0236), suggest redundant explicit arguments (M0237). These are allowed (disabled) by default; -W= enables them as warnings.
Moc Args Layering
Flags are applied in this order (later overrides earlier):
[moc].args— global, all commands (check, build, test, etc.)[build].args— build only (e.g.--release)[canisters.<name>.migrations]— auto-injected--enhanced-migration(managed by mops)[canisters.<name>].args— per-canister- CLI
-- <flags>— one-off overrides
Core Commands
mops install
mops install
Run after cloning or after manual mops.toml edits. Updates mops.lock. In CI, uses --lock check by default (fails if lockfile is stale).
mops add <package>
mops add core # latest version
mops add core@2.5.0 # specific version
mops add --dev test # dev dependency
Updates mops.toml and mops.lock.
mops check
Primary correctness command — runs moc check, then check-stable (if configured), then lint (if lintoko is in toolchain).
mops check # all canisters
mops check backend # single canister
mops check --fix # autofix + check + stable + lint
mops check --verbose # show moc invocations
mops check -- -Werror # treat warnings as errors
Always use canister names, not file paths. Per-canister args from mops.toml are applied automatically.
--fix applies machine-applicable fixes from both moc and lintoko in one pass.
mops build
mops build # all canisters
mops build backend # single canister
mops build --verbose # show compiler commands
mops build -- --ai-errors # pass extra moc flags
Produces .wasm, .did, and .most files in [build].outputDir (default .mops/.build).
mops toolchain
mops toolchain use moc 1.7.0 # pin specific version
mops toolchain use moc latest # pin latest version (non-interactive)
mops toolchain use lintoko 0.10.0 # pin specific version
mops toolchain update moc # update to latest (requires existing [toolchain] entry)
mops toolchain update # update all tools to latest
mops toolchain bin moc # print path to binary
Agent note: toolchain use <tool> without a version opens an interactive picker — do not use in scripts or agents. Always pass a version or latest. toolchain update only works when the tool already has a [toolchain] entry.
Enhanced migrations
When [canisters.<name>.migrations] is configured, mops check, mops build, and mops check-stable automatically inject --enhanced-migration. Do not add --enhanced-migration to [canisters.<name>].args — mops will error.
Create migration files directly in the chain directory.
check-limit (optional) caps how many recent chain files mops check and mops lint consider — useful when the chain grows long and re-checking every old migration slows feedback down. mops build is unaffected by check-limit. When the limit kicks in, mops stages the included files into .migrations-<canister>/ next to the chain directory (auto-.gitignored). moc diagnostics may then print paths there — the real file lives in the chain directory with the same name.
mops remove <package>
mops remove base
Dependency Management
mops outdated # list outdated dependencies (caret-bound)
mops update # update all within caret bound (no major-version crossing)
mops update core # update specific package within caret bound
mops update --major # allow updates that cross major versions
mops sync # add missing / remove unused packages
Other Commands
mops test
Tests live in test/*.test.mo:
mops test # run all tests
mops test my-test # filter by name
mops test --mode wasi # use wasmtime (for to_candid/from_candid)
mops test --reporter verbose # show Debug.print output
mops test --watch # re-run on file changes
mops lint
Runs lintoko (also runs automatically as part of mops check when lintoko is in toolchain):
mops lint # lint all .mo files
mops lint --fix # autofix lint issues
mops lint <name> # filter to .mo files matching <name>
When [canisters.<name>.migrations].check-limit is set, mops lint skips the trimmed chain migrations to match what moc sees during mops check. To lint a trimmed migration on demand, pass an explicit filter (e.g. mops lint OldMigrationName).
mops format
mops format # format all .mo files
mops format --check # check formatting without modifying
Common Patterns
Warning suppression for a canister
Use per-canister args (not global) for suppressions:
[canisters.backend]
main = "src/backend/main.mo"
args = ["-A=M0198"]
New project
mops init -y
mops toolchain use moc latest # pin latest moc (non-interactive)
mops toolchain use lintoko latest # pin latest lintoko
mops add core
Then configure [moc].args, [canisters], and [build] in mops.toml.
To update tools later: mops toolchain update moc or mops toolchain update (all tools).