Motoko 2.0.0: migrate actors and check upgrades
Before deploying Motoko 2.0.0, mark reset-on-upgrade fields transient, remove obsolete compiler flags, and check stable compatibility. A canister still using classical persistence needs a one-time, irreversible migration.

Motoko 2.0.0 shipped on October 6, 2026, making enhanced orthogonal persistence the compiler’s only persistence mode and actor fields persistent by default. Teams upgrading existing canisters should mark fields that must reset, check compatibility, and identify whether the canister still uses classical persistence before deploying.
This guide is for developers moving projects from moc 1.x to 2.0.0. You will finish with a source and deployment checklist for one canister upgrade. You need the Motoko 2.0.0 compiler, your project’s existing build setup, and access to the canister’s current stable signature or source. If you deploy with ICP CLI, use the version already pinned by your project and verify it with icp --version.
The Motoko 2.0.0 migration guide is the main reference for the source changes and compiler checks below. This is a follow-up focused on those steps; the earlier Motoko 2.0 upgrade overview covers the release at a higher level.
What changes for an existing canister
In Motoko 2.0, fields in a bare actor or actor class persist across upgrades by default. A field marked transient is initialized again on upgrade. For example, a cache or function value that should not survive a code change needs that modifier:
actor {
var accountCount : Nat = 0; // retained across upgrades
transient var cache : [Nat] = []; // initialized again on upgrade
transient let log = func (message : Text) { Debug.print(message) };
};
This default is a source-level change with a runtime consequence. If a project previously compiled with --legacy-actors, unmarked fields were transient. In 2.0, those fields become persistent unless marked transient. Non-persistable fields, such as functions or objects with methods, need transient; otherwise compilation reports an error. Fields that become persistent are treated as new stable fields on their first upgrade, so their initializer runs then, and their value is retained by later upgrades. These rules are described in the moc v1 to v2 migration guide and the Motoko data persistence docs.
Existing source that already declares persistent actor generally follows the new default already. In 2.0, persistent and stable become redundant, with compiler warnings suggesting they can be removed. Removing them does not change the stable signature. The keyword flexible, previously an alias for transient, is removed; replace it with transient.
The one-way persistence migration
First determine how the deployed canister was compiled. Motoko 2.0 cannot produce a classical-persistence canister, but it can upgrade an existing one. On that canister’s next upgrade to enhanced orthogonal persistence (EOP), the transition is one-way: later upgrades cannot return to classical persistence.
For that single transition, compile with --enhanced-orthogonal-persistence and without --enhanced-migration. Otherwise the upgrade traps with Detected implicit upgrade from classical orthogonal persistence to enhanced orthogonal persistence, and combining the transition with --enhanced-migration traps too. Later upgrades need neither flag. For a canister already using EOP, the flag is accepted but does nothing. The 2.0.0 release notes state that EOP retains persistent 64-bit main memory and uses incremental garbage collection; they also say classical persistence and the other garbage collectors are removed.
On ICP, EOP depends on retaining the WebAssembly main memory during an upgrade. The platform upgrade option for this is wasm_memory_persistence = opt keep. The EOP documentation explains that this option is required for EOP and warns that replace drops Wasm main memory. Use a deployment tool that supports the EOP upgrade metadata; do not construct a custom install call that replaces memory.
Pre-upgrade checklist
Follow these steps for each canister before releasing the Motoko 2.0.0 build.
-
Build with your last 1.x compiler and clear its deprecation warnings. The migration guide specifically recommends building cleanly with the latest 1.16.x first. Fix
.vals()warnings by using.values(), and plan to replacepreupgradeandpostupgradehooks with a migration function where they are used. This separates existing cleanup from errors introduced by the compiler upgrade. -
Pin the compiler. In
mops.toml, set the toolchain version so local and CI builds use the same release:[toolchain] moc = "2.0.0" -
Audit state declarations. Review every
letandvarinside actors and actor classes. Mark caches, function values, version-specific values, and other state that should reset withtransient. Replace eachflexiblekeyword. Persistent collections can remain ordinary fields if their types support persistence. Check any legacy actor flags carefully because their old default made unmarked fields transient. -
Remove options and APIs that 2.0 no longer supports. Remove
--default-persistent-actors,--require-persistent-actors, and--legacy-actors. The release also removes--legacy-persistence,--copying-gc,--compacting-gc,--generational-gc,--rts-stack-pages,--skip-gc-deprecation-warning,--incremental-gc, and--experimental-rtti; these are rejected as unknown options. Theflexiblekeyword andExperimentalStableMemory/Prim.stableMemory*APIs are removed too. ReplaceExperimentalStableMemoryuse withRegion, as appropriate for your project. -
Run the project checks and address errors. Use the commands in the project’s Motoko toolchain. For the migration-guide workflow:
mops check mops check-stableThe migration guide also documents checking signatures directly with moc:
moc --stable-compatible old.most new.mostUse the signatures from the deployed and proposed versions. The compatibility check catches incompatible stable declarations; it does not replace reviewing the public Candid interface or testing the upgrade path.
-
Build with the correct transition flags. If the deployed canister still uses classical persistence, compile this upgrade with
--enhanced-orthogonal-persistenceand omit--enhanced-migration. For a canister already on EOP, the flag is redundant. If you use enhanced migration, it no longer requires the EOP flag, but do not combine that migration mode with the one-time classical-to-EOP transition. -
Deploy explicitly as an upgrade and verify the canister. With ICP CLI, build and request upgrade mode using the documented commands:
icp build icp deploy my-canister --mode upgrade icp canister status my-canisterReplace
my-canisterwith the configured canister name. ICP CLI documents that upgrade mode preserves stable state; the Motoko release guide explains the compiler-side migration requirements. Check the canister’s expected state through its own query methods after deployment. Consult the canister lifecycle guide for the CLI upgrade flow.
Pitfalls and troubleshooting
The upgrade traps with "Detected implicit upgrade from classical orthogonal persistence". Confirm that this is the intended one-time transition. Compile that upgrade with --enhanced-orthogonal-persistence, without --enhanced-migration, and preserve the resulting toolchain configuration for later releases.
A value unexpectedly survives an upgrade. Find the field declaration and mark it transient if it must reset. Pay special attention to projects that previously used --legacy-actors; the old default does not carry into 2.0.
A value unexpectedly resets. Check whether the field is explicitly or implicitly transient, and confirm the deployed version and new stable signature. New persistent fields initialize on their first upgrade; existing persistent fields are carried forward only when their stable representation is compatible.
The build rejects a removed option or feature. Search compiler arguments in project configuration and CI scripts, not only source files. Replace flexible with transient; replace removed stable-memory primitives with the Region API. Keep an older compiler only if you must continue producing classical modules.
A compatibility check passes but the upgrade still needs review. Stable compatibility is one part of upgrade safety. Review the Candid interface, migration behavior, and post-upgrade application state, and keep a recovery path. The EOP docs recommend testing upgrades and maintaining a way to recover data, such as controller-privileged data queries.
FAQ
Does Motoko 2.0 preserve variables in a bare actor?
Yes. Actor fields persist across upgrades by default. Add transient to each field that should be initialized again after an upgrade.
Can I switch a canister back from enhanced to classical persistence?
No. The classical-to-EOP move happens on the canister’s next upgrade using the Motoko 2.0 transition flag and is irreversible. Later upgrades need no transition flag.
Which check should I run before deploying?
The moc v1-to-v2 guide documents mops check-stable and moc --stable-compatible old.most new.most. Run the project’s check, review Candid compatibility, then deploy with ICP CLI’s explicit upgrade mode.
Get the wire in your inbox
Every new signal, straight from the generator. No noise, unsubscribe anytime.


