Skip to content

Upgrades & state

An upgrade replaces an app’s code and keeps its state. The upgrade is an entry in the app’s own log; from that index on, every replica runs the new component against the state the old one wrote (Upgrades & migrations covers who may upgrade and organization-wide upgrade runs).

State lives under keys derived from the #[app::state] field names and the table and record layouts (Tables & Collections):

  • Safe: new methods; new events and errors; new fields on the state struct (a new field is a new key range, empty until written); new tables; new indexes on a new table.
  • Not safe: renaming a state field (its old data is left under the old name), changing a field’s collection type or value type, or changing a record’s key, fields or field types. Old rows would no longer decode as the new type.

When a shape has to change, add a new field or table beside the old one, write new data there, and read the old one until it is empty.

#[app::state(version = N)] records N in the app’s ABI, so clients and the node’s upgrade gate can tell which state layout a bundle expects. Bump it when the state’s shape changes.

Keep each release’s signed .mpk beside the crate and run a migration scenario from it to the build under test: the steps before its { "op": "upgrade" } write state with the shipped code, the steps after read it with the new code (Testing apps). Ship v2 of your app walks through a release end to end: the state change, the test, publishing, and upgrading one instance and then an organization, with what to do about each refusal.