Learn how NEAR smart contracts manage their state, including initialization, serialization, and storage costs.
Smart contracts store data in their account’s state, which is public on the blockchain. The storage starts empty until a contract is deployed and the state is initialized.
The account’s code and account’s storage are independent. Updating the code does not erase the state.
Understanding how state is managed during contract execution:
1
Load
When a function is called, the contract’s state is loaded from storage into memory and deserialized.
2
Execute
The function executes with access to the deserialized state.
3
Save
When the method finishes successfully, all changes to the state are serialized and saved back to storage.
Rust
JavaScript
#[near]impl Contract { pub fn increment(&mut self) { // 1. State loaded and deserialized // 2. Modify the state self.counter += 1; near_sdk::log!("Counter: {}", self.counter); // 3. State serialized and saved automatically } pub fn get_counter(&self) -> u64 { // State loaded but not modified self.counter // No save needed }}
@NearBindgen({})class Contract { counter = 0; @call({}) increment() { // 1. State loaded and deserialized // 2. Modify the state this.counter += 1; near.log(`Counter: ${this.counter}`); // 3. State serialized and saved automatically } @view({}) get_counter() { // State loaded but not modified return this.counter; // No save needed }}
Each collection must have a unique prefix (e.g., b"u", b"m", b"a") to avoid storage collisions.
import { NearBindgen } from 'near-sdk-js';import { UnorderedMap, Vector, UnorderedSet } from 'near-sdk-js/lib/collections';@NearBindgen({})class Contract { // Each collection needs a unique prefix users = new UnorderedMap('users'); messages = new Vector('messages'); admins = new UnorderedSet('admins'); constructor() { this.users = new UnorderedMap('users'); this.messages = new Vector('messages'); this.admins = new UnorderedSet('admins'); }}
Each collection must have a unique prefix string to avoid storage collisions.
Why use collections?
Native arrays/maps load all data into memory on every call
Important: State Persists Through UpdatesIn NEAR, the contract’s code and contract’s storage are independent.Updating the code does not erase the state, which can lead to unexpected behavior or errors if the new code expects a different state structure.
When updating a contract:
Adding methods - ✅ No problems
Modifying existing methods - ✅ Usually safe if state structure unchanged
Modifying state structure - ⚠️ Requires migration
Learn About Contract Updates
See how to safely update contracts and migrate state
Every byte stored costs NEAR tokens. Design your state efficiently:
Use compact data types
Clean up unused data
Consider using collections for large datasets
Ensure Sufficient Balance
Always ensure your contract has enough balance to cover storage costs:
let required_storage = env::storage_usage() as u128 * env::storage_byte_cost();assert!(env::account_balance() >= required_storage, "Insufficient balance for storage");
Use Unique Collection Prefixes
When using multiple collections, ensure each has a unique prefix to avoid storage collisions: