Skip to main content
NEAR allows you to update deployed smart contracts while preserving their state. This enables you to fix bugs, add features, and improve contracts after deployment.
Contract updates are powerful but require careful planning. Improper updates can corrupt state or break functionality.

Overview

Updating a contract involves:
  1. Deploying new contract code to an existing account
  2. Migrating state if the data structure changed
  3. Testing thoroughly before and after

GitHub Repository

View the complete contract update example

Simple update (no state changes)

If your contract structure doesn’t change, updating is straightforward:
1

Build the new contract

cargo near build
2

Deploy the update

near contract deploy <contract-account>.testnet \
  use-file ./target/near/contract.wasm \
  without-init-call \
  network-config testnet
Using without-init-call preserves existing state. The new code can access all previously stored data.
3

Verify the update

Test that existing functionality still works and new features are available:
# Test existing function
near contract call-function as-transaction <contract>.testnet \
  existing_method json-args '{}' \
  prepaid-gas '30 TeraGas' \
  attached-deposit '0 NEAR' \
  network-config testnet

# Test new function
near contract call-function as-transaction <contract>.testnet \
  new_method json-args '{}' \
  prepaid-gas '30 TeraGas' \
  attached-deposit '0 NEAR' \
  network-config testnet

Update with state migration

When your contract structure changes, you need to migrate state:

Example scenario

#[near(contract_state)]
pub struct OldContract {
    owner: AccountId,
    counter: u64,
}

Migration method

Implement a migration function:
use near_sdk::{near, env};

#[near(contract_state)]
pub struct OldContract {
    owner: AccountId,
    counter: u64,
}

#[near(contract_state)]
pub struct NewContract {
    owner: AccountId,
    counter: u128,
    created_at: u64,
}

#[near]
impl NewContract {
    #[init(ignore_state)]
    #[private]
    pub fn migrate() -> Self {
        // Read old state
        let old: OldContract = env::state_read()
            .expect("Failed to read old state");
        
        // Return new state with migrated data
        Self {
            owner: old.owner,
            counter: old.counter as u128,
            created_at: env::block_timestamp(),
        }
    }
}

Executing migration

1

Deploy the new contract code

near contract deploy <contract>.testnet \
  use-file ./target/near/contract.wasm \
  without-init-call \
  network-config testnet
2

Call the migration method

near contract call-function as-transaction <contract>.testnet \
  migrate json-args '{}' \
  prepaid-gas '300 TeraGas' \
  attached-deposit '0 NEAR' \
  network-config testnet
Mark migration methods as #[private] so only the contract account can call them.
3

Verify the migration

Check that state was migrated correctly:
near contract call-function as-read-only <contract>.testnet \
  get_counter json-args '{}' \
  network-config testnet

Migration patterns

Use Option<T> for new fields or provide defaults:
#[near(contract_state)]
pub struct Contract {
    owner: AccountId,
    new_field: Option<String>,  // Can be None for old state
}

impl Default for Contract {
    fn default() -> Self {
        Self {
            owner: env::current_account_id(),
            new_field: None,
        }
    }
}
Simply don’t include removed fields in the new struct. Old data is ignored:
// Old contract had 'deprecated_field', new one doesn't
#[near(contract_state)]
pub struct NewContract {
    owner: AccountId,
    // deprecated_field removed
}
Read old state, convert types, write new state:
#[init(ignore_state)]
#[private]
pub fn migrate() -> Self {
    #[near(serializers = [borsh])]
    struct OldContract {
        counter: u64,
    }
    
    let old: OldContract = env::state_read().unwrap();
    
    Self {
        counter: old.counter as u128,
    }
}
For complex changes, implement multi-step migrations:
#[near]
impl Contract {
    #[private]
    pub fn migrate_step_1(&mut self) {
        // First migration step
    }
    
    #[private]
    pub fn migrate_step_2(&mut self) {
        // Second migration step
    }
}

Best practices

Test thoroughly

Always test migrations on testnet with production-like data before updating mainnet

Version your contracts

Track contract versions in state to enable conditional migrations

Make migrations private

Only the contract should be able to trigger migrations

Plan for rollback

Keep old contract code available in case you need to redeploy it

Testing migrations

#[cfg(test)]
mod tests {
    use super::*;
    use near_sdk::test_utils::VMContextBuilder;
    use near_sdk::testing_env;
    
    #[test]
    fn test_migration() {
        let context = VMContextBuilder::new().build();
        testing_env!(context);
        
        // Create old contract state
        let old = OldContract {
            owner: "alice.near".parse().unwrap(),
            counter: 42,
        };
        env::state_write(&old);
        
        // Perform migration
        let new = NewContract::migrate();
        
        // Verify migration
        assert_eq!(new.owner, old.owner);
        assert_eq!(new.counter, 42u128);
        assert!(new.created_at > 0);
    }
}

Versioning contracts

Track versions to enable smart migrations:
#[near(contract_state)]
pub struct Contract {
    version: u32,
    // other fields...
}

#[near]
impl Contract {
    #[init(ignore_state)]
    #[private]
    pub fn migrate() -> Self {
        let version: u32 = env::storage_read(b"version")
            .and_then(|v| v.try_into().ok())
            .unwrap_or(0);
        
        match version {
            0 => Self::migrate_v0_to_v1(),
            1 => Self::migrate_v1_to_v2(),
            _ => env::panic_str("Unknown version"),
        }
    }
}

Next steps

Contract security

Secure your contract updates

Testing guide

Comprehensive testing strategies

Lock contracts

Make contracts immutable when ready

Factory pattern

Deploy versioned contracts with factories

Build docs developers (and LLMs) love