Skip to main content
Anchor 0.30+ Migration Guide: 0.29 to 1.2 in 7 Steps - Featured image

Anchor 0.30+ Migration Guide: 0.29 to 1.2 in 7 Steps

BUILDERZ

BUILDERZ

Share

In this guide

The decisions and working steps ahead.

What changed since 0.29, and where to stop

Steps 1 to 3: toolchain, crates, and idl-build

Step 4: the new IDL format and declare_program!

Your Anchor program still builds on the 2023 toolchain. Then one fresh laptop breaks the client.

A frontend dev runs anchor build, copies the new IDL into the app, and every .accounts() call stops type checking. The discriminators moved into the IDL file and the npm package has a new name. The on-chain IDL account now belongs to a different program.

None of that is a bug. It's three years of breaking changes landing in one afternoon. Here's how we take a production program from 0.29 to the current release without a mainnet outage.

What changed since 0.29, and where to stop

Anchor 0.29.0 shipped on 2023-10-16. The current release is 1.2.1, dated 2026-10-05 in the Anchor CHANGELOG, which lists every change between them. Four of those releases break something.

  • 0.30.0 (2024-04-15): new IDL spec with discriminators in the file, idl-build feature required in Cargo.toml, the declare_program macro. In TypeScript, .accounts() is type-safe and programId leaves the Program constructor.
  • 0.30.1 (2024-06-20): anchor idl convert for legacy IDLs.
  • 0.31.0 (2025-03-08): Solana v2 crates, agave-install replaces solana-install, borsh 0.9 support removed.
  • 0.32.0 (2025-10-08): anchor deploy uploads the IDL by default, anchor publish removed.
  • 1.0.0 (2026-04-02): @coral-xyz/anchor becomes @anchor-lang/core, Solana 3.0 crates, on-chain IDLs move to the Program Metadata Program. The CLI stops shelling out to the solana binary.

The repository now lives at github.com/otter-sec/anchor; the old coral-xyz links redirect there.

Teams with a program in production then ask: do we need to go all the way? Three checks answer that.

First, your Solana crate line. Anchor 0.31 moved to Solana v2 crates, and 1.0 targets Solana 3.x with Agave 3.1.10 recommended in the 1.0.0 release notes. If a dependency still pins the 1.x solana-program crate, you're stopping at 0.30 until it updates.

Second, your IDL consumers. Indexers and wallets that parse your on-chain IDL account need to know 1.0 stores it through the Program Metadata Program. Warn them before you redeploy.

Third, your appetite for a two-hop migration. Our default for a live program is to land on the 0.30 line first, then jump to 1.2 in a second release. Patched targets exist for every stop: the 0.30.2, 0.31.2, and 0.32.2 tags all shipped on 2026-09-14 per the releases page.

The steps below assume you're going the full distance. If you stop at 0.32, skip the package rename and the IDL account step.

Steps 1 to 3: toolchain, crates, and idl-build

Every code sample in this post targets Anchor CLI 1.2.1, with anchor-lang 1.2.1 and @anchor-lang/core 1.2.1 as the matching crate and npm package. We checked both on the registries today.

Pin the CLI with avm so the whole team builds the same binary. The installation docs use this flow:

Code examplebash
cargo install --git https://github.com/otter-sec/anchor avm --force
avm install 1.2.1
avm use 1.2.1
anchor --version   # anchor-cli 1.2.1

Install the matching Solana toolchain next. Since 1.2.0, avm can also resolve the platform-tools version from the Anchor version, which removes one source of "works on my machine".

Code examplebash
sh -c "$(curl -sSfL https://release.anza.xyz/v3.1.10/install)"

Now the program crate. Bump anchor-lang and anchor-spl, and add the idl-build feature that 0.30 made mandatory. Builds that skip this feature produce no IDL and fail late.

Code exampletoml
# programs/my_program/Cargo.toml
[dependencies]
anchor-lang = "1.2.1"
anchor-spl = "1.2.1"

[features]
default = []
cpi = ["no-entrypoint"]
no-entrypoint = []
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]

Then pin the toolchain in Anchor.toml so CI and laptops agree. Both keys are documented in the Anchor.toml reference.

Code exampletoml
[toolchain]
anchor_version = "1.2.1"
solana_version = "3.1.10"

Run anchor build here, before changing any Rust. The umbrella solana-program crate was replaced by smaller crates in 0.32 per the CHANGELOG, so fix those imports first. Move on only when target/idl/my_program.json exists.

Step 4: the new IDL format and declare_program!

The 0.30 IDL spec is the change that breaks clients. The IDL docs explain the reason: discriminators are now written into the file, so the client no longer computes them.

Code examplejson
{
  "address": "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS",
  "metadata": { "name": "my_program", "version": "0.1.0", "spec": "0.1.0" },
  "instructions": [
    {
      "name": "initialize",
      "discriminator": [175, 175, 109, 31, 13, 152, 155, 237],
      "accounts": [
        { "name": "authority", "writable": true, "signer": true },
        { "name": "system_program", "address": "11111111111111111111111111111111" }
      ],
      "args": []
    }
  ]
}

Compared with 0.29: isMut became writable, isSigner became signer, and the program ID lives in address. Fixed accounts such as system_program carry their own address. That last detail is what lets the TypeScript client resolve them for you.

If you have a legacy IDL you can't regenerate, convert it with the command that landed in the 0.30.1 release. Since 0.31 most CLI commands convert legacy IDLs on the fly, with idl fetch as the exception.

Code examplebash
anchor idl convert target/idl/my_program_legacy.json > idls/my_program.json

