Sui uses UpgradeCap to authorize compatible Move package upgrades
Sui controls upgrades to application Move packages through UpgradeCap, a capability object granting authority to publish compatible releases. An upgrade creates a new package ID and keeps previously published code intact. The capability records the latest package in its upgrade chain and the permitted level of change. Its holder can authorize releases, tighten compatibility rules, or relinquish upgrade authority permanently. Existing public interfaces and struct layouts constrain what an upgrade may change. Publishing code does not automatically migrate application objects or update packages depending on it. A compatible implementation change can still alter an application’s rules for handling its objects.
Last updated -
Package code and upgrade authority
Published package code gives callers a fixed implementation, while an UpgradeCap gives its authorized holder control over later releases in the same package family. The package ID identifies the implementation a transaction calls. The capability has its own object ID, ownership, and policy. Transferring the capability changes who can authorize upgrades without editing a published module. The cap authorizes upgrades only from the latest package in its family. Keeping both IDs in deployment records prevents confusion between the code callers use and the object controlling future releases.
Package metadata also separates type identity from the address of a newer implementation. A type-origin table records the release introducing each type, preserving existing types’ identities through compatible upgrades. A linkage table identifies the dependency versions the package uses. Publication metadata records package addresses, capability IDs, and build configuration for the relevant network. Package-management workflows using
Published.toml
keep these records there; older workflows may retain publication information in
Move.lock.
Framework packages follow a separate system-upgrade path that preserves their package IDs.
Compatibility limits and policy choices
Compatibility rules preserve existing types and public interfaces while allowing maintenance within defined boundaries, and the capability’s policy can impose further restrictions on those changes. An UpgradeCap’s compatibility policy can only become more restrictive.
Compatible releases
The compatible policy permits implementation changes, new functions and types, and dependency updates. Existing struct fields, their order, and struct abilities stay unchanged. Existing public functions retain their parameter and return types. Removing generic ability constraints from function signatures is an allowed relaxation. Private and
public(package)
functions can change signatures or disappear, including entry functions with those visibility levels. Compatibility checks preserve the required interfaces; they do not establish the correctness of changed application logic.
Additive releases
An additive policy permits new functions and types while preserving existing declarations and function implementations. It also allows dependency changes. This leaves room for additional entry points while keeping established code stable. Editing an existing function body, including a bug fix, requires permission beyond an additive policy.
Dependency-only releases
A dependency-only policy permits dependency changes while holding the package’s own modules fixed. Tightening to this level removes permission to add functions or patch existing bodies. The maintenance work anticipated for the package should fit that boundary before the holder restricts the capability.
Who can authorize the next package release?
The UpgradeCap holder controls upgrade authorization within the capability’s policy, and its custody model determines which signatures or onchain conditions can exercise that authority.
Wallet and multisig custody
Keeping an address-owned capability in a wallet ties authorization to that address’s signing control. Transferring it to a multisig address introduces that address’s configured approval threshold. The capability ID alone grants no permission to use it. Single-key custody lets anyone holding that key exercise the upgrade permissions the policy allows. Multisig custody changes approval requirements without broadening compatibility permissions.
Custom controls and permanent immutability
A custom policy can wrap the capability and guard the functions issuing tickets and consuming receipts. Governance approvals and timelocks are possible conditions implemented by that policy. Their behavior comes from its actual Move code; UpgradeCap does not automatically create them. Calling
make_immutable
consumes the capability and permanently ends upgrades for that package family. This also closes the dependency-only maintenance path.
Application-admin capabilities govern the permissions their modules assign; they do not inherently confer package-upgrade authority.
Authorization, execution, and commit
An authorized package upgrade combines permission, bytecode publication, and capability bookkeeping within one programmable transaction block, so intermediate values never become reusable approvals.
The framework’s
authorize_upgrade
function borrows the capability mutably and produces an UpgradeTicket binding authorization to particular bytecode, dependencies, and policy. The built-in Upgrade command consumes that ticket, checks the submitted contents and compatibility, and returns an UpgradeReceipt on success. The
commit_upgrade
function consumes the receipt and updates the originating capability to the new package. It also advances the capability’s package version.
The UpgradeTicket and UpgradeReceipt must both be consumed within the transaction performing the upgrade.
The basic
sui client upgrade
command handles this flow; wrapped capabilities require calls through their custom policy.
Layout rejection and release confirmation
A hypothetical release adds a field to an existing struct, creating a compatibility failure even when the publisher controls an UpgradeCap using the compatible policy. A compatibility check identifies the layout mismatch before deployment. An implementation change preserving the required interfaces can follow the normal upgrade path; the additional field needs a different design.
- If the comparison reports changed fields, preserve the existing layout and define a new type for the additional state.
- If the capability policy forbids the revised change, stop; a stricter capability cannot be loosened for this retry.
- After the revised package passes compatibility checks, authorize and submit the upgrade using the matching capability.
- Read the transaction effects for success, the new package ID, and the originating capability update.
- Compare the committed package with the intended source, then check whether the application requires an explicit migration.
A submitted upgrade failing this layout check creates no new usable release. Review the reported incompatibility and rebuild the package before retrying. Increasing the gas budget does not make an incompatible struct change valid. A transaction identifier alone establishes neither successful publication nor successful migration.
Application state and dependent callers
Publishing corrected code leaves existing application objects in place, so migration logic and callers’ package targets determine how the release changes actual application behavior.
Existing state and version checks
Additional persistent state needs a design respecting existing struct layouts. New types can provide a migration destination, while dynamic fields support extensions without changing the original layout. Shared-object applications can include a stored application version and guard functions against incompatible versions. Older functions need an existing version check to reject objects because their application version changed. New code cannot retrofit a version guard into an already published module. Privileged migration functions should check the relationship between the authorizing capability and the object they change.
Dependency linkage and retained releases
Changing a package’s dependency selections requires an explicit upgrade of the consuming package. A dependency’s new release does not redirect already published callers. Offchain integrations likewise choose the package ID they invoke. Older releases remain callable wherever their own access rules permit. A migration can restrict access to particular state without deleting the old code. Before routing callers to the new package, decide whether existing objects satisfy its functions’ requirements or need an authorized migration.
Before you start with Sui
-
Does a package upgrade run its init function again?
- A package upgrade does not run module init functions. Initial publication runs those initializers, but an upgrade does not repeat them or execute new initializers added in later releases. New setup work therefore needs an explicit callable function with suitable access controls when it creates privileged application state.
-
Can changing a manifest version number replace an onchain upgrade?
- Changing a manifest version number does not upgrade an onchain package. Manifest version fields document the source package; publication and upgrade commands do not use them to establish an upgrade chain. Publishing again independently creates a separate package. Maintaining the existing package family requires the authorized upgrade mechanism.
-
Why can source verification fail after a successful package upgrade?
- A successful upgrade establishes protocol acceptance, while source verification checks whether source rebuilds to the published bytecode and linkage. Different source files, dependency resolution, or build configuration can produce a mismatch. The rebuild must reproduce the publication’s compiler output. A release’s transaction success does not establish a match with a particular source tree.
-
Is losing access to an UpgradeCap the same as making a package immutable?
- Losing access leaves the UpgradeCap object in existence, whereas make_immutable consumes it permanently. Upgrade availability after lost access depends on whether authorized custody can be recovered. Knowing the capability ID does not grant that access, and a separate application-admin capability does not automatically replace the package’s upgrade authority.
-
How does an UpgradeCap’s package version differ from its object version?
- The version field inside UpgradeCap advances with successfully committed package upgrades. Its object metadata version instead identifies a state of the capability object and can change through mutations unrelated to a package release. Reading the object version as an upgrade count therefore confuses the capability’s state history with the package’s release history.