Skip to content

Part 1: Your First Stack

In this first part you’ll install Alchemy and Effect, create a Stack with a Cloudflare R2 Bucket, and deploy it — all in under five minutes.

Start with an empty directory and initialize a package.json:

Terminal window
mkdir my-app && cd my-app && bun init -y

Install alchemy@next and effect@>=4.0.0-beta.78 || >=4.0.0:

Terminal window
bun add "alchemy@next" "effect@>=4.0.0-beta.78 || >=4.0.0" "@effect/platform-bun@>=4.0.0-beta.78 || >=4.0.0" "@effect/platform-node@>=4.0.0-beta.78 || >=4.0.0"

Every Alchemy program starts with a Stack — a collection of Resources managed by Providers with state tracked between deploys.

Create an alchemy.run.ts file:

alchemy.run.ts
import * as
import Alchemy
Alchemy
from "alchemy";
import * as
import Effect
Effect
from "effect/Effect";
import * as
import Layer
Layer
from "effect/Layer";
export default
import Alchemy
Alchemy
.
Stack<void, never>(stackName: string, options: Alchemy.StackProps<never>, eff: Effect.Effect<void, ConfigError, Alchemy.StackServices>): Effect.Effect<Alchemy.CompiledStack<void, any>, ConfigError, never> (+2 overloads)
export Stack
Stack
(
"MyApp",
{
Error ts(2345) ― Argument of type '{ providers: Layer.Layer<never, never, never>; }' is not assignable to parameter of type 'StackProps<never>'. Property 'state' is missing in type '{ providers: Layer.Layer<never, never, never>; }' but required in type 'StackProps<never>'.
StackProps<never>.providers: Layer.Layer<never, never, Alchemy.StackServices>
providers
:
import Layer
Layer
.
const empty: Layer.Layer<never, never, never>

An empty layer that provides no services, cannot fail, has no requirements, and performs no construction or finalization work.

When to use

Use as the no-op branch when conditionally composing layers.

Example (Disabling optional lifecycle work)

import { Console, Layer } from "effect"
declare const flag: boolean
const StartupLogLive = flag
? Layer.effectDiscard(Console.log("application starting"))
: Layer.empty

@seeeffectDiscard for running an effect while providing no services

@categoryconstructors

@since2.0.0

empty
,
},
import Effect
Effect
.
const gen: <never, void>(f: () => Generator<never, void, never>) => Effect.Effect<void, never, never> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@categoryconstructors

@since2.0.0

gen
(function* () {
// we'll add resources here next
}),
);

TypeScript is unhappy: the state property is required. Every Stack needs a state store so Alchemy can persist resource state between deploys and compute diffs against your infrastructure.

For this tutorial we’ll use Cloudflare.state(), which persists state in a Cloudflare-hosted Worker backed by a Durable Object:

alchemy.run.ts
import * as
import Alchemy
Alchemy
from "alchemy";
import * as
import Cloudflare
Cloudflare
from "alchemy/Cloudflare";
import * as
import Effect
Effect
from "effect/Effect";
import * as
import Layer
Layer
from "effect/Layer";
export default
import Alchemy
Alchemy
.
Stack<void, never>(stackName: string, options: Alchemy.StackProps<never>, eff: Effect.Effect<void, ConfigError, Alchemy.StackServices>): Effect.Effect<Alchemy.CompiledStack<void, any>, ConfigError, never> (+2 overloads)
export Stack
Stack
(
"MyApp",
{
StackProps<never>.providers: Layer.Layer<never, never, Alchemy.StackServices>
providers
:
import Layer
Layer
.
const empty: Layer.Layer<never, never, never>

An empty layer that provides no services, cannot fail, has no requirements, and performs no construction or finalization work.

When to use

Use as the no-op branch when conditionally composing layers.

Example (Disabling optional lifecycle work)

import { Console, Layer } from "effect"
declare const flag: boolean
const StartupLogLive = flag
? Layer.effectDiscard(Console.log("application starting"))
: Layer.empty

@seeeffectDiscard for running an effect while providing no services

@categoryconstructors

@since2.0.0

empty
,
StackProps<Req>.state: Layer.Layer<State, never, Alchemy.StackServices>
state
:
import Cloudflare
Cloudflare
.
function state(): Layer.Layer<Profile | CredentialsStore | State | Access | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment, never, FileSystem | Path | Alchemy.AlchemyContext | HttpClient | ChildProcessSpawner | Alchemy.AuthProviders | Alchemy.Cli>
export state
state
(),
},
import Effect
Effect
.
const gen: <never, void>(f: () => Generator<never, void, never>) => Effect.Effect<void, never, never> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@categoryconstructors

@since2.0.0

gen
(function* () {
// we'll add resources here next
}),
);