The same release notes warn that zero-copy accounts and custom repr layouts can convert incorrectly. Regenerate from source whenever you can.

The declare_program macro is the payoff. Drop an IDL into idls/ and it generates a typed Rust client, CPI helpers, and account parsers. No dependency on the other program's crate is needed.

Code examplerust
use anchor_lang::prelude::*;

declare_program!(my_program);
use my_program::accounts::Config;

// 1.0 renamed the helper module: utils::try_from_bytes became parsers::parse
let config = my_program::parsers::Config::parse(&account.data.borrow())?;

Steps 5 and 6: the TypeScript client

Rename the package first. On npm, @coral-xyz/anchor stops at 0.32.1 while @anchor-lang/core carries the current release as latest. Under 1.0 only the import path changes, so a search and replace does most of the work.

Code examplebash
pnpm remove @coral-xyz/anchor
pnpm add @anchor-lang/core@1.2.1
# macOS sed shown; on GNU sed drop the empty '' after -i
grep -rl "@coral-xyz/anchor" src tests | xargs sed -i '' 's#@coral-xyz/anchor#@anchor-lang/core#g'

Then fix the four behaviours that changed in 0.30 and 0.31, documented in the 0.30.0 release notes.

  • The Program constructor dropped programId; the IDL's address field replaces it.
  • .accounts() only accepts accounts the IDL can't resolve on its own.
  • Every field is camelCase.
  • You pass the IDL type as a generic because the idl argument is typed any.
Code exampletypescript
import * as anchor from "@anchor-lang/core";
import { Program } from "@anchor-lang/core";
import type { MyProgram } from "../target/types/my_program";
import idl from "../target/idl/my_program.json";

const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);

// 0.29: new Program(idl, programId, provider)
const program = new Program<MyProgram>(idl as MyProgram, provider);

// 0.29: .accounts({ config, authority, systemProgram })
// 1.2: resolvable accounts are filled from the IDL; pass only what it can't derive
await program.methods
  .updateConfig(new anchor.BN(42))
  .accounts({ authority: provider.wallet.publicKey })
  .rpc();

// Need the old behaviour during the migration? Use accountsPartial.
await program.methods
  .updateConfig(new anchor.BN(42))
  .accountsPartial({ config: configPda, authority: provider.wallet.publicKey })
  .rpc();

Expect the type checker to flag every call site. That's the point. A PDA you used to pass by hand is now derived from the seeds in the IDL, so a wrong seed fails at compile time.

This client code drops into a front end like our public Next.js dApp scaffold, a Next.js 13, TypeScript, and web3.js starter. An app on that stack would add the renamed package and the typed Program instance above.

Step 7: ship it without breaking mainnet

The program bytes change when you rebuild on new crates. Treat this like any upgrade of a live program: a staging deploy, a diff of account layouts, and a rollback plan. Two Anchor-specific steps come on top.

Close the legacy IDL account before you deploy the 1.x build. The 1.0.0 notes say to do this with the 0.32.1 CLI, because the new CLI speaks only the Program Metadata Program. Then anchor deploy uploads the new IDL by default, as it has since 0.32.0.

Code examplebash
# with avm use 0.32.1
anchor idl close <PROGRAM_ID> --provider.cluster mainnet

# with avm use 1.2.1
anchor build --verifiable
anchor deploy --provider.cluster mainnet
anchor idl fetch <PROGRAM_ID> --provider.cluster mainnet > /tmp/live-idl.json

Our release checklist for an Anchor migration:

  1. anchor build is green on CI with the pinned toolchain.
  2. anchor test passes against a localnet clone of the mainnet program accounts.
  3. A staging deploy on devnet runs the frontend's full happy path with the new client.
  4. The on-chain IDL fetched after deploy matches target/idl byte for byte.
  5. The previous .so and IDL are stored so solana program deploy can roll back in one command.

If the program also stores account data whose layout changed, that's a separate migration with its own guide. Our Solana development service covers both halves. When the in-house team is short on Rust hands, we can place a Solana developer for the migration window alone.

FAQ

Do I have to upgrade from Anchor 0.29 at all?

No, a deployed program keeps running on whatever it was built with. The pressure comes from dependencies: new SPL crates, Solana 3.x tooling, and the @anchor-lang/core client all assume the 0.30 IDL spec or newer. Staying on 0.29 means freezing your client stack with it.

Can I upgrade the TypeScript client before the program?

Yes, if you convert the IDL first. anchor idl convert turns a 0.29 IDL into the new spec, and the new client can talk to an old program through it because discriminators didn't change. Zero-copy and custom repr accounts are the exception and should be regenerated from source.

What is declare_program! for?

It generates a typed Rust client and CPI helpers for another program from its IDL file in idls/. You get account types, instruction builders, and parsers without depending on that program's crate, which removes a whole class of version conflicts. Since 1.0 it also works with anchor_client alone per the 1.0.0 release notes, so off-chain Rust services can use it.

Does Anchor 1.0 change the program ID or deployed bytes?

The program ID stays the same because it's your keypair, not the framework. The bytes change because you rebuilt on new crates, so the upgrade authority has to sign a program deploy like any other release. The on-chain IDL account does change: close the old one with the 0.32.1 CLI before deploying.

Plan your Anchor upgrade with us

We've shipped Solana builds since 2021, and the path above is the one we recommend. Try steps 1 to 3 on a branch first; the build errors tell you which stop is realistic. For a second pair of eyes on the version plan, the IDL consumers, or the release window, talk to us about your Solana program.

Continue reading

Building on Solana?

Book a 20-minute intro call, or send a short brief. We reply within one business day.

Solana development service