Skip to content

State Store

Alchemy persists resource state between deploys so it can compute diffs — comparing the desired state in your code against the current state of your infrastructure.

Each resource’s state is keyed by its fully qualified name (FQN), which includes the namespace path and logical ID. State is scoped by stack name and stage, so different stacks and environments are fully isolated.

A resource’s persisted state includes:

  • Resource type — e.g. Cloudflare.R2Bucket
  • Input properties — the props you passed when creating it
  • Output attributes — the values returned after creation
  • Instance ID — a unique identifier for this instance
  • Lifecycle statuscreated, updating, deleting, etc.
  • Bindings — data attached by policies and event sources

By default, state is stored on disk in the .alchemy/ directory:

.alchemy/
state/
MyApp/
dev_sam/
Bucket.json
Worker.json

Add .alchemy/ to your .gitignore:

Terminal window
echo ".alchemy/" >> .gitignore

Local state works for solo development. Each developer gets their own state via their default stage (dev_$USER).

For teams and CI, configure a remote state store so all deploys share the same state. Alchemy includes a Cloudflare-backed store:

Alchemy.Stack(
"MyApp",
{
providers: Cloudflare.providers(),
state: Cloudflare.state(),
},
Effect.gen(function* () {
// ...
}),
);

Cloudflare.state() is the recommended state store for projects already running on Cloudflare. It persists state in a Worker backed by a Durable Object with embedded SQLite, with the auth token and encryption key kept in your account’s Secrets Store.

The first time you run alchemy deploy, plan, or dev against a stack configured with Cloudflare.state(), Alchemy can’t find the state-store Worker on your account and pauses to ask permission before deploying it. Confirming kicks off a one-time deploy of the state-store and its supporting resources:

~/my-appDEPLOY
$

What gets created:

  • Api — the state-store Worker itself (a Durable Object with embedded SQLite, enabled on a workers.dev subdomain)
  • AlchemyStateStoreToken — bearer token used by the CLI to authenticate to the Worker, stored in your Secrets Store
  • StateStoreEncryptionKey — symmetric key used to encrypt resource state at rest inside the Durable Object’s SQLite
  • StateStoreSecrets — the Secrets Store that holds the two secrets above, scoped to your Cloudflare account
  • StateStoreAuthTokenValue / StateStoreEncryptionKeyValue — the generated random values bound into the secrets above

These resources are reused across every stack and stage that uses Cloudflare.state() on this account — no duplication per project. Subsequent runs detect the existing Worker and skip the prompt.

After the bootstrap, Alchemy writes the state-store URL and bearer token to a credentials file under your Alchemy profile directory (~/.alchemy/<profile>/cloudflare-state-store.json by default). On CI, set CI=true; the credentials are resolved from the Secrets Store on every run via a short-lived edge-preview Worker, so nothing needs to be persisted to disk.

By default the state-store Worker is named alchemy-state-store. Pass workerName to use a separate state store — for example, when you want a dedicated store per Cloudflare account or per team:

state: Cloudflare.state({ workerName: "alchemy-state-store-team-a" }),

State transitions track the lifecycle of each resource:

StatusMeaning
creatingCreate operation in progress
createdResource exists and is healthy
updatingUpdate operation in progress
updatedResource was updated successfully
deletingDelete operation in progress
replacingResource is being replaced (new one created, old pending delete)

These intermediate states (creating, updating, deleting) exist because state is persisted before the cloud operation completes. If the process crashes mid-operation, Alchemy uses the intermediate state to recover on the next deploy.

A state store is just an Effect Layer that provides the State service. If the built-in stores don’t fit, you can back one with Postgres, S3, Redis, DynamoDB, or any other backend.

See Writing a Custom State Store for a walkthrough of the StateService interface, an end-to-end implementation, and references to the built-in stores you can copy from.

For tests, Alchemy provides an in-memory state store so tests don’t touch the filesystem:

import * as TestState from "alchemy/Test/TestState";
// Seed with existing resource state
const state = TestState.state({
Bucket: {
/* ... */
},
});

The test harness (alchemy/Test/Bun and alchemy/Test/Vitest) handles state setup automatically.