The first time you run alchemy deploy, plan, or dev, Alchemy will prompt for permission and bootstrap the state-store Worker into your Cloudflare account. It looks like this:

~/my-appDEPLOY
$

This is a one-time event — the state-store Worker, its Durable Object, and the Secrets Store entries holding its auth token and encryption key are reused across every stack and stage on this Cloudflare account. See State Store for the full picture.

Resources represent cloud infrastructure — buckets, queues, functions, databases, and so on. Each resource is yield*-ed inside the Stack’s Effect generator.

Let’s add a Cloudflare R2 Bucket to our Stack and observe the type error:

alchemy.run.ts
import * as
import Alchemy
Alchemy
from "alchemy";
import * as
import Cloudflare
Cloudflare
from "alchemy/Cloudflare";
import * as
import Effect
Effect
from "effect/Effect";
import * as
import Layer
Layer
from "effect/Layer";
export default
import Alchemy
Alchemy
.
Stack<void, Cloudflare.Providers>(stackName: string, options: Alchemy.StackProps<NoInfer<Cloudflare.Providers>>, eff: Effect.Effect<void, ConfigError, Alchemy.StackServices | Cloudflare.Providers>): Effect.Effect<Alchemy.CompiledStack<void, any>, ConfigError, never> (+2 overloads)
export Stack
Stack
(
"MyApp",
{
providers:
import Layer
Layer
.
const empty: Layer.Layer<never, never, never>

An empty layer that provides no services, cannot fail, has no requirements, and performs no construction or finalization work.

When to use

Use as the no-op branch when conditionally composing layers.

Example (Disabling optional lifecycle work)

import { Console, Layer } from "effect"
declare const flag: boolean
const StartupLogLive = flag
? Layer.effectDiscard(Console.log("application starting"))
: Layer.empty

@seeeffectDiscard for running an effect while providing no services

@categoryconstructors

@since2.0.0

empty
,
Error ts(2322) ― Type 'Layer<never, never, never>' is not assignable to type 'Layer<NoInfer<Providers>, never, StackServices>'. Type 'Providers' is not assignable to type 'never'.
StackProps<Req>.state: Layer.Layer<State, never, Alchemy.StackServices>
state
:
import Cloudflare
Cloudflare
.
function state(): Layer.Layer<Profile | CredentialsStore | State | Access | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment, never, FileSystem | Path | Alchemy.AlchemyContext | HttpClient | ChildProcessSpawner | Alchemy.AuthProviders | Alchemy.Cli>
export state
state
(),
},
import Effect
Effect
.
const gen: <Effect.Effect<Cloudflare.R2Bucket, never, Cloudflare.Providers>, void>(f: () => Generator<Effect.Effect<Cloudflare.R2Bucket, never, Cloudflare.Providers>, void, never>) => Effect.Effect<void, never, Cloudflare.Providers> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@categoryconstructors

@since2.0.0

gen
(function* () {
const
const bucket: Cloudflare.R2Bucket
bucket
= yield*
import Cloudflare
Cloudflare
.
const R2Bucket: (id: string, props?: {
name?: Alchemy.Input<string | undefined>;
storageClass?: Alchemy.Input<Cloudflare.R2Bucket.StorageClass | undefined>;
jurisdiction?: Alchemy.Input<Cloudflare.R2Bucket.Jurisdiction | undefined>;
locationHint?: Alchemy.Input<Cloudflare.R2Bucket.Location | undefined>;
domains?: Alchemy.Input<Cloudflare.R2BucketCustomDomain[] | undefined>;
lifecycleRules?: Alchemy.Input<Cloudflare.R2BucketLifecycleRule[] | undefined>;
} | undefined) => Effect.Effect<...> (+2 overloads)
R2Bucket
("Bucket");
}),
);

