Error Handling On-Chain
Programs communicate failure through ProgramResult (Result<(), ProgramError>) or custom error enums mapped to codes. Clients decode these codes off-chain for UX and debugging.
Search across all documentation pages
Programs communicate failure through ProgramResult (Result<(), ProgramError>) or custom error enums mapped to codes. Clients decode these codes off-chain for UX and debugging.
use solana_program::program_error::ProgramError;
fn require_init(data_len: usize, need: usize) -> Result<(), ProgramError> {
if data_len < need {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}When to reach for this:
?.use solana_program::program_error::ProgramError;
#[repr(u32)]
pub enum VaultError {
InsufficientFunds = 6000,
Unauthorized = 6001,
}
impl From<VaultError> for ProgramError {
fn from(e: VaultError) -> Self {
ProgramError::Custom(e as u32)
}
}
pub fn withdraw(amount: u64, balance: u64) -> Result<(), ProgramError> {
if amount > balance {
return Err(VaultError::InsufficientFunds.into());
}
Ok(())
}What this demonstrates:
ProgramError::Custom(code).From impl enables ? in instruction handlers.| Layer | Type |
|---|---|
| Runtime | ProgramError::InvalidAccountData |
| App | ProgramError::Custom(u32) |
| Anchor | error_code attribute macro |
type VaultResult<T> = Result<T, ProgramError>;Err with explicit variant.invoke Result. Fix: Use ? or map to your enum.| Alternative | Use When | Don't Use When |
|---|---|---|
Anchor error! macro | Auto IDL export | Non-Anchor builds |
| Only ProgramError builtins | Minimal codes | Product UX |
| Events for success paths | Rich observability | Not a substitute for errors |
Alias for Result<(), ProgramError>.
No - failed instruction rolls back all changes.
Anchor adds offset 6000+ for custom codes in 0.32.x.
Works if no std-only features enabled.
Allowed but costs CUs - keep short.
Match custom code to enum off-chain.
Builtins are portable; custom need program IDL.
Yes - bubble with ? in instruction handler.
Avoid - use explicit returns.
First failing instruction fails whole tx (unless handled).
Same codes as on-chain execution.
Bubble up or map deliberately - do not swallow.
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.
Reviewed by Chris St. John·Last updated Jul 16, 2026