Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
dfinity avatar

Migrating Motoko

  • 67 installs
  • 28 repo stars
  • Updated August 4, 2026
  • dfinity/icskills

Migrate Motoko canister actor state across an upgrade using the inline `(with migration = ...)` syntax for field renames or type changes.

About

Handles inline actor state migration for Motoko canisters using the `(with migration = ...)` syntax when renaming fields, changing types, or restructuring state on upgrade. A developer uses it for a single one-shot migration without the enhanced-migration flag.

  • Migrates actor state with `(with migration = ...)` inline syntax
  • Distinguishes implicit compatible upgrades from explicit migrations for renames/type changes

Migrating Motoko by the numbers

  • 67 all-time installs (skills.sh)
  • Ranked #183 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dfinity/icskills --skill migrating-motoko

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs67
repo stars28
Last updatedAugust 4, 2026
Repositorydfinity/icskills

What it does

Migrate Motoko canister actor state across an upgrade using the inline `(with migration = ...)` syntax for field renames or type changes.

Files

SKILL.mdMarkdownGitHub ↗

Inline Actor Migration

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.

When to Use

Implicit migration (no code needed)

The runtime allows the upgrade if the new program is compatible with the old:

  • Adding actor fields
  • Removing actor fields
  • Changing mutability (varlet)
  • Adding variant constructors
  • Widening types (NatInt)

Explicit migration required

  • Renaming fields
  • Changing a field's type (e.g. Bool → variant, IntFloat)
  • Restructuring state (splitting/merging fields)
  • Transforming collection values

Syntax

Parenthetical 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 { ... };

Migration Function Rules

  • Type: func (old : { ... }) : { ... } — local, non-generic, both records must use persistable types (no functions or mutable arrays)
  • Domain: old actor fields (names and types from the previous version)
  • Codomain: new actor fields (must exist in the new actor with compatible types)
  • Runs only on upgrade — on fresh install, initializers run normally
  • If the migration traps, the upgrade is aborted and the canister stays on the old version

Field semantics

Field appears inEffect
Input and outputField is transformed
Output onlyNew field produced by migration
Input onlyField consumed (compiler warns about possible data loss)
NeitherCarried through or initialized by declaration

Migration Module Pattern

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 Map "mo:core/Map";
import Types "types";
import Migration "migration";

(with migration = Migration.run)
actor {
  var tasks = Map.empty<Nat, Types.Task>();
  var nextId : Nat = 0;
};

Fields must have initializers — the migration function runs only on upgrade. On fresh install the initializers are used.

Common Patterns

Add field with default

old.users.map<Nat, OldUser, NewUser>(
  func(_, u) { { u with zipCode = "" } }
)

Add optional field

{ task with var assignee = null : ?Principal }

Bool to variant

var status = if (task.completed) #completed else #pending;

Rename a field

Consume old name, produce new name:

func(old : { var state : Int }) : { var value : Int } {
  { var value = old.state }
}

Drop a field

Consume it in the input, omit from output. Compiler warns — ensure the loss is intentional.

Checklist

  • [ ] Decide: implicit (compatible change) or explicit (migration function)
  • [ ] If explicit: define old types inline in migration.mo
  • [ ] Migration type: func (old : RecordIn) : RecordOut with persistable types
  • [ ] Attach with (with migration = Migration.run) before the actor
  • [ ] Do not use preupgrade/postupgrade for data migration
  • [ ] Verify with mops check --fix and mops build

Additional References

  • Load motoko for general Motoko language reference and mo:core APIs
  • Load migrating-motoko-enhanced for multi-migration with --enhanced-migration
  • Load mops-cli for mops check, mops build, and toolchain setup

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.