Skip to main content
NEAR accounts separate their logic (contract code) from their state (storage), allowing the code to be changed without losing data. Contracts can be updated in two ways:

Via Tools

Using NEAR CLI or API if you hold the account’s full access key

Programmatically

Implementing a method that takes new code and deploys it

Updating via NEAR CLI

The simplest way to update a contract is to re-deploy using NEAR CLI:
# Compile your updated contract
cargo near build  # or npm run build

# Deploy the new version
near deploy <your-account.testnet> <path-to-new-wasm>
The account’s state persists - only the code changes.

Programmatic Updates

A contract can update itself by implementing an update method:
use near_sdk::{near, env, Promise};

#[near]
impl Contract {
    /// Update the contract code
    /// Only callable by the contract owner
    pub fn update_contract(&self) -> Promise {
        // Check authorization
        self.assert_owner();

        // Get new contract code from input
        let code = env::input().expect("No input");

        // Deploy the new code
        Promise::new(env::current_account_id())
            .deploy_contract(code.to_vec())
    }

    fn assert_owner(&self) {
        assert_eq!(
            env::predecessor_account_id(),
            self.owner,
            "Only owner can update"
        );
    }
}

Calling the Update Method

# Read the contract code
CONTRACT_CODE=$(cat path/to/new-contract.wasm | base64)

# Call update method
near call <contract-account> update_contract \
  file-args <path/to/wasm> \
  --gas 300000000000000 \
  --useAccount <owner-account>
DAO FactoriesThis is how DAO factories update their contracts.

State Migration

Since the account’s state persists when updating the contract, changes to the state structure require migration.

Safe Changes

When Migration is Needed

If you modify or remove structures stored in state, you’ll see:
Error: Cannot deserialize the contract state
You have three options:
  1. Use a different account (fresh start)
  2. Rollback to previous contract code
  3. Migrate the state (recommended for production)

State Migration Example

Original Contract

Suppose you have a guest book contract:
use near_sdk::{near, store::Vector};

#[near(serializers=[borsh])]
pub struct PostedMessage {
    pub premium: bool,
    pub sender: AccountId,
    pub text: String,
}

#[near(contract_state)]
pub struct Contract {
    messages: Vector<PostedMessage>,
    payments: Vector<u128>,
}

Updated Contract

You decide to store payments inside messages:
#[near(serializers=[borsh])]
pub struct PostedMessage {
    pub premium: bool,
    pub sender: AccountId,
    pub text: String,
    pub payment: u128,  // NEW FIELD
}

#[near(contract_state)]
pub struct Contract {
    messages: Vector<PostedMessage>,
    // payments vector removed
}

The Problem

If you deploy this directly:
  1. Extra payments vector still in storage
  2. Existing PostedMessages missing the payment field
  3. Contract fails to deserialize state

The Solution: Migration Method

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

// Old state structure
#[near(serializers=[borsh])]
struct OldPostedMessage {
    premium: bool,
    sender: AccountId,
    text: String,
}

#[near(serializers=[borsh])]
struct OldState {
    messages: Vector<OldPostedMessage>,
    payments: Vector<u128>,
}

// New state structure
#[near(serializers=[borsh])]
pub struct PostedMessage {
    pub premium: bool,
    pub sender: AccountId,
    pub text: String,
    pub payment: u128,
}

#[near(contract_state)]
pub struct Contract {
    messages: Vector<PostedMessage>,
}

#[near]
impl Contract {
    /// Migration method
    #[init(ignore_state)]
    #[private]
    pub fn migrate() -> Self {
        // Read old state
        let old_state: OldState = env::state_read()
            .expect("Failed to read state");

        // Create new messages with payments
        let mut new_messages = Vector::new(b"m");
        
        for i in 0..old_state.messages.len() {
            let old_msg = old_state.messages.get(i).unwrap();
            let payment = old_state.payments.get(i).unwrap_or(0);
            
            new_messages.push(PostedMessage {
                premium: old_msg.premium,
                sender: old_msg.sender,
                text: old_msg.text,
                payment,
            });
        }

        // Return new state
        Self {
            messages: new_messages,
        }
    }
}
Important:The migration function is an initialization method that ignores the existing state (#[init(ignore_state)] or @initialize), allowing it to execute and rewrite the state.

Running the Migration

# 1. Deploy the new contract
near deploy <contract-account> <new-wasm>

# 2. Call the migration method
near call <contract-account> migrate '{}' --useAccount <contract-account>
Why Remove Old Structures?Old data remains in storage even after migration, occupying space and costing NEAR. Always clean up by calling methods like clear() on removed collections.

Best Practices

Always test state migrations on testnet first:
  1. Deploy old version
  2. Add test data
  3. Deploy new version
  4. Run migration
  5. Verify all data migrated correctly
Include a version field in your contract state:
#[near(contract_state)]
pub struct Contract {
    version: u32,
    // other fields
}
This makes it easier to handle multiple migration paths.
Design your contract with upgrades in mind:
  • Use collections instead of fixed structures
  • Keep state structure simple
  • Document state schema
  • Plan migration strategy early
Restrict who can update the contract:
pub fn update_contract(&self) {
    // Only specific accounts can update
    assert_eq!(
        env::predecessor_account_id(),
        self.owner,
        "Unauthorized"
    );
}
For important contracts, use a DAO to govern updates:
  • Community votes on upgrades
  • Timelock for updates
  • Emergency pause mechanism
Before major updates:
  1. Export contract state
  2. Save locally
  3. Verify export is complete
  4. Then proceed with update

Common Migration Patterns

Adding Optional Fields

// Before
pub struct User {
    pub name: String,
    pub balance: u128,
}

// After - no migration needed if using Option
pub struct User {
    pub name: String,
    pub balance: u128,
    pub email: Option<String>,  // Optional field
}

Renaming Fields

Requires migration to copy data:
#[init(ignore_state)]
pub fn migrate() -> Self {
    let old: OldContract = env::state_read().unwrap();
    Self {
        new_field_name: old.old_field_name,
        // ... other fields
    }
}

Changing Field Types

Requires migration with type conversion:
#[init(ignore_state)]
pub fn migrate() -> Self {
    let old: OldContract = env::state_read().unwrap();
    Self {
        // Convert String to AccountId
        account: old.account_string.parse().unwrap(),
        // ... other fields
    }
}

Emergency Procedures

Rollback

If an update goes wrong, rollback immediately:
# Redeploy previous version
near deploy <account> <old-contract.wasm>

Pause Contract

Implement a pause mechanism for emergencies:
#[near]
impl Contract {
    pub fn pause(&mut self) {
        self.assert_owner();
        self.paused = true;
    }

    pub fn unpause(&mut self) {
        self.assert_owner();
        self.paused = false;
    }

    fn assert_not_paused(&self) {
        assert!(!self.paused, "Contract is paused");
    }
}

Migration Checklist


Resources

Migration Example

Complete migration example project

DAO Updates

How DAOs handle contract updates

Testing

Test your migrations thoroughly

Security

Security considerations for updates

Next Steps

Monitor Contract

Track your contract after updates

Build Frontend

Update your frontend for new features

Security Audit

Audit after major changes

Documentation

Update your documentation

Build docs developers (and LLMs) love