How do package upgrades work on Sui?
November 13, 2026
Sui packages are immutable, so an upgrade doesn't change the existing package. It publishes a new package object with a new ID that the chain records as the next version of the original. You need the package's UpgradeCap, the new code has to be compatible with the previous version (public function signatures and existing struct layouts can't change), and init doesn't run again. The old version stays on chain and anyone can still call it, so code that relies on shared objects has to handle that itself.
Upgrade with the CLI
From the package directory, after changing the code:
sui client upgradeThe CLI reads the UpgradeCap ID from Published.toml for your active environment. If it isn't recorded there (for example, a package published with an older CLI), pass it yourself:
sui client upgrade --upgrade-capability 0x<UPGRADE_CAP_ID>The output contains the new package ID, and Published.toml is updated with the new published-at address and version. original-id stays the same. Add --dry-run to check compatibility and cost first.
What's allowed
Under the default compatible policy, an upgrade may:
- change any function's implementation
- add new modules, structs and functions
- change, remove or make public any non-
publicfunction, includingpublic(package)andentryfunctions - remove ability constraints from generic type parameters
It may not:
- change the signature of an existing
publicfunction - change an existing struct's fields or abilities
- remove a
publicfunction or a struct
sui client upgrade checks this locally and stops if the new code breaks the rules. Validators check it again on chain.
What happens under the hood
An upgrade is one transaction with three steps:
- Authorize:
0x2::package::authorize_upgrade(&mut cap, policy, digest)returns anUpgradeTicketfor this exact bytecode digest. - Upgrade: the built-in
Upgradecommand consumes the ticket, verifies the bytecode and compatibility, creates the new package and returns anUpgradeReceipt. - Commit:
0x2::package::commit_upgrade(&mut cap, receipt)points the cap at the new package and increments its version.
The ticket and receipt have no abilities ("hot potatoes"), so all three steps must happen in the same transaction. The CLI builds this for you. In TypeScript, tx.upgrade({ modules, dependencies, package, ticket }) is the middle step.
Old versions and types
- Types keep their original ID. A struct defined in v1 is always
<original-id>::module::Type, even when you call functions from v3. Event types and object types don't change across upgrades. New types added in a later version use that version's ID. - Callers choose the version. A transaction calls a specific package ID. Clients that still call the v1 ID run v1 code. Update your frontends to the new
published-atID. - Dependents don't move automatically. If package B depends on your package A, B keeps linking to the A version it was built with until B is upgraded too.
Versioned shared objects
Because old code stays callable, a fix to a shared object's logic does nothing if people can still call the old functions on it. The documented pattern is a version field on the shared object:
module example::counter;
const VERSION: u64 = 2;
const EWrongVersion: u64 = 1;
const ENotAdmin: u64 = 0;
const ENotUpgrade: u64 = 2;
public struct Counter has key {
id: UID,
version: u64,
admin: ID,
value: u64,
}
public struct AdminCap has key { id: UID }
public fun increment(c: &mut Counter) {
assert!(c.version == VERSION, EWrongVersion);
c.value = c.value + 1;
}
entry fun migrate(c: &mut Counter, a: &AdminCap) {
assert!(c.admin == object::id(a), ENotAdmin);
assert!(c.version < VERSION, ENotUpgrade);
c.version = VERSION;
}Every upgrade increases VERSION. After upgrading, the admin calls migrate, and from then on calls through the old package abort. migrate is entry, not public, so its signature can change in later upgrades. You have to design this in from the first version, since you can't add a field to an existing struct later.
To control who can upgrade and how much they can change, see UpgradeCap and upgrade policies. For the first deploy, see publishing a package.
Sources
- Upgrading Packages, Sui documentation
- Custom Upgrade Policies, Sui documentation
package.move, Sui framework source- Package Upgrades, The Move Book