Original post at DEV.
I’ve published this comprehensive guide that accumulates the learnings of multiple months of upgrading the Polkadot SDK on multiple runtimes. It also aggregates guides that are already here in the forum, but on external sources as well. Enjoy, and if you have any comment or observation, just let me know here.
Note: This tutorial focuses on MacOS and Linux development environments. If you are using Windows, please consider running Linux under WSL or having a dedicated Linux distro on your computer.
Polkadot SDK is one of the most advanced tooling sets for writing a blockchain that works. Whether you run a solo chain or a parachain part of the Polkadot network, the SDK is a Swiss knife that you can find helpful in building a functioning blockchain.
However, blockchains are not set in stone. Eventually, you’ll need to upgrade to the next version to get the latest updates and the best features or fix potential vulnerabilities that might have been found since your last upgrade.
This article serves as a guide to almost infallibly migrate your runtime between your current Polkadot SDK version and the latest available version.
Understanding Polkadot SDK versioning
When we talk about Polkadot versioning, we find two versioning conventions.
The stable convention follows stableYYMM-PATCH
, where YYMM
is a short date format (YY/MM) marking the month when this version was initially published. Polkadot SDK versions are published around the end of every quarter, and PATCH
versions are released monthly.
The semver convention follows 1.XX.PATCH
. In this case, the minor version is tied to a stable release, and PATCH
is the same patch version, defaulting to 0
if it’s the initial release.
There are other variations of the versioning (like pre-releases and RC releases, but we’re not interested in these right now).
You can find more information about the release schedule for Polkadot SDK here.
Determine the PSDK version you plan to update
The above is just a hint to help you understand which version you might be in and which one you plan to update.
Ideally, you’ll want to update to the latest version available, as you’ll immediately get all the latest goodies and security fixes. However, this is not generally recommended, as you’ll deal with huge changes.
The best advice is to upgrade one stable version at a time. This is the safest way to prevent unwanted behaviours due to bumping multiple versions while incorrectly handling migrations between versions.
There’s one big exception to this: when your current version and the following ones are past the End of Life.
Here are some quick steps on how to perform the usual upgrades. Look them up in the future, and go further to check on details if you forgot something:
Updating to your latest patch version
This is by far the most straightforward process. No breaking changes expected (typically), no XCM version updates, no changes in
- Determine your current stable version.
- Read the Release Notes to know what changed between patch versions.
- Upgrade dependencies to the latest patch of the stable version.
- Sometimes, you might need to adjust some configuration changes.
Optionally, you can try-runtime
and run benchmarks, but this is not usually expected on patch releases. These are expected to be non-breaking, and changes tend to be minimal, mostly fixes and/or reversions.
Updating to your next stable version
- Determine your next stable version.
- Read the Release Notes to know what changed between patch versions.
- Upgrade dependencies to the latest patch of the stable version.
- You must adjust some configuration changes.
- (Optional) If you use XCM types in your storage configuration, you must review possible XCM migrations. Coding a custom migration script might be necessary if these are unavailable in the XCM crates or elsewhere.
try-runtime
to check the needed migrations.- Once you do that, check whether the pallet needing migration has a migration script.
- You don’t need to run benchmarks using the reference benchmarks provided by the pallet crate. Otherwise, run benchmarks to get updated weights.
Updating to the latest stable version
Repeat the process above until you reach the latest stable version. Then, follow the first process if patch releases exist for that stable version.
Reading the Release Notes
An important part of upgrading is reading the Release Notes. Each note summarises the changelog between two versions of the Polkadot SDK and includes a list of changes for different audiences. The audiences[1] are:
Audience | Description |
---|---|
Node Dev | Those who build around the client side code. Alternative client builders, SMOLDOT, those who consume RPCs. These are people who are oblivious to the runtime changes. They only care about the meta-protocol, not the protocol itself. |
Runtime Dev. | All of those who rely on the runtime. A parachain team that is using a pallet. A DApp that is using a pallet. These are people who care about the protocol (WASM), not the meta-protocol (client). |
Node Operator | Those who don’t write any code and only run code. |
Runtime User | Anyone using the runtime. This can be a token holder or a dev writing a front end for a chain. |
In our case, you should be mainly interested in the updates regarding Runtime Dev. If you run a custom node, and can’t execute your blockchain using the omni-node
(maybe because you use a non-standard consensus algorithm), also Node Dev.
Hint If you’ve been operating a blockchain that operates in a standard way (i.e. using consensus algorithms like AURA or GRANDPA), I strongly recommend you use the
polkadot
(for operating solochains) orpolkadot-parachain
(for operating parachains). This will reduce the code you must maintain and the time required to migrate further.
Ultimately, check out Node Operator for updates regarding infrastructure operators and your DevOps teams. And Runtime User to see if your dApps and client applications need upgrades.
Using psvm
to manage upgrading the dependencies
The Polkadot SDK is a set of thousands of crates comprising Substrate (the bare bones), FRAME (to construct the runtime logic), Cummulus and Polkadot (to interact with Polkadot).
This means that every crate is versioned differently, and there’s no easy way to determine which crate version is related to which SDK version.
An exception to this is the umbrella crate (
polkadot-sdk
) for which versioning is aligned to the stable versioning (i.e.v2503.0.0
points tostable2503
) starting with the latest published in March 2025. The easiest way to migrate is just usingpolkadot-sdk
as the only dependency, which we’ll cover in a follow-up post.
Once you’ve determined your version and plan to upgrade, we’ll use psvm
(short for Polkadot SDK Version Manager), a CLI tool designed to facilitate this process.
Follow the steps to install it on your machine, and once you’re ready, go to your project, then run psvm
to upgrade your project’s dependencies to the latest version:
psvm -v stableYYMM-ZZ
If your dependencies are correctly configured, you should be able to see how they change versions.
Adjusting your runtime configuration
The next step is to adjust the configuration changes. Multiple strategies can achieve this: we can run a checklist, use cargo check,
or both.
Run a checklist
- Make a list of the pallet setups in your runtime.
- Compare with the list of changes defined by the Release Notes of the SDK version you aim to upgrade to.
By doing this, you’ll be able to build a checklist of pallets that might require configuration adjustments or changes.
The next step is locating a reference configuration for each pallet. Depending on which pallets you’re trying to configure, you can check the configuration for either Westend Asset Hub or Westend runtime.
Finally, you’ll be able to spot the configuration differences that need to be adjusted for each configuration trait associated with your Runtime.
Use cargo check
If you use workspaces, point cargo check
directly to your project. Run:
cargo +nightly-2025-01-30 check -p your-chain-runtime
This approach is helpful as it usually tracks the configuration changes first. However, it must be used carefully, often in conjunction with the checklist approach, to get the best results. Sometimes, as trait bounds are not met for some outer enums of the runtime, the whole runtime construction might fail, leaving a trail of hundreds of errors that are just the same thing (trait bounds not met), which can add an extra unnecessary burden.
Cargo checks with a checklist
The best approach is to have a list of config traits to adjust, aided by cargo check
. You can achieve this by letting an IDE (like RustRover, or any code editor with rust-analyzer
) highlight the configuration errors for you, while knowing where to look for (in runtimes, config traits are usually placed in a known place). This is one of the most useful, giving you a better workflow.
Including new goodies
Now that you have adjusted the configuration, it’s time to check for new goodies. Each Polkadot SDK version usually introduces new features, such as new pallets, improvements in UX, and better extensions.
Read your desired SDK version’s Release Notes for better information about these changes.
Ensuring migrations and try-runtime
This is a necessary step, as preserving the integrity of your runtime storage ensures that no unintended behaviours or issues occur.
First, install try-runtime-cli
. You’ll need to figure out the latest release, then run the following command:
cargo install --git https://github.com/paritytech/try-runtime-cli --tag vX.Y.Z --locked
Alternatively, download the latest binary from the Releases page on the try-runtime
repository.
# Use `try-runtime-x86_64-unknown-linux-musl` if on Linux,
# or `try-runtime-x86_64-apple-darwin` otherwise if on Mac.
curl -o try-runtime -L https://github.com/paritytech/try-runtime-cli/releases/download/v0.8.0/try-runtime-x86_64-unknown-linux-musl
chmod +x ./try-runtime
./try-runtime --version
Also, you’ll need to build your runtime using the try-runtime
feature.
cargo build --release --features try-runtime -p my-runtime
Once built, you’d be able to use try-runtime-
. The most common way to use try-runtime
is to test a runtime upgrade, like this:
./try-runtime \
--runtime target/release/wbuild/my-runtime/my-runtime.wasm \
on-runtime-upgrade \
live --uri wss://your-chain-node.example.com
This should hint at which migrations need to be executed to preserve the integrity of your chain’s storage. FRAME pallets often come with VersionedMigration
s whenever there are breaking changes in the storage types they support. Importing them to your runtime is usually as simple as declaring a Migrations
type (a tuple, or a tuple of tuples) that you’ll attach to your runtime’s Executive
handler.
This is an example of a runtime that requires multiple migrations:
/// A list of migrations that need to undergo.
pub type Migrations = (
// Unreleased
pallet_collator_selection::migration::v2::MigrationToV2<Runtime>,
pallet_session::migrations::v1::MigrateV0ToV1<
Runtime,
pallet_session::migrations::v1::InitOffenceSeverity<Runtime>,
>,
cumulus_pallet_aura_ext::migration::MigrateV0ToV1<Runtime>,
cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4<Runtime>,
cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5<Runtime>,
// Permanent
pallet_xcm::migration::MigrateToLatestXcmVersion<Runtime>,
);
// ...
/// Executive: handles dispatch to the various modules.
pub type Executive = frame_executive::Executive<
Runtime,
Block,
frame_system::ChainContext<Runtime>,
Runtime,
AllPalletsWithSystem,
Migrations,
>;
Also, you can check out more examples on the try-runtime
repository to see the possibilities of using this tool.
Benchmarking and weights
Refreshing your runtime’s weights to keep them current is essential, especially if you use custom types or special configurations not considered in the reference benchmarking included with your pallets in FRAME.
To do so, you’ll need frame-omni-bencher
.
# Note: it says `--profile=production` in the GitHub README, but this tends
# to fail MacOS machines.
cargo install frame-omni-bencher --profile=release
Also, you’ll need to build your runtime with the runtime-benchmarks
feature.
cargo build --release --features runtime-benchmarks -p my-runtime
Hint:
try-runtime
andruntime-benchmarks
are not mutually exclusive. So, you can build a runtime with both features at the same time, so you’ll save time building runtimes.
Finally, if your runtime supports genesis config via Runtime APIs, ensure a dev
(not development
) configuration because the bencher CLI uses that one to initialise your runtime when running benchmarks.
Ensure your benchmarks work, using this command
frame-omni-bencher v1 benchmark pallet --runtime target/release/wbuild/my-runtime/my-runtime.wasm --all --steps 2 --repeat 1 --quiet
If the setup, execution, or verification code fails for some extrinsic, you can catch it. In that case, you might need to work on a fix by going to the configuration of the failed pallet and checking on the benchmark helper or types (sometimes, it’s an incorrectly configured origin, sometimes it’s a misconfigured benchmark helper, or sometimes both).
Once you can adjust the bencher usage to refresh the weight files. This is even automatable on your CI (if you can afford a dedicated machine matching the minimum requirements).
Here’s an example of an automated benchmarking execution:
frame-omni-bencher v1 benchmark pallet \
--runtime {{runtime}} \
--pallet "{{pallet}}" --extrinsic "*" \
--steps 50 \
--repeat 20 \
--output ./my-runtime/src/weights/ | \
save --force .benchmarking-logs/{{pallet}}.out.txt \
--stderr .benchmarking-logs/{{pallet}}.log.txt
Advanced topics
Upgrading the Polkadot Fellowship runtimes
Updating runtimes is no easy feat. And it gets worse when maintaining the Polkadot and Kusama network runtimes
, a task assigned to the Polkadot Fellowship.
I’ve been personally in charge of leading the upgrade of the Polkadot SDK on the whole runtimes a couple times, and while most of these steps apply, you’ll always need extra help, due to the complexity, and the multiple contexts and things you need to understand and manage to configure everything alright (i.e. there are people in the Fellowship working on specialised topics like Bridges and XCM, or you might need help from the maintainers of Encointer) all of which escape from a generalist knowledge.
However, if followed appropriately, a series of steps might increase the chance of getting to the point where you need help after performing most of the bump correctly.
For this guide, I’ll mention tasks (such as adjust configs) as single steps, considering you’ve read the entire post so far and have some comprehension about what’s needed to upgrade a single runtime successfully.
Update dependencies
Use psvm -v stableXXYY
as indicated above.
Formatting (after each step)
We run zepter
, taplo
for TOML formatting, cargo fmt -p {runtime}
for code formatting.
Disable Encointer (and request for help)
Comment out every reference to Encointer in the following places:
- GitHub Workflows: This is optional, and works to ensure you can see the CI checks.
chain-spec-generator
Cargo.toml
After this, reach one of the Encointer maintainers available on the Fellowship Channel and request for help.
Note: Do this on a single commit, so the Encointer maintainer you reach can revert the changes with a single step.
Adjust runtime configurations
Follow this order to adjust configs on the runtimes:
staging-kusama-runtime
(relay/kusama
).polkadot-runtime
(relay/polkadot
).asset-hub-kusama-runtime
(system-parachains/asset-hubs/asset-hub-kusama
).asset-hub-polkadot-runtime
(system-parachains/asset-hubs/asset-hub-polkadot
).bridge-hub-kusama-runtime
(system-parachains/bridge-hubs/bridge-hub-kusama
).bridge-hub-polkadot-runtime
(system-parachains/bridge-hubs/bridge-hub-polkadot
).collectives-polkadot-runtime
(system-parachains/collectives/collectives-polkadot
).coretime-kusama-runtime
(system-parachains/coretime/coretime-kusama
).coretime-polkadot-runtime
(system-parachains/coretime/coretime-polkadot
).glutton-kusama-runtime
(system-parachains/gluttons/glutton-kusama
).people-kusama-runtime
(system-parachains/people/people-kusama
).people-polkadot-runtime
(system-parachains/people/people-polkadot
).
Note: This order is not just appropriate lexicographically but also ensures you’re following the appropriate comparable codebase (Rococo for most Kusama changes, Westend for Polkadot changes).
Also, if changes are only related to Kusama, this order will indicate what to do at each runtime.
Run cargo check
and cargo clippy
after adjusting every runtime.
Adjust configs one runtime at a time. This and committing the changes for one runtime at a time ensure git
atomicity, so in case you need to revert some commits or even rebase, this will make things easier.
Adjust Integration Tests
Follow the same order as indicated in the adjust runtime configurations step, starting with integrated-tests/emulated/chains
, then integrated-tests/emulated/tests
. Sometimes, you must change something on emulated/helpers
or zombienet
, but that’s more unusual.
Migrations and Benchmarks
The CI will tell you, but you must also check on Migrations and (if needed) adjust benchmark configs.
Bench Bot
Finally, once you open the PR, and have everything set up (all green checks) run the bench bot, as a comment on your PR.
Run this command for every chain (in the same order as stated above, ideally).
/cmd bench --runtime polkadot --pallet pallet_rc_migrator