Skip to main content
State management in Anchor programs involves creating, reading, updating, and closing accounts that store your program’s data. Anchor provides powerful abstractions through the #[account] macro and account constraints to make state management safe and ergonomic.

The #[account] Macro

The #[account] macro is applied to structs to create custom account types for your program. It automatically implements several traits:
  • Owner: Sets the account owner to the program ID (from declare_id!)
  • AccountSerialize and AccountDeserialize: Handles serialization
  • Discriminator: Adds an 8-byte discriminator to distinguish account types

Basic Account Definition

#[account]
pub struct MyAccount {
    pub data: u64,
    pub authority: Pubkey,
    pub created_at: i64,
}
The #[account] macro adds an 8-byte discriminator at the start, so when calculating space:
// Space = 8 (discriminator) + account data size
space = 8 + 8 + 32 + 8  // discriminator + u64 + Pubkey + i64

InitSpace Derive Macro

For easier space calculation, use the InitSpace derive macro:
#[account]
#[derive(InitSpace)]
pub struct MyAccount {
    pub data: u64,
    pub authority: Pubkey,
    #[max_len(50)]
    pub name: String,
    #[max_len(10)]
    pub tags: Vec<u8>,
}

// Usage in accounts struct:
#[account(
    init,
    payer = authority,
    space = 8 + MyAccount::INIT_SPACE
)]
pub my_account: Account<'info, MyAccount>,

Account Initialization

To create and initialize a new account, use the init constraint:
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = user,
        space = 8 + MyAccount::INIT_SPACE
    )]
    pub my_account: Account<'info, MyAccount>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}
The init constraint:
  • Creates the account via CPI to the system program
  • Allocates the specified space
  • Assigns ownership to your program
  • Sets the account discriminator
  • Requires payer and space parameters
  • Requires system_program in the accounts struct

Initialization with PDA

For PDA (Program Derived Address) accounts:
#[derive(Accounts)]
pub struct InitializePda<'info> {
    #[account(
        init,
        payer = user,
        space = 8 + MyAccount::INIT_SPACE,
        seeds = [b"my_account", user.key().as_ref()],
        bump
    )]
    pub my_account: Account<'info, MyAccount>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

Reading Account Data

Access account data through the Context:
pub fn read_data(ctx: Context<ReadData>) -> Result<()> {
    let account = &ctx.accounts.my_account;
    msg!("Data: {}", account.data);
    msg!("Authority: {}", account.authority);
    Ok(())
}

#[derive(Accounts)]
pub struct ReadData<'info> {
    pub my_account: Account<'info, MyAccount>,
}

Updating Account Data

To modify account data, mark the account as mut (mutable):
pub fn update_data(ctx: Context<UpdateData>, new_value: u64) -> Result<()> {
    let account = &mut ctx.accounts.my_account;
    account.data = new_value;
    msg!("Updated data to: {}", new_value);
    Ok(())
}

#[derive(Accounts)]
pub struct UpdateData<'info> {
    #[account(mut)]
    pub my_account: Account<'info, MyAccount>,
}
Anchor automatically serializes the account data back to the account when the instruction completes successfully.

Account Constraints

Use constraints to enforce security and business logic:

has_one Constraint

Verifies that an account field matches a provided account:
#[derive(Accounts)]
pub struct UpdateData<'info> {
    #[account(
        mut,
        has_one = authority
    )]
    pub my_account: Account<'info, MyAccount>,
    pub authority: Signer<'info>,
}
This checks that my_account.authority == authority.key().

constraint Constraint

Custom validation logic:
#[derive(Accounts)]
pub struct UpdateData<'info> {
    #[account(
        mut,
        constraint = my_account.data < 100 @ ErrorCode::DataTooLarge
    )]
    pub my_account: Account<'info, MyAccount>,
}

Closing Accounts

Reclaim SOL rent by closing accounts with the close constraint:
pub fn close_account(ctx: Context<CloseAccount>) -> Result<()> {
    msg!("Closing account");
    Ok(())
}

#[derive(Accounts)]
pub struct CloseAccount<'info> {
    #[account(
        mut,
        close = authority,
        has_one = authority
    )]
    pub my_account: Account<'info, MyAccount>,
    #[account(mut)]
    pub authority: Signer<'info>,
}
The close constraint:
  • Transfers all lamports to the specified account
  • Zeroes out the account data
  • Marks the account for garbage collection

Reallocating Accounts

Increase or decrease account size with realloc:
#[derive(Accounts)]
pub struct ReallocAccount<'info> {
    #[account(
        mut,
        realloc = 8 + MyAccount::INIT_SPACE + 100,
        realloc::payer = authority,
        realloc::zero = true,
    )]
    pub my_account: Account<'info, MyAccount>,
    #[account(mut)]
    pub authority: Signer<'info>,
    pub system_program: Program<'info, System>,
}

Complete Example

Here’s a complete counter program demonstrating state management:
use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod counter {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.authority = ctx.accounts.authority.key();
        counter.count = 0;
        msg!("Counter initialized");
        Ok(())
    }

    pub fn increment(ctx: Context<Update>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.count = counter.count.checked_add(1)
            .ok_or(ErrorCode::Overflow)?;
        msg!("Counter: {}", counter.count);
        Ok(())
    }

    pub fn decrement(ctx: Context<Update>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.count = counter.count.checked_sub(1)
            .ok_or(ErrorCode::Underflow)?;
        msg!("Counter: {}", counter.count);
        Ok(())
    }

    pub fn close(ctx: Context<Close>) -> Result<()> {
        msg!("Counter closed");
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = authority,
        space = 8 + Counter::INIT_SPACE,
        seeds = [b"counter", authority.key().as_ref()],
        bump
    )]
    pub counter: Account<'info, Counter>,
    #[account(mut)]
    pub authority: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Update<'info> {
    #[account(
        mut,
        has_one = authority
    )]
    pub counter: Account<'info, Counter>,
    pub authority: Signer<'info>,
}

#[derive(Accounts)]
pub struct Close<'info> {
    #[account(
        mut,
        close = authority,
        has_one = authority
    )]
    pub counter: Account<'info, Counter>,
    #[account(mut)]
    pub authority: Signer<'info>,
}

#[account]
#[derive(InitSpace)]
pub struct Counter {
    pub authority: Pubkey,
    pub count: u64,
}

#[error_code]
pub enum ErrorCode {
    #[msg("Counter overflow")]
    Overflow,
    #[msg("Counter underflow")]
    Underflow,
}

init_if_needed

For accounts that might already exist, use init_if_needed:
#[account(
    init_if_needed,
    payer = user,
    space = 8 + MyAccount::INIT_SPACE
)]
pub my_account: Account<'info, MyAccount>,
Be careful with init_if_needed as it can introduce security vulnerabilities if not used properly. Always validate the account state after initialization.

Best Practices

  1. Use InitSpace: Leverage #[derive(InitSpace)] for automatic space calculation
  2. Validate ownership: Always use has_one or other constraints to verify account relationships
  3. Check arithmetic: Use checked math operations to prevent overflows
  4. Close unused accounts: Reclaim rent by closing accounts when done
  5. Use PDAs for deterministic addresses: Prefer PDAs over keypair-based accounts
  6. Minimize account size: Only store necessary data to reduce rent costs

Build docs developers (and LLMs) love