Inline actor migration with (with migration = ...). Use when upgrading canister state, changing field types, or writing migration functions without the --enhanced-migration flag.
Migrate actor state across canister upgrades using a migration expression attached to the actor. Each upgrade has at most one migration function.
For multi-migration with a migrations/ directory, load migrating-motoko-enhanced instead.
The runtime allows the upgrade if the new program is compatible with the old:
var ↔ let)Nat → Int)Bool → variant, Int → )FloatParenthetical expression immediately before the actor:
import Migration "migration";
(with migration = Migration.run)
actor {
var newState : Float = 0.0;
};
Or inline:
import Int "mo:core/Int";
(with migration = func(old : { var state : Int }) : { var newState : Float } {
{ var newState = old.state.toFloat() }
})
actor {
var newState : Float = 0.0;
};
Or using the shorthand when the imported module exports a migration field:
import { migration } "migration";
(with migration)
actor { ... };
func (old : { ... }) : { ... } — local, non-generic, both records must use persistable types (no functions or mutable arrays)| Field appears in | Effect |
|---|---|
| Input and output | Field is transformed |
| Output only | New field produced by migration |
| Input only | Field consumed (compiler warns about possible data loss) |
| Neither | Carried through or initialized by declaration |
Keep migrations in a separate module. Define old types inline — do not import them from old code paths:
// migration.mo
import Types "types";
import Map "mo:core/Map";
module {
type OldTask = { id : Nat; title : Text; completed : Bool };
type OldActor = {
var tasks : Map.Map<Nat, OldTask>;
var nextId : Nat;
};
type NewActor = {
var tasks : Map.Map<Nat, Types.Task>;
var nextId : Nat;
};
public func run(old : OldActor) : NewActor {
let tasks = old.tasks.map<Nat, OldTask, Types.Task>(
func(_, task) {
{
id = task.id;
title = task.title;
due = 0;
var status = if (task.completed) #completed else #pending;
}
}
);
{ var tasks; var nextId = old.nextId };
};
};
// main.mo
import Migration "migration";
(with migration = Migration.run)
actor {
var tasks : Map.Map<Nat, Types.Task>;
var nextId : Nat = 0;
};
old.users.map<Nat, OldUser, NewUser>(
func(_, u) { { u with zipCode = "" } }
)
{ task with var assignee = null : ?Principal }
var status = if (task.completed) #completed else #pending;
Consume old name, produce new name:
func(old : { var state : Int }) : { var value : Int } {
{ var value = old.state }
}
Consume it in the input, omit from output. Compiler warns — ensure the loss is intentional.
migration.mofunc (old : RecordIn) : RecordOut with persistable types(with migration = Migration.run) before the actorpreupgrade/postupgrade for data migrationmops check --fix and mops buildwriting-motoko for general Motoko language referencemigrating-motoko-enhanced for multi-migration with --enhanced-migration