TypeScript is telling us that Layer.empty doesn’t provide Cloudflare.Providers — the layer required by R2Bucket.

Replace Layer.empty with Cloudflare.providers() to resolve the type error:

alchemy.run.ts
import * as
import Alchemy
Alchemy
from "alchemy";
import * as
import Cloudflare
Cloudflare
from "alchemy/Cloudflare";
import * as
import Effect
Effect
from "effect/Effect";
import * as
import Layer
Layer
from "effect/Layer";
export default
import Alchemy
Alchemy
.
Stack<void, Cloudflare.Providers>(stackName: string, options: Alchemy.StackProps<NoInfer<Cloudflare.Providers>>, eff: Effect.Effect<void, ConfigError, Alchemy.StackServices | Cloudflare.Providers>): Effect.Effect<Alchemy.CompiledStack<void, any>, ConfigError, never> (+2 overloads)
export Stack
Stack
(
"MyApp",
{
StackProps<NoInfer<Providers>>.providers: Layer.Layer<NoInfer<Cloudflare.Providers>, never, Alchemy.StackServices>
providers
:
import Cloudflare
Cloudflare
.
const providers: () => Layer.Layer<Profile | CredentialsStore | Cloudflare.Providers | Alchemy.Provider<Command> | Alchemy.Provider<Alchemy.KeyPair> | Alchemy.Provider<Alchemy.Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, Alchemy.Stack | Alchemy.Stage | Scope | FileSystem | Path | Alchemy.AlchemyContext | HttpClient | ChildProcessSpawner | Alchemy.AuthProviders>

Cloudflare providers, bindings, and credentials for Worker-based stacks.

providers
(),
StackProps<Req>.state: Layer.Layer<State, never, Alchemy.StackServices>
state
:
import Cloudflare
Cloudflare
.
function state(): Layer.Layer<Profile | CredentialsStore | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | State, never, FileSystem | Path | Alchemy.AlchemyContext | HttpClient | ChildProcessSpawner | Alchemy.AuthProviders | Alchemy.Cli>
export state
state
(),
},
import Effect
Effect
.
const gen: <Effect.Effect<Cloudflare.R2Bucket, never, Cloudflare.Providers>, void>(f: () => Generator<Effect.Effect<Cloudflare.R2Bucket, never, Cloudflare.Providers>, void, never>) => Effect.Effect<void, never, Cloudflare.Providers> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@categoryconstructors

@since2.0.0

gen
(function* () {
const
const bucket: Cloudflare.R2Bucket
bucket
= yield*
import Cloudflare
Cloudflare
.
const R2Bucket: (id: string, props?: {
name?: Alchemy.Input<string | undefined>;
storageClass?: Alchemy.Input<Cloudflare.R2Bucket.StorageClass | undefined>;
jurisdiction?: Alchemy.Input<Cloudflare.R2Bucket.Jurisdiction | undefined>;
locationHint?: Alchemy.Input<Cloudflare.R2Bucket.Location | undefined>;
domains?: Alchemy.Input<Cloudflare.R2BucketCustomDomain[] | undefined>;
lifecycleRules?: Alchemy.Input<Cloudflare.R2BucketLifecycleRule[] | undefined>;
} | undefined) => Effect.Effect<...> (+2 overloads)
R2Bucket
("Bucket");
}),
);

Now the program type-checks. The providers layer tells Alchemy how to talk to Cloudflare’s APIs, and the type system ensures you never forget to wire it up.

Stack outputs let you see important values after a deploy. Return an object from the generator to expose them:

