Skip to main content
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.

Defining the State

The attributes of the contract structure define the data that will be stored.
use near_sdk::{near, AccountId, PanicOnDefault};
use near_sdk::store::UnorderedMap;

#[near(serializers=[borsh])]
pub struct Bid {
    pub bidder: AccountId,
    pub amount: u128,
}

#[near(contract_state)]
#[derive(PanicOnDefault)]
pub struct AuctionContract {
    highest_bid: Bid,
    auction_end_time: u64,
    auctioneer: AccountId,
    claimed: bool,
    // Collections for efficient storage
    bids_history: UnorderedMap<AccountId, Vec<u128>>,
}
Borsh SerializationStructures that will be saved need a special macro that tells the SDK to store them serialized in Borsh format:
#[near(serializers=[borsh])]
Storage CostsContracts pay for their storage by locking part of their balance.It currently costs approximately 1Ⓝ to store 100KB of data.

Initializing the State

After the contract is deployed, its state is empty and needs to be initialized. There are two ways to initialize state:

1. Initialization Functions

Create an initialization function that sets initial values.
use near_sdk::{near, env, PanicOnDefault};

#[near(contract_state)]
#[derive(PanicOnDefault)]
pub struct Contract {
    owner: AccountId,
    start_time: u64,
    config: Config,
}

#[near]
impl Contract {
    #[init]
    #[private]
    pub fn init(owner: AccountId, config: Config) -> Self {
        Self {
            owner,
            start_time: env::block_timestamp(),
            config,
        }
    }
}
  • The initialization function is marked with #[init]
  • #[derive(PanicOnDefault)] forces users to call init before using the contract
  • Mark init functions as #[private] to restrict who can initialize

2. Default State

Set default values for the contract attributes.
use near_sdk::near;

#[near(contract_state)]
pub struct Contract {
    greeting: String,
    counter: u64,
}

impl Default for Contract {
    fn default() -> Self {
        Self {
            greeting: "Hello".to_string(),
            counter: 0,
        }
    }
}
Implement the Default trait to provide default values. The first time the contract is called, these defaults will be stored.
The state can only be initialized once!

State Lifecycle

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.
#[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
    }
}

Storage Collections

For efficient storage of large amounts of data, use NEAR’s storage collections instead of native data structures.
use near_sdk::store::{UnorderedMap, Vector, UnorderedSet};
use near_sdk::{near, AccountId};

#[near(contract_state)]
pub struct Contract {
    // Each collection needs a unique prefix
    users: UnorderedMap<AccountId, User>,
    messages: Vector<String>,
    admins: UnorderedSet<AccountId>,
}

#[near]
impl Contract {
    #[init]
    pub fn new() -> Self {
        Self {
            users: UnorderedMap::new(b"u"),
            messages: Vector::new(b"m"),
            admins: UnorderedSet::new(b"a"),
        }
    }
}
Each collection must have a unique prefix (e.g., b"u", b"m", b"a") to avoid storage collisions.
Why use collections?
  • Native arrays/maps load all data into memory on every call
  • Collections load only needed elements, saving gas
  • Better performance for large datasets

State and Code Independence

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:
  1. Adding methods - ✅ No problems
  2. Modifying existing methods - ✅ Usually safe if state structure unchanged
  3. Modifying state structure - ⚠️ Requires migration

Learn About Contract Updates

See how to safely update contracts and migrate state

Best Practices

Every byte stored costs NEAR tokens. Design your state efficiently:
  • Use compact data types
  • Clean up unused data
  • Consider using collections for large datasets
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");
When using multiple collections, ensure each has a unique prefix to avoid storage collisions:
users: UnorderedMap::new(b"u"),
posts: UnorderedMap::new(b"p"),
comments: Vector::new(b"c"),
Always initialize your contract’s state before allowing user interactions. Use #[init] functions or the PanicOnDefault derive to enforce this.

Next Steps

Functions

Learn about function types

Cross-Contract Calls

Interact with other contracts

Upgrade

Update contracts safely

Security

Security best practices

Build docs developers (and LLMs) love