State Management
Alchemy maintains a persistent state file for each resource to track infrastructure across deployments. This state enables Alchemy to detect changes, perform updates, and clean up orphaned resources.
What is State?
State in Alchemy is a JSON representation of your deployed infrastructure that includes:
Resource metadata : ID, type, fully qualified name
Resource props : Input parameters from previous deployment
Resource output : Actual values returned by the provider
Custom data : Additional state stored by resources
Status : Current lifecycle status of the resource
State files are how Alchemy knows what’s already deployed and what needs to change.
State Structure
Each resource’s state follows this structure:
interface State {
// Resource type (e.g., "cloudflare::Worker")
kind : string ;
// Logical ID
id : string ;
// Fully qualified name (e.g., "my-app/dev/api")
fqn : string ;
// Sequence number for ordering
seq : number ;
// Current lifecycle status
status : "creating" | "created" | "updating" | "updated" | "deleting" | "deleted" ;
// Input props from last deployment
props : ResourceProps ;
// Previous props (used during updates)
oldProps ?: ResourceProps ;
// Resource output from provider
output : Resource ;
// Custom state data
data : Record < string , any >;
}
Where State is Stored
By default, state is stored in the .alchemy directory:
.alchemy/
my-app/
dev/
api.json # Worker resource state
database.json # Database resource state
prod/
api.json
database.json
Each file contains the state for one resource in one stage.
The .alchemy directory should be added to .gitignore for local development. For production, use a persistent state store.
State Lifecycle
Creating State
When a resource is first created:
Resource creation
Alchemy invokes the resource handler in “create” phase
State initialization
State file is created with status “creating”
Provider call
Resource is provisioned via the provider’s API
State persistence
Final state is saved with status “created” and output values
const worker = await Worker ( "api" , {
entrypoint: "./src/api.ts"
});
// State file: .alchemy/my-app/dev/api.json created
Updating State
When resource props change:
State comparison
Alchemy compares new props with props in state
Update detection
If different, resource handler is called in “update” phase
State update
Previous props are stored in oldProps, new props replace props
Provider update
Resource is updated via provider API
State save
Updated state is persisted with new output values
// First deployment
const worker = await Worker ( "api" , {
entrypoint: "./src/api.ts" ,
compatibilityFlags: [ "nodejs_compat" ]
});
// Second deployment - props changed
const worker = await Worker ( "api" , {
entrypoint: "./src/api.ts" ,
compatibilityFlags: [ "nodejs_compat" , "streams_enable_constructors" ]
});
// Worker is updated, state reflects new props
Deleting State
When a resource is removed from code:
Orphan detection
During finalization, Alchemy detects resources in state but not in code
Deletion phase
Resource handler is called with phase “delete”
Cleanup
Resource is destroyed via provider API
State removal
State file is deleted
// First deployment
const worker = await Worker ( "api" , { ... });
const bucket = await R2Bucket ( "storage" , { ... });
// Second deployment - bucket removed
const worker = await Worker ( "api" , { ... });
// During finalization:
// - bucket is identified as orphaned
// - bucket is destroyed
// - bucket.json is deleted
State Stores
Alchemy supports pluggable state storage backends.
File System State Store (Default)
Stores state in local .alchemy directory:
const app = await alchemy ( "my-app" );
// Uses FileSystemStateStore by default
File system state is not suitable for team environments or CI/CD. Use a persistent state store for production.
Cloudflare State Store
Stores state in Cloudflare KV:
import { CloudflareStateStore } from "alchemy/cloudflare" ;
const app = await alchemy ( "my-app" , {
stateStore : ( scope ) => new CloudflareStateStore ( scope , {
accountId: process . env . CLOUDFLARE_ACCOUNT_ID ! ,
apiToken: process . env . CLOUDFLARE_API_TOKEN ! ,
namespaceId: process . env . STATE_KV_NAMESPACE_ID !
})
});
AWS S3 State Store
Stores state in S3:
import { S3StateStore } from "alchemy/aws" ;
const app = await alchemy ( "my-app" , {
stateStore : ( scope ) => new S3StateStore ( scope , {
bucket: "my-alchemy-state" ,
region: "us-east-1"
})
});
Custom State Store
Implement your own state store:
import type { StateStore , Scope , State } from "alchemy" ;
class MyStateStore implements StateStore {
constructor ( private scope : Scope ) {}
async init () : Promise < void > {
// Initialize storage backend
}
async deinit () : Promise < void > {
// Cleanup storage backend
}
async list () : Promise < string []> {
// Return all resource IDs
}
async count () : Promise < number > {
// Return number of resources
}
async get ( key : string ) : Promise < State | undefined > {
// Retrieve state for resource
}
async getBatch ( ids : string []) : Promise < Record < string , State >> {
// Retrieve multiple states
}
async all () : Promise < Record < string , State >> {
// Retrieve all states
}
async set ( key : string , value : State ) : Promise < void > {
// Persist state for resource
}
async delete ( key : string ) : Promise < void > {
// Remove state for resource
}
}
const app = await alchemy ( "my-app" , {
stateStore : ( scope ) => new MyStateStore ( scope )
});
Custom state stores enable integration with any storage backend: databases, cloud storage, version control, etc.
State Operations
Reading State
Access state within a resource:
export const MyResource = Resource (
"provider::MyResource" ,
async function ( this : Context < MyResource >, id : string , props : MyResourceProps ) {
// Current state
const state = this . output ; // undefined in create phase
// Previous props
const prevProps = this . props ; // undefined in create phase
if ( this . phase === "update" ) {
console . log ( "Previous name:" , this . output . name );
console . log ( "New name:" , props . name );
}
// ...
}
);
Custom State Data
Store additional data in state:
export const MyResource = Resource (
"provider::MyResource" ,
async function ( this : Context < MyResource >, id : string , props : MyResourceProps ) {
// Store custom data
await this . set ( "deployCount" ,
( await this . get < number >( "deployCount" ) ?? 0 ) + 1
);
// Read custom data
const deployCount = await this . get < number >( "deployCount" );
console . log ( `Deployed ${ deployCount } times` );
// Delete custom data
await this . delete ( "lastError" );
// ...
}
);
Custom state data persists across deployments and is separate from resource props and output.
Scope State
Scopes can also store state:
await alchemy . run ( "api" , async ( scope ) => {
// Store at scope level
await scope . set ( "version" , "2.0.0" );
// Read from scope
const version = await scope . get < string >( "version" );
// Delete from scope
await scope . delete ( "version" );
});
State Serialization
Alchemy automatically serializes complex types:
Secrets
Secrets are encrypted before storage:
const worker = await Worker ( "api" , {
bindings: {
API_KEY: alchemy . secret . env . API_KEY
}
});
// In state file:
// {
// "props": {
// "bindings": {
// "API_KEY": {
// "@secret": "encrypted-base64-value"
// }
// }
// }
// }
Resources
Resource references are serialized by FQN:
const database = await D1Database ( "db" , { ... });
const worker = await Worker ( "api" , {
bindings: {
DB: database
}
});
// In state file:
// {
// "props": {
// "bindings": {
// "DB": {
// "@resource": "my-app/dev/db"
// }
// }
// }
// }
Dates and Special Types
Custom serializers handle complex types:
const resource = await MyResource ( "item" , {
createdAt: new Date ()
});
// Date is serialized to ISO string
// Custom deserializers restore original types on read
State Locking
Alchemy uses mutexes to prevent concurrent state modifications:
// Automatic locking during state operations
await scope . set ( "key" , "value" );
// Lock is released after operation
// Manual locking for complex operations
await scope . dataMutex . lock ( async () => {
const current = await scope . get < number >( "counter" );
await scope . set ( "counter" , current + 1 );
});
State locking prevents race conditions when multiple resources access shared state.
State Migration
When changing state stores:
Export existing state
Read all state from current store
Initialize new store
Configure new state store
Import state
Write state to new store
Verify
Run deployment with --read to verify state
Cutover
Update all deployments to use new store
// Migration script
import { FileSystemStateStore } from "alchemy" ;
import { S3StateStore } from "alchemy/aws" ;
const oldStore = new FileSystemStateStore ( scope );
const newStore = new S3StateStore ( scope , { ... });
const states = await oldStore . all ();
for ( const [ key , state ] of Object . entries ( states )) {
await newStore . set ( key , state );
}
Debugging State
Inspect state files directly:
# View all resources in dev stage
ls .alchemy/my-app/dev/
# View specific resource state
cat .alchemy/my-app/dev/api.json | jq .
Or programmatically:
await alchemy . run ( "debug" , async ( scope ) => {
const resourceIds = await scope . state . list ();
console . log ( "Resources:" , resourceIds );
for ( const id of resourceIds ) {
const state = await scope . state . get ( id );
console . log ( ` ${ id } :` , state );
}
});
State Best Practices
Use persistent storage in production
File system state doesn’t work in CI/CD or team environments
Never manually edit state
Let Alchemy manage state automatically
Back up state regularly
State is critical - losing it means losing track of your infrastructure
Use different stores per stage
Isolate dev and prod state completely
Monitor state size
Large state files can slow down deployments
Troubleshooting
State Corruption
If state is corrupted:
# Force re-creation of resources
bun ./alchemy.run.ts --force
Lost State
If state is lost:
# Adopt existing resources
bun ./alchemy.run.ts --adopt
State Conflicts
If state conflicts with reality:
# Read current state without changes
bun ./alchemy.run.ts --read
# Force update to match code
bun ./alchemy.run.ts --force
Advanced Patterns
State Versioning
Track state versions:
export const MyResource = Resource (
"provider::MyResource" ,
async function ( this : Context < MyResource >, id : string , props : MyResourceProps ) {
const version = ( await this . get < number >( "stateVersion" ) ?? 0 ) + 1 ;
await this . set ( "stateVersion" , version );
// Perform migrations based on version
if ( version === 2 ) {
// Migrate from v1 to v2
}
// ...
}
);
State Inspection
Expose state for debugging:
await alchemy . run ( "inspect" , async ( scope ) => {
const allState = await scope . state . all ();
console . log ( "State summary:" );
for ( const [ id , state ] of Object . entries ( allState )) {
console . log ( ` ${ id } : ${ state . kind } ( ${ state . status } )` );
}
});
Next Steps
Scopes Understand scope-level state management
Resources Learn how resources interact with state
Secrets See how secrets are encrypted in state
Lifecycle Understand state transitions during lifecycle