alchemy.run.ts
import * as
import Alchemy
Alchemy
from "alchemy";
import * as
import Cloudflare
Cloudflare
from "alchemy/Cloudflare";
import * as
import Effect
Effect
from "effect/Effect";
export default
import Alchemy
Alchemy
.
Stack<{
bucketName: Alchemy.Output<string, never>;
}, Cloudflare.Providers>(stackName: string, options: Alchemy.StackProps<NoInfer<Cloudflare.Providers>>, eff: Effect.Effect<{
bucketName: Alchemy.Output<string, never>;
}, ConfigError, Alchemy.StackServices | Cloudflare.Providers>): Effect.Effect<Alchemy.CompiledStack<{
bucketName: Alchemy.Output<string, never>;
}, any>, ConfigError, never> (+2 overloads)
export Stack
Stack
(
"MyApp",
{
StackProps<NoInfer<Providers>>.providers: Layer<NoInfer<Cloudflare.Providers>, never, Alchemy.StackServices>
providers
:
import Cloudflare
Cloudflare
.
const providers: () => Layer<Profile | CredentialsStore | Cloudflare.Providers | Alchemy.Provider<Command> | Alchemy.Provider<Alchemy.KeyPair> | Alchemy.Provider<Alchemy.Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, Alchemy.Stack | Alchemy.Stage | Scope | FileSystem | Path | Alchemy.AlchemyContext | HttpClient | ChildProcessSpawner | Alchemy.AuthProviders>

Cloudflare providers, bindings, and credentials for Worker-based stacks.

providers
(),
StackProps<Req>.state: Layer<State, never, Alchemy.StackServices>
state
:
import Cloudflare
Cloudflare
.
function state(): Layer<Profile | CredentialsStore | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | State, never, FileSystem | Path | Alchemy.AlchemyContext | HttpClient | ChildProcessSpawner | Alchemy.AuthProviders | Alchemy.Cli>
export state
state
(),
},
import Effect
Effect
.
const gen: <Effect.Effect<Cloudflare.R2Bucket, never, Cloudflare.Providers>, {
bucketName: Alchemy.Output<string, never>;
}>(f: () => Generator<Effect.Effect<Cloudflare.R2Bucket, never, Cloudflare.Providers>, {
bucketName: Alchemy.Output<string, never>;
}, never>) => Effect.Effect<{
bucketName: Alchemy.Output<string, never>;
}, never, Cloudflare.Providers> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@categoryconstructors

@since2.0.0

gen
(function* () {
const
const bucket: Cloudflare.R2Bucket
bucket
= yield*
import Cloudflare
Cloudflare
.
const R2Bucket: (id: string, props?: {
name?: Alchemy.Input<string | undefined>;
storageClass?: Alchemy.Input<Cloudflare.R2Bucket.StorageClass | undefined>;
jurisdiction?: Alchemy.Input<Cloudflare.R2Bucket.Jurisdiction | undefined>;
locationHint?: Alchemy.Input<Cloudflare.R2Bucket.Location | undefined>;
domains?: Alchemy.Input<Cloudflare.R2BucketCustomDomain[] | undefined>;
lifecycleRules?: Alchemy.Input<Cloudflare.R2BucketLifecycleRule[] | undefined>;
} | undefined) => Effect.Effect<...> (+2 overloads)
R2Bucket
("Bucket");
return {
bucketName:
const bucket: Cloudflare.R2Bucket
bucket
.bucketName,
bucketName: Alchemy.Output<string, never>
};
}),
);

Run alchemy deploy to create the Bucket on Cloudflare:

Terminal window
bun alchemy deploy

The first time you deploy, Alchemy walks each provider in your stack through an interactive login and saves the credentials to your default profile at ~/.alchemy/profiles.json. For Cloudflare you can sign in with OAuth in the browser or paste an API token — no environment variables or wrangler login required.

Plan: 1 to create
+ Bucket (Cloudflare.R2Bucket)

Proceed?
◉ Yes ○ No
 Bucket (Cloudflare.R2Bucket) created
{
  bucketName: "myapp-bucket-a1b2c3d4e5",
}

Alchemy shows a plan, asks for confirmation, creates the resource, and prints the stack outputs. Your bucket is live on Cloudflare.

Your newly created R2 bucket will be listed on the Cloudflare R2 Object Storage Dashboard.

Run alchemy deploy again. Because nothing changed, the bucket shows as a no-op:

Plan: no changes

{
  bucketName: "myapp-bucket-a1b2c3d4e5",
}

This is the core loop — declare resources in code, deploy, and Alchemy figures out what changed.

You now have:

  • An alchemy.run.ts with a Stack and a Cloudflare R2 Bucket
  • A live bucket deployed to your Cloudflare account
  • Stack outputs showing the bucket name

In Part 2, you’ll add a Cloudflare Worker that uses this bucket to serve HTTP requests.