Skip to main content
Read and understand all of this page before running a migration on a live chain.
Synopsis In-place store migrations let modules ship breaking state changes during a chain upgrade. This page covers how an upgrade runs, setting up x/upgrade, writing and registering migrations, running them in an upgrade handler, and a worked example.
The Cosmos SDK supports two approaches to chain upgrades: exporting the entire application state to JSON and starting fresh with a modified genesis file, or performing in-place store migrations that update state directly. In-place migrations are significantly faster for chains with large state and are the standard approach for live networks. This page covers the in-place approach.

How an upgrade works

An upgrade is scheduled on-chain, usually through governance, and runs in this order:
  1. Someone submits a governance proposal containing a MsgSoftwareUpgrade whose Plan names the upgrade (matching the name passed to SetUpgradeHandler) and sets a target height.
  2. Validators vote. If the proposal passes, x/upgrade records the plan.
  3. At the plan height every node halts and writes upgrade-info.json to its home directory.
  4. The node operator, or Cosmovisor, starts the new binary. During the next block’s PreBlock, x/upgrade sees the due plan and runs the registered upgrade handler, which calls RunMigrations.
  5. The chain continues on the new binary with migrated state.
A Plan is the on-chain upgrade record: a name and a target height. The VersionMap is a map of module name to consensus version, stored by x/upgrade, recording the version each module’s state was last migrated to. The sections below cover each piece. The Cosmovisor guide covers the node-operator side.

Consensus version

Successful upgrades of existing modules require each AppModule to implement the function ConsensusVersion() uint64.
  • The versions must be hard-coded by the module developer.
  • The initial version must be set to 1.
Consensus versions serve as state-breaking versions of app modules and must be incremented when the module introduces breaking changes. RunMigrations compares these against the VersionMap to decide which migrations to run.

Set up x/upgrade

The rest of this page assumes the app has x/upgrade wired in. Create the UpgradeKeeper before the module manager so the upgrade module can be registered, register the module, and run its PreBlocker. See the full wiring in the cosmos/example app.
The PreBlocker method runs the module manager’s PreBlock, which is where x/upgrade detects a due plan and executes its handler:
x/upgrade only runs during PreBlock. If SetPreBlocker is not wired to a PreBlocker that calls the module manager’s PreBlock, upgrade plans never execute and no migrations run.
Also save the consensus version of each module to state at genesis, so future upgrades can detect when modules with newer consensus versions are introduced. Add this to InitChainer:

Registering migrations

To register the functionality that takes place during a module upgrade, register the migrations in the Configurator using its RegisterMigration method, from the AppModule’s RegisterServices method. Register migrations in increasing order, one per source version, up to the target consensus version. For example, to migrate to version 3 of a module, register migrations for versions 1 and 2:
The migration functions need access to the keeper’s store, so they are defined as methods on a Migrator that wraps the keeper. The next section writes one.

Writing migration scripts

A migration reads the module’s existing state and rewrites it into the new layout. Place migration functions in the module’s keeper package, or in a versioned migrations/ directory for larger modules (for example x/bank/migrations/v2). The following Migrator moves the counter module from consensus version 1 to 2. The breaking change is a re-denomination: every stored count is multiplied by 10. Migrate1to2 reads the current value, transforms it, and writes it back, treating an unset value as a no-op rather than an error:
Register this migration with RegisterMigration(types.ModuleName, 1, m.Migrate1to2) as shown in the previous section. For larger modules, keep the transformation in a versioned package so the Migrator method stays a thin wrapper. The bank module follows this pattern:
For a production example that manipulates raw KV store keys, see migrateBalanceKeys. This code updated bank addresses to be prefixed by their length in bytes as outlined in ADR-028.

Running migrations in the app

Once modules have registered their migrations, the app runs them inside an UpgradeHandler. The upgrade handler type is:
As of Cosmos SDK v0.54, x/upgrade is part of the main SDK module. Import it from github.com/cosmos/cosmos-sdk/x/upgrade, not the standalone cosmossdk.io/x/upgrade module, which is not compatible with v0.54.
The handler receives the VersionMap stored by x/upgrade (reflecting the consensus versions from the previous binary), performs any additional upgrade logic, and must return the updated VersionMap from RunMigrations. Register the handler in app.go:
RunMigrations iterates over all registered modules in order, checks each module’s version in the VersionMap, and runs all registered migration scripts for modules whose consensus version has increased. The updated VersionMap is returned to the upgrade keeper, which persists it in the x/upgrade store.

Order of migrations

By default, migrations run in alphabetical order by module name, with one exception: x/auth runs last due to state dependencies with other modules (see cosmos/cosmos-sdk#10591). To change the order, call app.ModuleManager.SetOrderMigrations(module1, module2, ...) in app.go. The function panics if any registered module is omitted.

Adding new modules during an upgrade

New modules are recognized because they have no entry in the x/upgrade VersionMap store. RunMigrations calls InitGenesis for them automatically. If you need to add stores for a new module, configure the store loader before the upgrade runs. The loader reads upgrade-info.json at startup and adds the store at the upgrade height:
Configure the store loader in the app constructor, before LoadLatestVersion runs, so the new store is mounted when the store loads. To skip InitGenesis for a new module (for example, if you are manually initializing state in the handler), set its version in fromVM before calling RunMigrations:

Overwriting genesis functions

The SDK provides modules that app developers can import, and those modules often already have an InitGenesis function. If you want to run a custom genesis function for one of those modules during an upgrade instead of the default one, you must both call your custom function in the handler AND manually set that module’s consensus version in fromVM. Without the second step, RunMigrations will run the module’s existing InitGenesis even though you already initialized it.
You must manually set the consensus version in fromVM for any module whose InitGenesis you are overriding. If you don’t, the SDK will call the module’s default InitGenesis in addition to your custom one.

Counter module upgrade example

The counter module is built step by step in the example chain tutorial, and its source lives in the cosmos/example repository. This example continues from that module: it bumps the counter to consensus version 2 and runs the migration shown earlier during an upgrade named my-plan.
  1. Increment the module’s ConsensusVersion so the SDK detects that its state layout changed:
  2. In RegisterServices, wrap the keeper in a Migrator and register the version 1 to 2 migration:
  3. In app.go, register a handler for the plan that calls RunMigrations:
  4. Schedule the my-plan upgrade through governance. When the chain reaches the plan height it halts, the node operator starts the new binary, and the handler runs the counter migration during PreBlock. See How an upgrade works.

Syncing a full node to an upgraded blockchain

A full node joining an already-upgraded chain must start from the initial binary that the chain used at genesis and replay all historical upgrades. If all upgrade plans include binary download instructions, Cosmovisor’s auto-download mode handles this automatically. Otherwise, you must provide each historical binary manually. See the Cosmovisor guide for setup and configuration.