1---
2title: State Store
3description: How Alchemy persists resource state between deploys to compute diffs and track infrastructure.
4---
5
6import StateStoreBootstrap from "../../../components/marketing-islands/StateStoreBootstrap.tsx";
7
8Alchemy persists resource state between deploys so it can compute
9diffs — comparing the desired state in your code against the current
10state of your infrastructure.
11
12## How state is stored
13
14Each resource's state is keyed by its **fully qualified name** (FQN),
15which includes the namespace path and
16[logical ID](/infrastructure-as-code/resource#logical-id). State is
17scoped by [stack name](/infrastructure-as-code/stack) and
18[**stage**](/environments/stages), so different stacks and
19environments are fully isolated.
20
21A resource's persisted state includes:
22
23- **Resource type** — e.g. `Cloudflare.R2.Bucket`
24- **Input properties** — the props you passed when creating it
25- **Output attributes** — the values returned after creation
26- **Instance ID** — a unique identifier for this instance
27- **Lifecycle status** — `created`, `updating`, `deleting`, etc.
28- **[Bindings](/infrastructure-as-effects/binding)** — data attached by policies and event sources
29
30## Local state
31
32By default, state is stored on disk in the `.alchemy/` directory:
33
34```
35.alchemy/
36 state/
37 MyApp/
38 dev_sam/
39 Bucket.json
40 Worker.json
41```
42
43Add `.alchemy/` to your `.gitignore`:
44
45```sh
46echo ".alchemy/" >> .gitignore
47```
48
49Local state works for solo development. Each developer gets their own
50state via their default stage (`dev_$USER`).
51
52## Remote state
53
54For teams and CI, configure a remote state store so all deploys share
55the same state. Alchemy includes a Cloudflare-backed store:
56
57```typescript
58Alchemy.Stack(
59 "MyApp",
60 {
61 providers: Cloudflare.providers(),
62 state: Cloudflare.state(),
63 },
64 Effect.gen(function* () {
65 // ...
66 }),
67);
68```
69
70## Cloudflare state store
71
72`Cloudflare.state()` is the recommended state store for projects
73already running on Cloudflare. It persists state in a Worker backed
74by a Durable Object with embedded SQLite, with the auth token and
75encryption key kept in your account's Secrets Store.
76
77### First-run bootstrap
78
79The first time you run `alchemy deploy`, `plan`, or `dev` against a
80stack configured with `Cloudflare.state()`, Alchemy can't find the
81state-store Worker on your account and pauses to ask permission
82before deploying it. Confirming kicks off a one-time deploy of the
83state-store and its supporting resources:
84
85<StateStoreBootstrap client:visible />
86
87What gets created:
88
89- **`Api`** — the state-store Worker itself (a Durable Object with
90 embedded SQLite, enabled on a `workers.dev` subdomain)
91- **`AlchemyStateStoreToken`** — bearer token used by the CLI to
92 authenticate to the Worker, stored in your Secrets Store
93- **`StateStoreEncryptionKey`** — symmetric key used to encrypt
94 resource state at rest inside the Durable Object's SQLite
95- **`StateStoreSecrets`** — the Secrets Store that holds the two
96 secrets above, scoped to your Cloudflare account
97- **`StateStoreAuthTokenValue` / `StateStoreEncryptionKeyValue`** —
98 the generated random values bound into the secrets above
99
100These resources are reused across every stack and stage that uses
101`Cloudflare.state()` on this account — no duplication per project.
102Subsequent runs detect the existing Worker and skip the prompt.
103
104### Where credentials live
105
106After the bootstrap, Alchemy writes the state-store URL and bearer
107token to a credentials file under your Alchemy profile directory
108(`~/.alchemy/<profile>/cloudflare-state-store.json` by default). On
109CI, set `CI=true`; the credentials are resolved from the Secrets
110Store on every run via a short-lived edge-preview Worker, so nothing
111needs to be persisted to disk.
112
113### Customizing the worker name
114
115By default the state-store Worker is named `alchemy-state-store`.
116Pass `workerName` to use a separate state store — for example, when
117you want a dedicated store per Cloudflare account or per team:
118
119```typescript
120state: Cloudflare.state({ workerName: "alchemy-state-store-team-a" }),
121```
122
123## Postgres state store
124
125`postgresState()` persists state in a Postgres database you already run, and it locks: each `(stack, stage)` holds a session advisory lock for the duration of a run, so two concurrent deploys of the same stage cannot interleave.
126
127```typescript
128import { postgresState } from "alchemy/State/PostgresState";
129import * as Config from "effect/Config";
130
131Alchemy.Stack(
132 "MyApp",
133 {
134 providers: Prisma.providers(),
135 state: postgresState({ url: Config.Redacted("STATE_DATABASE_URL") }),
136 },
137 Effect.gen(function* () {
138 // ...
139 }),
140);
141```
142
143It is imported from `alchemy/State/PostgresState` rather than the `State` barrel because it depends on the optional `pg` driver, which must stay out of bundled worker code. Install `pg` and `@effect/sql-pg` to use it.
144
145## State and lifecycle
146
147State transitions track the lifecycle of each resource:
148
149| Status | Meaning |
150| ----------- | ---------------------------------------------------------------- |
151| `creating` | Create operation in progress |
152| `created` | Resource exists and is healthy |
153| `updating` | Update operation in progress |
154| `updated` | Resource was updated successfully |
155| `deleting` | Delete operation in progress |
156| `replacing` | Resource is being replaced (new one created, old pending delete) |
157
158These intermediate states (`creating`, `updating`, `deleting`) exist
159because state is persisted before the cloud operation completes. If
160the process crashes mid-operation, Alchemy uses the intermediate
161state to recover on the next deploy.
162
163## In-memory state (testing)
164
165For tests, Alchemy provides an in-memory state store so tests don't
166touch the filesystem:
167
168```typescript
169import * as TestState from "alchemy/Test/TestState";
170
171// Seed with existing resource state
172const state = TestState.state({
173 Bucket: {
174 /* ... */
175 },
176});
177```
178
179The test harness (`alchemy/Test/Bun` and `alchemy/Test/Vitest`)
180handles state setup automatically.
181
182## Writing your own state store
183
184A state store is just an Effect `Layer` that provides the `State`
185service — back it with Postgres, S3, Redis, DynamoDB, or any other
186backend. See [Custom State Store](/state-store/custom-state-store) for
187the full walkthrough.
188
189## Where next
190
191- [Custom State Store](/state-store/custom-state-store) — implement the
192 `StateService` interface end-to-end.
193- [Testing Providers](/testing/testing-providers) — the in-memory store in practice.
194- [References](/infrastructure-as-code/references) — reading another
195 stack's outputs.