Learn how to safely upgrade NEAR smart contracts, including state migration strategies and best practices for contract updates.
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
A contract can update itself by implementing an update method:
Rust
JavaScript
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" ); }}
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, } }}
@NearBindgen({})class Contract { messages = []; @initialize({}) migrate() { // Read old state directly from storage const oldMessages = this.messages || []; const oldPayments = this.payments || []; // Transform to new structure this.messages = oldMessages.map((msg, index) => ({ premium: msg.premium, sender: msg.sender, text: msg.text, payment: oldPayments[index] || '0', })); // Clear old payments field delete this.payments; near.log('Migration completed'); }}
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.
# 1. Deploy the new contractnear deploy <contract-account> <new-wasm># 2. Call the migration methodnear 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.