Motoko base to core migration guide
The core package is a new and improved standard library for Motoko, focusing on:
- AI-friendly design patterns.
- Familiarity coming from languages such as JavaScript, Python, Java, and Rust.
- Simplified usage of data structures in stable memory.
- Consistent naming conventions and parameter ordering.
This page provides a comprehensive guide for migrating from the base Motoko package to the new core package.
Project configuration
Add the following to your mops.toml file to begin using the core package:
[dependencies]
core = "0.0.0" # Check the latest version: https://mops.one/core
If you are migrating an existing project, you can keep the base import and gradually transition to using the new API.
Important considerations
The core package depends on new language features, so make sure to update to the latest dfx (0.28+) or Motoko compiler (0.15+) before migrating.
When updating to the core package:
- All data structures can now be stored in stable memory without the need for pre-upgrade/post-upgrade hooks, provided those data structures are instantiated at stable type arguments.
- range()functions in the- corelibrary are now exclusive rather than inclusive! Keep this in mind when replacing- Iter.range()with- Nat.range().
- Functions previously named vals()are renamed tovalues(). This also applies to fields. For example,array.vals()can be replaced witharray.values().
- Hash-based data structures are no longer included in the standard library. It is encouraged to use ordered maps and sets for improved security.
In some cases, it won't be possible to fully migrate to coredue to removal of some features inbase. In these cases, you can continue using both packages side-by-side or search for Mops packages built by the community.
For details on function signatures, please refer to the official documentation.
Also, feel free to ask for help by posting on the ICP developer forum or opening a GitHub issue on the dfinity/motoko-core repository.
Module changes
1. New modules
The following modules are new in the core package:
- List- Mutable list
- Map- Mutable map
- Queue- Mutable double-ended queue
- Set- Mutable set
- Runtime- Runtime utilities and assertions
- Tuples- Tuple utilities
- Types- Common type definitions
- VarArray- Mutable array operations
- pure/List- Immutable list (originally- mo:base/List)
- pure/Map- Immutable map (originally- mo:base/OrderedMap)
- pure/RealTimeQueue- Queue implementation with performance tradeoffs
- pure/Set- Immutable set (originally- mo:base/OrderedSet)
2. Renamed modules
| Base module | Core module | Notes | 
|---|---|---|
| ExperimentalCycles | Cycles | Stabilized module for cycle management | 
| ExperimentalInternetComputer | InternetComputer | Stabilized low-level ICP interface | 
| Deque | pure/Queue | Enhanced double-ended queue becomes immutable queue | 
| List | pure/List | Original immutable list moved to pure/namespace | 
| OrderedMap | pure/Map | Ordered map moved to pure/namespace | 
| OrderedSet | pure/Set | Ordered set moved to pure/namespace | 
The pure/ namespace contains immutable (purely functional) data structures where operations return new instances rather than modifying existing ones. This naming follows functional programming conventions and makes the distinction between mutable and immutable data structures explicit.
3. Removed modules
The following modules have been removed in the core package:
- AssocList- Use- Mapor- pure/Mapinstead
- Buffer- Use- Listor- VarArrayinstead
- ExperimentalStableMemory- Deprecated
- Hash- Vulnerable to hash collision attacks
- HashMap- Use- Mapor- pure/Map
- Heap
- IterType- Merged into- Typesmodule
- None
- Prelude- Merged into- Debugand- Runtime
- RBTree
- Trie
- TrieMap- Use- Mapor- pure/Mapinstead
- TrieSet- Use- Setor- pure/Setinstead
Modules like Random, Region, Time, Timer, and Stack still exist in core but with modified APIs.
Data structure improvements
The core package introduces a fundamental reorganization of data structures with a clear separation between mutable and immutable (purely functional) APIs. All data structures are now usable in stable memory.
| Structure | Module | Description | 
|---|---|---|
| List | List | Mutable list | 
| Map | Map | Mutable map | 
| Queue | Queue | Mutable queue | 
| Set | Set | Mutable set | 
| Array | Array | Immutable array | 
| VarArray | VarArray | Mutable array | 
| List | pure/List | Immutable list (originally mo:base/List) | 
| Map | pure/Map | Immutable map (originally mo:base/OrderedMap) | 
| Set | pure/Set | Immutable set (originally mo:base/OrderedSet) | 
| Queue | pure/Queue | Immutable queue  (orginally mo:base/Deque) | 
| RealTimeQueue | pure/RealTimeQueue | Real-time queue with constant-time operations | 
Interface changes by module
Array
Renamed functions
- append()→- concat()
- chain()→- flatMap()
- freeze()→- fromVarArray()
- init()→- repeat()with reversed argument order
- make()→- singleton()
- mapFilter()→- filterMap()
- slice()→- range()
- subArray()→- sliceToArray()
- thaw()→- toVarArray()
- vals()→- values()
New functions
- all()- Check if all elements satisfy predicate
- any()- Check if any element satisfies predicate
- compare()- Compare two arrays
- empty()- Create empty array
- enumerate()- Get indexed iterator
- findIndex()- Find index of first matching element
- forEach()- Apply function to each element
- fromIter()- Create array from iterator
- isEmpty()- Check if array is empty
- join()- Join arrays from iterator
- toText()- Convert array to text representation
Parameter order changes
- indexOf(element, array, equal)→- indexOf(array, equal, element)
- lastIndexOf(element, array, equal)→- lastIndexOf(array, equal, element)
- nextIndexOf(element, array, fromInclusive, equal)→- nextIndexOf(array, equal, element, fromInclusive)
- prevIndexOf(element, array, fromExclusive, equal)→- prevIndexOf(array, equal, element, fromExclusive)
Removed functions
- take()- Use- sliceToArray()instead
- sortInPlace()- Use- VarArray.sortInPlace()instead
- tabulateVar()- Use- VarArray.tabulate()instead
Blob
Modified functions
- fromArrayMut()→- fromVarArray()
- hash()- Return type changed from- Nat32to- Types.Hash
- toArrayMut()→- toVarArray()
New functions
- empty()- Create an empty blob (- "" : Blob)
- isEmpty()- Check if blob is empty
- size()- Get number of bytes in a blob (equivalent to- blob.size())
Bool
Renamed functions
- logand()→- logicalAnd()
- lognot()→- logicalNot()
- logor()→- logicalOr()
- logxor()→- logicalXor()
New functions
- allValues()- Iterator over all boolean values
Char
Renamed functions
- isLowercase()→- isLower()
- isUppercase()→- isUpper()
Debug
Added functions
- todo()- Replaces- Prelude.nyi()
Removed functions
- trap()- Moved to- Runtime.trap()
Float
Modified functions
- equal()- Now requires epsilon parameter
- notEqual()- Now requires epsilon parameter
Removed functions
- equalWithin(),- notEqualWithin()- Use- equal()and- notEqual()with epsilon
Iter
Iter.range() has been removed in favor of type-specific range functions such as Nat.range(), Int.range(), Nat32.range(), etc. These functions have an exclusive upper bound, in contrast to the original inclusive upper bound of Iter.range(). 
import Int "mo:base/Int";
import Debug "mo:base/Debug";
persistent actor {
  // Iterate through -3, -2, -1, 0, 1, 2 (exclusive upper bound)
  for (number in Int.range(-3, 3)) {
    Debug.print(debug_show number);
  };
  // Iterate through -3, -2, -1, 0, 1, 2, 3
  for (number in Int.rangeInclusive(-3, 3)) {
    Debug.print(debug_show number);
  };
}
rangeInclusive() is included for use cases with an inclusive upper bound. The original Iter.range() corresponds to Nat.rangeInclusive().
Helper functions have been added, such as allValues(), for each finite type in the base package.
Int
New functions
- fromNat()- Convert Nat to Int
- fromText()- Parse Int from text
- range()- Create iterator over range
- rangeBy()- Create iterator with step
- rangeByInclusive()- Inclusive range with step
- rangeInclusive()- Inclusive range
- toNat()- Convert Int to Nat (safe conversion)
Modified functions
- fromText()- Now returns- nullinstead of- ?0for the inputs "+" and "-"
Removed functions
- hash()
- hashAcc()
Nat
New functions
- allValues()- Iterator over all natural numbers
- bitshiftLeft()/- bitshiftRight()- Bit shifting operations
- fromInt()- Safe conversion from Int
- fromText()- Parse Nat from text
- range(),- rangeInclusive()- Range iterators
- rangeBy(),- rangeByInclusive()- Range with step
- toInt()- Convert to Int
Int8, Int16, Int32, Int64, Nat8, Nat16, Nat32, Nat64
Renamed fields
- maximumValue→- maxValue
- minimumValue→- minValue
New functions
- allValues()- Iterator over all values in range
- range(),- rangeInclusive()- Range iterators (replaces- Iter.range())
- explode()- Slice into constituent bytes (only for sizes- 16,- 32,- 64)
Option
Renamed functions
- make()→- some()- Create option from value
- iterate()→- forEach()- Apply function to option value
New functions
- compare()- Compare two options
- toText()- Convert option to text representation
Removed functions
- assertNull()- Removed in favor of pattern matching
- assertSome()- Removed in favor of pattern matching
Order
New functions
- allValues()- Iterator over all order values (- #less,- #equal,- #greater)
Random
The Random module has been completely redesigned in the core package with a new API that provides better control over random number generation and supports both pseudo-random and cryptographic random number generation.
import Random "mo:core/Random";
persistent actor {
  transient let random = Random.crypto();
  public func main() : async () {
    let coin = await* random.bool(); // true or false
    let byte = await* random.nat8(); // 0 to 255
    let number = await* random.nat64(); // 0 to 2^64
    let numberInRange = await* random.natRange(0, 10); // 0 to 9
  }
}
New classes
- Random- Synchronous pseudo-random number generator for simulations and testing
- AsyncRandom- Asynchronous cryptographic random number generator using ICP entropy
Class methods
- bool()- Random choice between- trueand- false
- nat8()- Random- Nat8value in range- [0, 256)
- nat64()- Random- Nat64value in range- [0, 2^64)
- nat64Range(from, to)- Random- Nat64in range- [from, to)
- natRange(from, to)- Random- Natin range- [from, to)
- intRange(from, to)- Random- Intin range- [from, to)
New functions
- emptyState()- Initialize empty random number generator state
- seedState()- Initialize pseudo-random state with 64-bit seed
- seed()- Create pseudo-random generator from seed
- seedFromState()- Create pseudo-random generator from state
- crypto()- Create cryptographic random generator using ICP entropy
- cryptoFromState()- Create cryptographic generator from state
Result
New functions
- all()- Check all results in iterator
- any()- Check any result satisfies predicate
- forOk()- Apply function to- #okvalue
- forErr()- Apply function to- #errvalue
- fromBool()- Create Result from boolean
Text
Renamed functions
- toLowercase()→- toLower()
- toUppercase()→- toUpper()
- translate()→- flatMap()
New functions
- isEmpty()- Check if text is empty
- reverse()- Swap the order of characters
- toText()- Identity function
Removed functions
- hash()
- fromList()- Use- fromIter()with list iterator instead
- toList()- Use- toIter()and convert to list if needed
Data structure migration examples
This section provides detailed migration examples showing how to convert common data structures from the base package to the core package. Each example demonstrates:
- Original implementation using the basepackage with pre/post-upgrade hooks
- Updated implementation using the corepackage with automatic stable memory support
- Migration pattern using the new with migrationsyntax for seamless data structure conversion
The new migration pattern allows you to automatically convert existing stable data from base package structures to core package structures during canister upgrades. The migration function runs once during the first upgrade and the converted data becomes the new stable state.
Understanding the migration pattern
The with migration syntax follows this structure:
(
  with migration = func(
    state : { /* old stable state types */ }
  ) : { /* new stable state types */ } = {
    /* conversion logic */
  }
)
actor App {
  // New stable declarations
};
This pattern ensures that existing stable data is preserved and converted to the new format during canister upgrades.
Buffer
Original (base)
import Buffer "mo:base/Buffer";
actor {
  type Item = Text;
  stable var items : [Item] = [];
  let buffer = Buffer.fromArray<Item>(items);
  system func preupgrade() {
    items := Buffer.toArray(buffer);
  };
  system func postupgrade() {
    items := [];
  };
  public func add(item : Item) : async () {
    buffer.add(item);
  };
  public query func getItems() : async [Item] {
    Buffer.toArray(buffer);
  };
};
Updated (core)
import List "mo:core/List";
(
  with migration = func(
    state : {
      var items : [App.Item];
    }
  ) : {
    list : List.List<App.Item>;
  } = {
    list = List.fromArray(state.items);
  }
)
actor App {
  public type Item = Text; // `public` for migration
  stable let list = List.empty<Item>();
  public func add(item : Item) : async () {
    List.add(list, item);
  };
  public query func getItems() : async [Item] {
    List.toArray(list);
  };
};
Deque
Original (base)
import Deque "mo:base/Deque";
actor {
  type Item = Text;
  stable var deque = Deque.empty<Item>();
  public func put(item : Item) : async () {
    deque := Deque.pushBack(deque, item);
  };
  public func take() : async ?Item {
    switch (Deque.popFront(deque)) {
      case (?(item, newDeque)) {
        deque := newDeque;
        ?item;
      };
      case null { null };
    };
  };
};
Updated (core)
import Deque "mo:base/Deque"; // For migration
import Queue "mo:core/Queue";
(
  with migration = func(
    state : {
      var deque : Deque.Deque<App.Item>;
    }
  ) : {
    queue : Queue.Queue<App.Item>;
  } {
    let queue = Queue.empty<App.Item>();
    label l loop {
      switch (Deque.popFront(state.deque)) {
        case (?(item, deque)) {
          Queue.pushBack(queue, item);
          state.deque := deque;
        };
        case null {
          break l;
        };
      };
    };
    { queue };
  }
) actor App {
  public type Item = Text; // `public` for migration
  stable let queue = Queue.empty<Item>();
  public func put(item : Item) : async () {
    Queue.pushBack(queue, item);
  };
  public func take() : async ?Item {
    Queue.popFront(queue);
  };
};
HashMap
Original (base)
import HashMap "mo:base/HashMap";
import Text "mo:base/Text";
import Iter "mo:base/Iter";
actor {
  stable var mapEntries : [(Text, Nat)] = [];
  let map = HashMap.fromIter<Text, Nat>(mapEntries.vals(), 10, Text.equal, Text.hash);
  system func preupgrade() {
    mapEntries := Iter.toArray(map.entries());
  };
  system func postupgrade() {
    mapEntries := [];
  };
  public func update(key : Text, value : Nat) : async () {
    map.put(key, value);
  };
  public func remove(key : Text) : async ?Nat {
    map.remove(key);
  };
  public query func getItems() : async [(Text, Nat)] {
    Iter.toArray(map.entries());
  };
};
Updated (core)
import Map "mo:core/Map";
import Text "mo:core/Text";
import Iter "mo:core/Iter";
(
  with migration = func(
    state : {
      var mapEntries : [(Text, Nat)];
    }
  ) : {
    map : Map.Map<Text, Nat>;
  } = {
    map = Map.fromIter(state.mapEntries.vals(), Text.compare);
  }
)
actor {
  stable let map = Map.empty<Text, Nat>();
  public func update(key : Text, value : Nat) : async () {
    Map.add(map, Text.compare, key, value);
  };
  public func remove(key : Text) : async ?Nat {
    Map.take(map, Text.compare, key);
  };
  public query func getItems() : async [(Text, Nat)] {
    Iter.toArray(Map.entries(map));
  };
};
OrderedMap
Original (base)
import OrderedMap "mo:base/OrderedMap";
import Text "mo:base/Text";
import Iter "mo:base/Iter";
actor {
  let textMap = OrderedMap.Make<Text>(Text.compare);
  stable var map = textMap.empty<Nat>();
  public func update(key : Text, value : Nat) : async () {
    map := textMap.put(map, key, value);
  };
  public func remove(key : Text) : async ?Nat {
    let (newMap, removedValue) = textMap.remove(map, key);
    map := newMap;
    removedValue;
  };
  public query func getItems() : async [(Text, Nat)] {
    Iter.toArray(textMap.entries(map));
  };
};
Updated (core)
import OrderedMap "mo:base/OrderedMap"; // For migration
import Map "mo:core/Map";
import Text "mo:core/Text";
import Iter "mo:core/Iter";
(
  with migration = func(
    state : {
      var map : OrderedMap.Map<Text, Nat>;
    }
  ) : {
    map : Map.Map<Text, Nat>;
  } {
    let compare = Text.compare;
    let textMap = OrderedMap.Make<Text>(compare);
    let map = Map.fromIter<Text, Nat>(textMap.entries(state.map), compare);
    { map };
  }
)
actor {
  stable let map = Map.empty<Text, Nat>();
  public func update(key : Text, value : Nat) : async () {
    Map.add(map, Text.compare, key, value);
  };
  public func remove(key : Text) : async ?Nat {
    Map.take(map, Text.compare, key);
  };
  public query func getItems() : async [(Text, Nat)] {
    Iter.toArray(Map.entries(map));
  };
};
OrderedSet
Original (base)
import OrderedSet "mo:base/OrderedSet";
import Text "mo:base/Text";
import Iter "mo:base/Iter";
actor {
  type Item = Text;
  let textSet = OrderedSet.Make<Item>(Text.compare);
  stable var set = textSet.empty();
  public func add(item : Item) : async () {
    set := textSet.put(set, item);
  };
  public func remove(item : Item) : async Bool {
    let oldSize = textSet.size(set);
    set := textSet.delete(set, item);
    oldSize > textSet.size(set);
  };
  public query func getItems() : async [Item] {
    Iter.toArray(textSet.vals(set));
  };
};
Updated (core)
import OrderedSet "mo:base/OrderedSet"; // For migration
import Set "mo:core/Set";
import Text "mo:core/Text";
import Iter "mo:core/Iter";
(
  with migration = func(
    state : {
      var set : OrderedSet.Set<App.Item>;
    }
  ) : {
    set : Set.Set<App.Item>;
  } {
    let compare = Text.compare;
    let textSet = OrderedSet.Make<App.Item>(compare);
    let set = Set.fromIter(textSet.vals(state.set), compare);
    { set };
  }
)
actor App {
  public type Item = Text; // `public` for migration
  stable let set = Set.empty<Item>();
  public func add(item : Item) : async () {
    Set.add(set, Text.compare, item);
  };
  public func remove(item : Item) : async Bool {
    Set.delete(set, Text.compare, item);
  };
  public query func getItems() : async [Item] {
    Iter.toArray(Set.values(set));
  };
};
Trie
Original (base)
import Trie "mo:base/Trie";
import Text "mo:base/Text";
import Iter "mo:base/Iter";
actor {
  type Key = Text;
  type Value = Nat;
  stable var trie : Trie.Trie<Key, Value> = Trie.empty();
  public func update(key : Key, value : Value) : async () {
    let keyHash = Text.hash(key);
    trie := Trie.put(trie, { key = key; hash = keyHash }, Text.equal, value).0;
  };
  public func remove(key : Key) : async ?Value {
    let keyHash = Text.hash(key);
    let (newTrie, value) = Trie.remove(trie, { key = key; hash = keyHash }, Text.equal);
    trie := newTrie;
    value;
  };
  public query func getItems() : async [(Key, Value)] {
    Iter.toArray(Trie.iter(trie));
  };
};
Updated (core)
import Trie "mo:base/Trie"; // For migration
import Map "mo:core/Map";
import Text "mo:core/Text";
import Iter "mo:core/Iter";
(
  with migration = func(
    state : {
      var trie : Trie.Trie<Text, Nat>;
    }
  ) : {
    map : Map.Map<Text, Nat>;
  } = {
    map = Map.fromIter(Trie.iter(state.trie), Text.compare);
  }
)
actor {
  stable let map = Map.empty<Text, Nat>();
  public func update(key : Text, value : Nat) : async () {
    Map.add(map, Text.compare, key, value);
  };
  public func remove(key : Text) : async ?Nat {
    Map.take(map, Text.compare, key);
  };
  public query func getItems() : async [(Text, Nat)] {
    Iter.toArray(Map.entries(map));
  };
};
TrieMap
Original (base)
import TrieMap "mo:base/TrieMap";
import Text "mo:base/Text";
import Iter "mo:base/Iter";
actor {
  stable var mapEntries : [(Text, Nat)] = [];
  let map = TrieMap.fromEntries<Text, Nat>(mapEntries.vals(), Text.equal, Text.hash);
  system func preupgrade() {
    mapEntries := Iter.toArray(map.entries());
  };
  system func postupgrade() {
    mapEntries := [];
  };
  public func update(key : Text, value : Nat) : async () {
    map.put(key, value);
  };
  public func remove(key : Text) : async ?Nat {
    map.remove(key);
  };
  public query func getItems() : async [(Text, Nat)] {
    Iter.toArray(map.entries());
  };
};
Updated (core)
import Map "mo:core/Map";
import Text "mo:core/Text";
import Iter "mo:core/Iter";
(
  with migration = func(
    state : {
      var mapEntries : [(Text, Nat)];
    }
  ) : {
    map : Map.Map<Text, Nat>;
  } = {
    map = Map.fromIter(state.mapEntries.values(), Text.compare);
  }
)
actor {
  stable let map = Map.empty<Text, Nat>();
  public func update(key : Text, value : Nat) : async () {
    Map.add(map, Text.compare, key, value);
  };
  public func remove(key : Text) : async ?Nat {
    Map.take(map, Text.compare, key);
  };
  public query func getItems() : async [(Text, Nat)] {
    Iter.toArray(Map.entries(map));
  };
};
TrieSet
Original (base)
import TrieSet "mo:base/TrieSet";
import Text "mo:base/Text";
actor {
  type Item = Text;
  stable var set : TrieSet.Set<Item> = TrieSet.empty<Item>();
  public func add(item : Item) : async () {
    set := TrieSet.put<Item>(set, item, Text.hash(item), Text.equal);
  };
  public func remove(item : Item) : async Bool {
    let contained = TrieSet.mem<Item>(set, item, Text.hash(item), Text.equal);
    set := TrieSet.delete<Item>(set, item, Text.hash(item), Text.equal);
    contained;
  };
  public query func getItems() : async [Item] {
    TrieSet.toArray(set);
  };
};
Updated (core)
import Set "mo:core/Set";
import Text "mo:core/Text";
import Iter "mo:core/Iter";
import TrieSet "mo:base/TrieSet";
(
  with migration = func(
    state : {
      var set : TrieSet.Set<Text>;
    }
  ) : {
    set : Set.Set<Text>;
  } = {
    set = Set.fromIter(TrieSet.toArray(state.set).vals(), Text.compare);
  }
)
actor App {
  public type Item = Text; // `public` for migration
  stable let set = Set.empty<Item>();
  public func add(item : Item) : async () {
    Set.add(set, Text.compare, item);
  };
  public func remove(item : Item) : async Bool {
    Set.delete(set, Text.compare, item);
  };
  public query func getItems() : async [Item] {
    Iter.toArray(Set.values(set));
  };
};
Troubleshooting
Version compatibility errors
If you encounter errors like field Array_tabulateVar does not exist in module, this indicates a version mismatch between your Motoko compiler and the core package. 
Solution:
2. Ensure you're using the latest Motoko compiler version
3. Update the core package to the latest version in your mops.toml
4. Clean and rebuild your project: dfx stop && dfx start --clean
Migration issues
If you experience issues with the migration pattern:
- Ensure your project structure follows the new with migrationsyntax exactly
- Verify that all types referenced in the migration function are accessible (marked as publicif needed)
- Test the migration incrementally by converting one data structure at a time
For additional help, visit the ICP developer forum or Discord community.