Documentation Index
Fetch the complete documentation index at: https://mintlify.com/mystenlabs/sui/llms.txt
Use this file to discover all available pages before exploring further.
Sui allows you to upgrade published packages while maintaining compatibility and security. This guide covers upgrade strategies and best practices.
Upgrade Basics
When you publish a package on Sui, you receive an UpgradeCap that allows you to publish new versions.
Publishing with Upgrade Capability
sui client publish --gas-budget 100000000
This creates:
- Published package at address
0xPACKAGE_ID
UpgradeCap object sent to publisher
Upgrade Policies
Sui enforces three upgrade policies:
-
Compatible: Most restrictive, safest
- Can only add new functions and structs
- Cannot modify existing public functions
- Cannot change struct definitions
-
Additive: Middle ground
- Can add abilities to structs
- Can change private function signatures
- Can add new public functions
-
Dependency-only: Least restrictive
- Can make breaking changes
- Only affects modules that depend on this package
Upgrading a Package
Step 1: Modify your code
Update your Move code with new features:
module my_package::example {
public struct MyObject has key, store {
id: UID,
value: u64,
// New field in upgrade
name: String, // Added in v2
}
// Existing function (unchanged)
public fun get_value(obj: &MyObject): u64 {
obj.value
}
// New function in upgrade
public fun get_name(obj: &MyObject): String {
obj.name
}
}
Step 2: Build the upgrade
Step 3: Authorize upgrade
Publish the new version:
sui client upgrade \
--upgrade-capability 0xUPGRADE_CAP_ID \
--gas-budget 100000000
The package will be published at a new address, but the original address remains as a reference.
Compatible Upgrades
Safe changes that don’t break existing code:
✅ Adding new functions
// v1
module my_package::math {
public fun add(a: u64, b: u64): u64 {
a + b
}
}
// v2 - Added multiplication
module my_package::math {
public fun add(a: u64, b: u64): u64 {
a + b
}
// New function
public fun multiply(a: u64, b: u64): u64 {
a * b
}
}
✅ Adding new structs
// v1
module my_package::objects {
public struct ObjectA has key { /* ... */ }
}
// v2 - Added new struct
module my_package::objects {
public struct ObjectA has key { /* ... */ }
// New struct
public struct ObjectB has key, store {
id: UID,
value: u64,
}
}
✅ Adding private functions
// v2 - Added helper
module my_package::example {
public fun public_function() { /* ... */ }
// New private helper
fun internal_helper() {
// ...
}
}
Incompatible Changes
Changes that break compatibility:
❌ Changing public function signatures
// v1
public fun transfer(obj: MyObject, recipient: address) { /* ... */ }
// v2 - BREAKING: Changed signature
public fun transfer(
obj: MyObject,
recipient: address,
fee: u64 // New parameter breaks compatibility
) { /* ... */ }
❌ Removing public functions
// v1
public fun old_function() { /* ... */ }
// v2 - BREAKING: Function removed
// Removed old_function - breaks compatibility
❌ Modifying struct definitions
// v1
public struct MyObject has key, store {
id: UID,
value: u64,
}
// v2 - BREAKING: Changed fields
public struct MyObject has key, store {
id: UID,
amount: u64, // Renamed field breaks compatibility
}
Migration Strategies
Strategy 1: Additive Only
Add new functionality without changing existing code:
// v1
module my_package::token {
public struct Token has key, store {
id: UID,
balance: u64,
}
public fun balance(token: &Token): u64 {
token.balance
}
}
// v2 - Add new features
module my_package::token {
public struct Token has key, store {
id: UID,
balance: u64,
}
// Keep existing function
public fun balance(token: &Token): u64 {
token.balance
}
// Add new token type
public struct PremiumToken has key, store {
id: UID,
balance: u64,
bonus: u64,
}
// New functions for premium tokens
public fun premium_balance(token: &PremiumToken): u64 {
token.balance + token.bonus
}
}
Strategy 2: Wrapper Pattern
Wrap old structs in new versions:
// v1
public struct ConfigV1 has key {
id: UID,
value: u64,
}
// v2 - Wrap old version
public struct ConfigV2 has key {
id: UID,
v1: ConfigV1, // Embed old version
new_field: String,
}
public fun migrate_to_v2(
old: ConfigV1,
new_field: String,
ctx: &mut TxContext
): ConfigV2 {
ConfigV2 {
id: object::new(ctx),
v1: old,
new_field,
}
}
Strategy 3: Versioned Modules
Create separate modules for major versions:
// sources/token_v1.move
module my_package::token_v1 {
public struct Token has key, store { /* ... */ }
// v1 functions
}
// sources/token_v2.move
module my_package::token_v2 {
public struct Token has key, store { /* ... */ }
// v2 functions with improvements
// Migration function
public fun upgrade_from_v1(
old_token: token_v1::Token
): Token {
// Convert v1 to v2
}
}
Using Dynamic Fields for Upgrades
Add data to existing objects without changing struct:
use sui::dynamic_field as df;
// v1 - Original object
public struct Game has key {
id: UID,
score: u64,
}
// v2 - Add features via dynamic fields
public fun add_multiplayer_support(game: &mut Game) {
df::add(&mut game.id, b"players", vector::empty<address>());
df::add(&mut game.id, b"max_players", 4u8);
}
public fun add_player(game: &mut Game, player: address) {
let players = df::borrow_mut<vector<u8>, vector<address>>(
&mut game.id,
b"players"
);
vector::push_back(players, player);
}
Managing UpgradeCap
Check upgrade capability
# List objects you own
sui client objects
# Look for UpgradeCap
# ObjectID: 0x...
# Type: 0x2::package::UpgradeCap
Transfer upgrade rights
sui client transfer \
--object-id 0xUPGRADE_CAP_ID \
--to 0xNEW_ADMIN_ADDRESS \
--gas-budget 10000000
Make package immutable
Burn the UpgradeCap to prevent future upgrades:
use sui::package;
public fun make_immutable(cap: UpgradeCap) {
package::make_immutable(cap);
}
Or via CLI:
sui client call \
--package 0x2 \
--module package \
--function make_immutable \
--args 0xUPGRADE_CAP_ID \
--gas-budget 10000000
Testing Upgrades
Test upgrade compatibility
# Build with compatibility check
sui move build --skip-fetch-latest-git-deps
Simulate upgrade locally
- Publish v1 on localnet
- Make changes
- Test upgrade
- Verify old objects still work
- Test new functionality
Best Practices
1. Plan for upgrades from the start
// Design extensible structs
public struct Config has key {
id: UID,
// Use dynamic fields for future extensions
}
// Version your APIs
public fun create_v1(/* ... */) { /* ... */ }
2. Document breaking changes
/// @deprecated Use transfer_v2 instead
public fun transfer_v1(/* ... */) { /* ... */ }
/// Enhanced transfer with fee support (v2)
public fun transfer_v2(/* ... */) { /* ... */ }
3. Provide migration paths
/// Upgrade old NFT to new version with metadata
public fun upgrade_nft(
old_nft: OldNFT,
metadata: Metadata,
ctx: &mut TxContext
): NewNFT {
// Migration logic
}
4. Use semantic versioning
[package]
name = "my_package"
version = "2.1.0" # Major.Minor.Patch
5. Test thoroughly before upgrading
- Test on devnet first
- Verify all existing functionality works
- Test new features
- Check gas costs
Common Upgrade Patterns
Adding optional features
// v1
public struct Item has key, store {
id: UID,
name: String,
}
// v2 - Add optional metadata
public fun add_metadata(item: &mut Item, key: String, value: String) {
df::add(&mut item.id, key, value);
}
Deprecating old functions
/// @deprecated Use new_function instead
public fun old_function() {
// Keep for compatibility
}
/// Improved version with better performance
public fun new_function() {
// New implementation
}
Supporting multiple versions
public fun process_v1(obj: &ObjectV1) { /* ... */ }
public fun process_v2(obj: &ObjectV2) { /* ... */ }
// Unified interface
public fun process(obj: &Object) {
if (is_v1(obj)) {
process_v1(as_v1(obj))
} else {
process_v2(as_v2(obj))
}
}
Troubleshooting
Upgrade fails with compatibility error
Check what changed:
sui move build --dump-bytecode-as-base64
Revert breaking changes or use a less restrictive policy.
Lost UpgradeCap
If you lose the UpgradeCap, you cannot upgrade. Options:
- Deploy as new package
- Implement migration functions in v1
Old objects incompatible
Provide migration functions:
public fun migrate_object(old: OldObject): NewObject {
// Convert old to new
}
Next Steps