Skip to content

Part 2: Add a Worker

In Part 1 you deployed an R2 Bucket. Now you’ll create a Cloudflare Worker that reads and writes objects in that bucket over HTTP.

Create src/worker.ts. A Worker is a special kind of Resource — it has both an infrastructure definition and a runtime implementation expressed as an Effect.

src/worker.ts
import * as
import Cloudflare
Cloudflare
from "alchemy/Cloudflare";
import * as
import Effect
Effect
from "effect/Effect";
import * as
import HttpServerResponse
HttpServerResponse
from "effect/unstable/http/HttpServerResponse";
export default
import Cloudflare
Cloudflare
.
const Worker: <{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, never, never>(id: string, props: InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never> | Effect.Effect<InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never>, never, never>, impl: Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, ConfigError, never>) => Effect.Effect<...> (+3 overloads)
Worker
(
"Worker",
{
main?: Input<string | undefined>

Path to the Worker's entry module. Bundled with rolldown before upload. Mutually exclusive with

script

— provide exactly one.

main
: import.

The type of import.meta.

If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.

meta
.
ImportMeta.filename: string

Alias of import.meta.path. Exists for Node.js compatibility

filename
,
},
import Effect
Effect
.
const gen: <never, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}>(f: () => Generator<never, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, never>) => Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, 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* () {
return {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>
fetch
:
import Effect
Effect
.
const gen: <never, HttpServerResponse.HttpServerResponse>(f: () => Generator<never, HttpServerResponse.HttpServerResponse, never>) => Effect.Effect<HttpServerResponse.HttpServerResponse, 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* () {
return
import HttpServerResponse
HttpServerResponse
.
const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse

Creates an HTTP response whose body is a string.

@categoryconstructors

@since4.0.0

text
("Hello, world!");
}),
};
}),
);

Now let’s bind the R2 Bucket from Part 1 to our new Worker. The problem is that the Bucket is declared inside the Stack’s generator in alchemy.run.ts — we can’t import it from there.

A common pattern in Alchemy is to give each resource its own file. Create src/bucket.ts:

src/bucket.ts
import * as
import Cloudflare
Cloudflare
from "alchemy/Cloudflare";
export const
const Bucket: Effect<Cloudflare.R2Bucket, never, Cloudflare.Providers>
Bucket
=
import Cloudflare
Cloudflare
.
const R2Bucket: (id: string, props?: {
name?: Input<string | undefined>;
storageClass?: Input<Cloudflare.R2Bucket.StorageClass | undefined>;
jurisdiction?: Input<Cloudflare.R2Bucket.Jurisdiction | undefined>;
locationHint?: Input<Cloudflare.R2Bucket.Location | undefined>;
domains?: Input<Cloudflare.R2BucketCustomDomain[] | undefined>;
lifecycleRules?: Input<Cloudflare.R2BucketLifecycleRule[] | undefined>;
} | undefined) => Effect<Cloudflare.R2Bucket, never, Cloudflare.Providers> (+2 overloads)
R2Bucket
("Bucket");

Update alchemy.run.ts to import it instead of declaring it inline:

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 {
import Bucket
Bucket
} from "./src/bucket.ts";
export default
import Alchemy
Alchemy
.
Stack<{
bucketName: Alchemy.Output<string, never>;
}, unknown>(stackName: string, options: Alchemy.StackProps<unknown>, eff: Effect.Effect<{
bucketName: Alchemy.Output<string, never>;
}, ConfigError, unknown>): Effect.Effect<Alchemy.CompiledStack<{
bucketName: Alchemy.Output<string, never>;
}, any>, ConfigError, never> (+2 overloads)
export Stack
Stack
(
"MyApp",
{
StackProps<unknown>.providers: Layer<unknown, 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: <any, {
bucketName: Alchemy.Output<string, never>;
}>(f: () => Generator<any, {
bucketName: Alchemy.Output<string, never>;
}, never>) => Effect.Effect<{
bucketName: Alchemy.Output<string, never>;
}, unknown, unknown> (+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");
const
const bucket: any
bucket
= yield*
import Bucket
Bucket
;
return {
bucketName: Alchemy.Output<string, never>
bucketName
:
const bucket: Cloudflare.R2Bucket
bucket
.
bucketName: Alchemy.Output<string, never>
bucketName
,
};
}),
);

Now the Worker can import Bucket and bind it in the Init phase:

src/worker.ts
import * as
import Cloudflare
Cloudflare
from "alchemy/Cloudflare";
import * as
import Effect
Effect
from "effect/Effect";
import * as
import HttpServerResponse
HttpServerResponse
from "effect/unstable/http/HttpServerResponse";
import {
const Bucket: Effect.Effect<Cloudflare.R2Bucket, never, Cloudflare.Providers>
Bucket
} from "./bucket.ts";
export default
import Cloudflare
Cloudflare
.
const Worker: <{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, never, Cloudflare.WorkerServices | PlatformServices>(id: string, props: InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never> | Effect.Effect<InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never>, never, never>, impl: Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, ConfigError, Cloudflare.WorkerServices | PlatformServices>) => Effect.Effect<...> (+3 overloads)
Worker
(
"Worker",
{
main?: Input<string | undefined>

Path to the Worker's entry module. Bundled with rolldown before upload. Mutually exclusive with

script

— provide exactly one.

main
: import.

The type of import.meta.

If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.

meta
.
ImportMeta.filename: string

Alias of import.meta.path. Exists for Node.js compatibility

filename
,
},
import Effect
Effect
.
const gen: <Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.Providers | Cloudflare.R2BucketBinding>, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}>(f: () => Generator<Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.Providers | Cloudflare.R2BucketBinding>, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, never>) => Effect.Effect<...> (+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* () {
Error ts(2345) ― Type 'R2BucketBinding' is not assignable to type 'WorkerServices | PlatformServices'.
const
const bucket: Cloudflare.R2BucketClient
bucket
= yield*
import Cloudflare
Cloudflare
.
const R2Bucket: ResourceClassWithMethods<Cloudflare.R2Bucket, {
readonly bind: (<Req = never>(args_0: Input<Cloudflare.R2Bucket> | Effect.Effect<Cloudflare.R2Bucket, never, Req>) => Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding | Req>) & ((bucket: Cloudflare.R2Bucket) => Effect.Effect<any, any, any>);
}>

A Cloudflare R2 object storage bucket with S3-compatible API.

R2 provides zero-egress-fee object storage. Create a bucket as a resource, then bind it to a Worker to read and write objects at runtime.

@sectionCreating a Bucket

@example

Basic R2 bucket

const bucket = yield* Cloudflare.R2Bucket("MyBucket");

@example

Bucket with location hint

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
locationHint: "wnam",
});

@sectionBinding to a Worker

@example

Reading and writing objects

const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
// Write an object
yield* bucket.put("hello.txt", "Hello, World!");
// Read an object
const object = yield* bucket.get("hello.txt");
if (object) {
const text = yield* object.text();
}

@example

Streaming upload with content length

const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
yield* bucket.put("upload.bin", request.stream, {
contentLength: Number(request.headers["content-length"] ?? 0),
});

@section

Custom Domains

Attach one or more custom domains to serve bucket objects from a hostname you control. The domain's zone must already exist in your Cloudflare account; the zone is inferred from the hostname when omitted, or you can pass a Cloudflare.Zone resource, a zone ID, or any hostname inside the zone via the zone field.

@example

Single custom domain

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [{ name: "assets.example.com" }],
});

@example

Multiple custom domains

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [
{ name: "assets.example.com" },
{ name: "static.example.com" },
],
});

@example

Disable a custom domain without removing it

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [{ name: "assets.example.com", enabled: false }],
});

@example

Custom domain with explicit zone and TLS settings

const zone = yield* Cloudflare.Zone("ExampleZone", {
name: "example.com",
});
const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [
{
name: "assets.example.com",
zone,
minTLS: "1.2",
},
],
});

@section

Object Lifecycle Rules

Configure lifecycle rules to automatically delete objects, abort incomplete multipart uploads, or transition objects to InfrequentAccess storage. Pass an empty array (or omit) to clear all rules. See the Cloudflare R2 docs for details and limits (max 1000 rules per bucket).

@example

Delete objects 30 days after upload

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "expire-old-objects",
deleteObjectsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 30 },
},
},
],
});

@example

Transition to InfrequentAccess after 60 days, delete after 365

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "archive-then-delete",
prefix: "logs/",
storageClassTransitions: [
{
condition: { type: "Age", maxAge: 60 * 60 * 24 * 60 },
storageClass: "InfrequentAccess",
},
],
deleteObjectsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 365 },
},
},
],
});

@example

Abort incomplete multipart uploads after 7 days

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "abort-stale-uploads",
abortMultipartUploadsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 7 },
},
},
],
});

R2Bucket
.
bind: <Cloudflare.Providers>(args_0: Effect.Effect<Cloudflare.R2Bucket, never, Cloudflare.Providers> | Input<Cloudflare.R2Bucket>) => Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.Providers | Cloudflare.R2BucketBinding> (+1 overload)
bind
(
const Bucket: Effect.Effect<Cloudflare.R2Bucket, never, Cloudflare.Providers>
Bucket
);
return {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>
fetch
:
import Effect
Effect
.
const gen: <never, HttpServerResponse.HttpServerResponse>(f: () => Generator<never, HttpServerResponse.HttpServerResponse, never>) => Effect.Effect<HttpServerResponse.HttpServerResponse, 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* () {
return
import HttpServerResponse
HttpServerResponse
.
const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse

Creates an HTTP response whose body is a string.

@categoryconstructors

@since4.0.0

text
("Hello, world!");
}),
};
}),
);

The previous step showed a type error — R2Bucket.bind requires the R2BucketBinding service. Fix it by piping the outer Effect through R2BucketBindingLive:

src/worker.ts
import * as
import Cloudflare
Cloudflare
from "alchemy/Cloudflare";
import * as
import Effect
Effect
from "effect/Effect";
import * as
import HttpServerResponse
HttpServerResponse
from "effect/unstable/http/HttpServerResponse";
import {
import Bucket
Bucket
} from "./bucket.ts";
export default
import Cloudflare
Cloudflare
.
const Worker: <{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>(id: string, props: InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never> | Effect.Effect<InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never>, never, never>, impl: Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)
Worker
(
"Worker",
{
main?: Input<string | undefined>

Path to the Worker's entry module. Bundled with rolldown before upload. Mutually exclusive with

script

— provide exactly one.

main
: import.

The type of import.meta.

If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.

meta
.
ImportMeta.filename: string

Alias of import.meta.path. Exists for Node.js compatibility

filename
},
import Effect
Effect
.
const gen: <Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding>, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}>(f: () => Generator<Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding>, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, never>) => Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, never, Cloudflare.R2BucketBinding> (+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.R2BucketClient
bucket
= yield*
import Cloudflare
Cloudflare
.
const R2Bucket: ResourceClassWithMethods<Cloudflare.R2Bucket, {
readonly bind: (<Req = never>(args_0: Input<Cloudflare.R2Bucket> | Effect.Effect<Cloudflare.R2Bucket, never, Req>) => Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding | Req>) & ((bucket: Cloudflare.R2Bucket) => Effect.Effect<any, any, any>);
}>

A Cloudflare R2 object storage bucket with S3-compatible API.

R2 provides zero-egress-fee object storage. Create a bucket as a resource, then bind it to a Worker to read and write objects at runtime.

@sectionCreating a Bucket

@example

Basic R2 bucket

const bucket = yield* Cloudflare.R2Bucket("MyBucket");

@example

Bucket with location hint

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
locationHint: "wnam",
});

@sectionBinding to a Worker

@example

Reading and writing objects

const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
// Write an object
yield* bucket.put("hello.txt", "Hello, World!");
// Read an object
const object = yield* bucket.get("hello.txt");
if (object) {
const text = yield* object.text();
}

@example

Streaming upload with content length

const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
yield* bucket.put("upload.bin", request.stream, {
contentLength: Number(request.headers["content-length"] ?? 0),
});

@section

Custom Domains

Attach one or more custom domains to serve bucket objects from a hostname you control. The domain's zone must already exist in your Cloudflare account; the zone is inferred from the hostname when omitted, or you can pass a Cloudflare.Zone resource, a zone ID, or any hostname inside the zone via the zone field.

@example

Single custom domain

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [{ name: "assets.example.com" }],
});

@example

Multiple custom domains

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [
{ name: "assets.example.com" },
{ name: "static.example.com" },
],
});

@example

Disable a custom domain without removing it

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [{ name: "assets.example.com", enabled: false }],
});

@example

Custom domain with explicit zone and TLS settings

const zone = yield* Cloudflare.Zone("ExampleZone", {
name: "example.com",
});
const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [
{
name: "assets.example.com",
zone,
minTLS: "1.2",
},
],
});

@section

Object Lifecycle Rules

Configure lifecycle rules to automatically delete objects, abort incomplete multipart uploads, or transition objects to InfrequentAccess storage. Pass an empty array (or omit) to clear all rules. See the Cloudflare R2 docs for details and limits (max 1000 rules per bucket).

@example

Delete objects 30 days after upload

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "expire-old-objects",
deleteObjectsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 30 },
},
},
],
});

@example

Transition to InfrequentAccess after 60 days, delete after 365

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "archive-then-delete",
prefix: "logs/",
storageClassTransitions: [
{
condition: { type: "Age", maxAge: 60 * 60 * 24 * 60 },
storageClass: "InfrequentAccess",
},
],
deleteObjectsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 365 },
},
},
],
});

@example

Abort incomplete multipart uploads after 7 days

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "abort-stale-uploads",
abortMultipartUploadsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 7 },
},
},
],
});

R2Bucket
.
bind: <never>(args_0: Input<Cloudflare.R2Bucket> | Effect.Effect<Cloudflare.R2Bucket, never, never>) => Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding> (+1 overload)
bind
(
import Bucket
Bucket
);
return {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>
fetch
:
import Effect
Effect
.
const gen: <never, HttpServerResponse.HttpServerResponse>(f: () => Generator<never, HttpServerResponse.HttpServerResponse, never>) => Effect.Effect<HttpServerResponse.HttpServerResponse, 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* () {
return
import HttpServerResponse
HttpServerResponse
.
const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse

Creates an HTTP response whose body is a string.

@categoryconstructors

@since4.0.0

text
("Hello, world!");
}),
};
}).
Pipeable.pipe<Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, never, Cloudflare.R2BucketBinding>, Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
}, never, Cloudflare.R2BucketBinding>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const provide: <Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>(layer: Layer<Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy | Exclude<...>> (+5 overloads)

Provides dependencies to an effect using layers or a context. Use options.local to build the layer every time; by default, layers are shared between provide calls.

Example (Providing dependencies with a layer)

import { Context, Effect, Layer } from "effect"
interface Database {
readonly query: (sql: string) => Effect.Effect<string>
}
const Database = Context.Service<Database>("Database")
const DatabaseLive = Layer.succeed(Database)({
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
})
const program = Effect.gen(function*() {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
const provided = Effect.provide(program, DatabaseLive)
Effect.runPromise(provided).then(console.log)
// Output: "Result for: SELECT * FROM users"

@categoryenvironment

@since2.0.0

provide
(
import Cloudflare
Cloudflare
.
const R2BucketBindingLive: Layer<Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>
R2BucketBindingLive
)),
);

R2BucketBindingLive tells the Worker runtime how to look up the underlying R2 binding from the Cloudflare environment. Without it, bind wouldn’t know where to find the bucket at runtime.

Let’s replace the placeholder response with a PUT route that stores objects in the bucket. Add HttpServerRequest to access the incoming request:

src/worker.ts
import * as
import Cloudflare
Cloudflare
from "alchemy/Cloudflare";
import * as
import Effect
Effect
from "effect/Effect";
import {
const HttpServerRequest: Service<HttpServerRequest, HttpServerRequest>

Server-side representation of an incoming HTTP request.

Details

It extends HttpIncomingMessage with request metadata, parsed cookies, multipart accessors, WebSocket upgrade support, and a modify method for creating adjusted request views.

Service tag for the active server-side HTTP request.

When to use

Use to access the request currently being handled by HTTP server routes and middleware.

@categorymodels

@since4.0.0

@categorycontext

@since4.0.0

HttpServerRequest
} from "effect/unstable/http/HttpServerRequest";
import * as
import HttpServerResponse
HttpServerResponse
from "effect/unstable/http/HttpServerResponse";
import {
import Bucket
Bucket
} from "./bucket.ts";
export default
import Cloudflare
Cloudflare
.
const Worker: <Cloudflare.WorkerShape, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>(id: string, props: InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never> | Effect.Effect<InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never>, never, never>, impl: Effect.Effect<Cloudflare.WorkerShape, ConfigError, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>) => Effect.Effect<...> (+3 overloads)
Worker
(
"Worker",
{
main?: Input<string | undefined>

Path to the Worker's entry module. Bundled with rolldown before upload. Mutually exclusive with

script

— provide exactly one.

main
: import.

The type of import.meta.

If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.

meta
.
ImportMeta.filename: string

Alias of import.meta.path. Exists for Node.js compatibility

filename
},
import Effect
Effect
.
const gen: <Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding>, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, Cloudflare.R2Error | HttpServerError, HttpServerRequest | RuntimeContext>;
}>(f: () => Generator<Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding>, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, Cloudflare.R2Error | HttpServerError, HttpServerRequest | RuntimeContext>;
}, never>) => Effect.Effect<...> (+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.R2BucketClient
bucket
= yield*
import Cloudflare
Cloudflare
.
const R2Bucket: ResourceClassWithMethods<Cloudflare.R2Bucket, {
readonly bind: (<Req = never>(args_0: Input<Cloudflare.R2Bucket> | Effect.Effect<Cloudflare.R2Bucket, never, Req>) => Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding | Req>) & ((bucket: Cloudflare.R2Bucket) => Effect.Effect<any, any, any>);
}>

A Cloudflare R2 object storage bucket with S3-compatible API.

R2 provides zero-egress-fee object storage. Create a bucket as a resource, then bind it to a Worker to read and write objects at runtime.

@sectionCreating a Bucket

@example

Basic R2 bucket

const bucket = yield* Cloudflare.R2Bucket("MyBucket");

@example

Bucket with location hint

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
locationHint: "wnam",
});

@sectionBinding to a Worker

@example

Reading and writing objects

const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
// Write an object
yield* bucket.put("hello.txt", "Hello, World!");
// Read an object
const object = yield* bucket.get("hello.txt");
if (object) {
const text = yield* object.text();
}

@example

Streaming upload with content length

const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
yield* bucket.put("upload.bin", request.stream, {
contentLength: Number(request.headers["content-length"] ?? 0),
});

@section

Custom Domains

Attach one or more custom domains to serve bucket objects from a hostname you control. The domain's zone must already exist in your Cloudflare account; the zone is inferred from the hostname when omitted, or you can pass a Cloudflare.Zone resource, a zone ID, or any hostname inside the zone via the zone field.

@example

Single custom domain

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [{ name: "assets.example.com" }],
});

@example

Multiple custom domains

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [
{ name: "assets.example.com" },
{ name: "static.example.com" },
],
});

@example

Disable a custom domain without removing it

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [{ name: "assets.example.com", enabled: false }],
});

@example

Custom domain with explicit zone and TLS settings

const zone = yield* Cloudflare.Zone("ExampleZone", {
name: "example.com",
});
const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [
{
name: "assets.example.com",
zone,
minTLS: "1.2",
},
],
});

@section

Object Lifecycle Rules

Configure lifecycle rules to automatically delete objects, abort incomplete multipart uploads, or transition objects to InfrequentAccess storage. Pass an empty array (or omit) to clear all rules. See the Cloudflare R2 docs for details and limits (max 1000 rules per bucket).

@example

Delete objects 30 days after upload

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "expire-old-objects",
deleteObjectsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 30 },
},
},
],
});

@example

Transition to InfrequentAccess after 60 days, delete after 365

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "archive-then-delete",
prefix: "logs/",
storageClassTransitions: [
{
condition: { type: "Age", maxAge: 60 * 60 * 24 * 60 },
storageClass: "InfrequentAccess",
},
],
deleteObjectsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 365 },
},
},
],
});

@example

Abort incomplete multipart uploads after 7 days

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "abort-stale-uploads",
abortMultipartUploadsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 7 },
},
},
],
});

R2Bucket
.
bind: <never>(args_0: Input<Cloudflare.R2Bucket> | Effect.Effect<Cloudflare.R2Bucket, never, never>) => Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding> (+1 overload)
bind
(
import Bucket
Bucket
);
return {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, Cloudflare.R2Error | HttpServerError, HttpServerRequest | RuntimeContext>
fetch
:
import Effect
Effect
.
const gen: <Effect.Effect<HttpServerRequest, never, HttpServerRequest> | Effect.Effect<Cloudflare.R2Object, Cloudflare.R2Error | HttpServerError, RuntimeContext>, HttpServerResponse.HttpServerResponse>(f: () => Generator<Effect.Effect<HttpServerRequest, never, HttpServerRequest> | Effect.Effect<Cloudflare.R2Object, Cloudflare.R2Error | HttpServerError, RuntimeContext>, HttpServerResponse.HttpServerResponse, never>) => Effect.Effect<...> (+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 request: HttpServerRequest
request
= yield*
const HttpServerRequest: Service<HttpServerRequest, HttpServerRequest>

Server-side representation of an incoming HTTP request.

Details

It extends HttpIncomingMessage with request metadata, parsed cookies, multipart accessors, WebSocket upgrade support, and a modify method for creating adjusted request views.

Service tag for the active server-side HTTP request.

When to use

Use to access the request currently being handled by HTTP server routes and middleware.

@categorymodels

@since4.0.0

@categorycontext

@since4.0.0

HttpServerRequest
;
const
const key: string
key
=
const request: HttpServerRequest
request
.
HttpServerRequest.url: string
url
.
String.split(separator: string | RegExp, limit?: number): string[] (+1 overload)

Split a string into substrings using the specified separator and return them as an array.

@paramseparator A string that identifies character or characters to use in separating the string. If omitted, a single-element array containing the entire string is returned.

@paramlimit A value used to limit the number of elements returned in the array.

split
("/").
Array<string>.pop(): string | undefined

Removes the last element from an array and returns it. If the array is empty, undefined is returned and the array is not modified.

pop
()!;
if (
const request: HttpServerRequest
request
.
HttpServerRequest.method: HttpMethod
method
=== "PUT") {
yield*
const bucket: Cloudflare.R2BucketClient
bucket
.
R2BucketClient.put<HttpServerError>(key: string, value: string | ReadableStream<any> | ArrayBuffer | ArrayBufferView<ArrayBufferLike> | Blob | Stream<Uint8Array<ArrayBufferLike>, HttpServerError, never> | null, options: Cloudflare.R2PutOptions & {
contentLength: number;
}): Effect.Effect<Cloudflare.R2Object, Cloudflare.R2Error | HttpServerError, RuntimeContext> (+2 overloads)
put
(
const key: string
key
,
const request: HttpServerRequest
request
.
HttpIncomingMessage<HttpServerError>.stream: Stream<Uint8Array<ArrayBufferLike>, HttpServerError, never>
stream
, {
contentLength: number
contentLength
:
var Number: NumberConstructor
(value?: any) => number

An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.

Number
(
const request: HttpServerRequest
request
.
HttpIncomingMessage<HttpServerError>.headers: Headers
headers
["content-length"] ?? 0),
});
return
import HttpServerResponse
HttpServerResponse
.
const empty: (options?: HttpServerResponse.Options.WithContent | undefined) => HttpServerResponse.HttpServerResponse

Creates an empty HTTP response.

Details

The default status is 204.

@categoryconstructors

@since4.0.0

empty
({
status?: number | undefined
status
: 201 });
}
return
import HttpServerResponse
HttpServerResponse
.
const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse

Creates an HTTP response whose body is a string.

@categoryconstructors

@since4.0.0

text
("Hello, world!");
}),
};
}).
Pipeable.pipe<Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, Cloudflare.R2Error | HttpServerError, HttpServerRequest | RuntimeContext>;
}, never, Cloudflare.R2BucketBinding>, Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, Cloudflare.R2Error | HttpServerError, HttpServerRequest | RuntimeContext>;
}, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const provide: <Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>(layer: Layer<Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy | Exclude<...>> (+5 overloads)

Provides dependencies to an effect using layers or a context. Use options.local to build the layer every time; by default, layers are shared between provide calls.

Example (Providing dependencies with a layer)

import { Context, Effect, Layer } from "effect"
interface Database {
readonly query: (sql: string) => Effect.Effect<string>
}
const Database = Context.Service<Database>("Database")
const DatabaseLive = Layer.succeed(Database)({
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
})
const program = Effect.gen(function*() {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
const provided = Effect.provide(program, DatabaseLive)
Effect.runPromise(provided).then(console.log)
// Output: "Result for: SELECT * FROM users"

@categoryenvironment

@since2.0.0

provide
(
import Cloudflare
Cloudflare
.
const R2BucketBindingLive: Layer<Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>
R2BucketBindingLive
)),
);

TypeScript flags a type error. The bucket.put call can fail with R2Error, but a Worker’s fetch handler only allows HttpServerError or HttpBodyError. Effect tracks this in the type system — you can’t forget to handle it.

Pipe the fetch Effect through Effect.catchTag to convert R2Error into a 500 response:

src/worker.ts
import * as
import Cloudflare
Cloudflare
from "alchemy/Cloudflare";
import * as
import Effect
Effect
from "effect/Effect";
import {
const HttpServerRequest: Service<HttpServerRequest, HttpServerRequest>

Server-side representation of an incoming HTTP request.

Details

It extends HttpIncomingMessage with request metadata, parsed cookies, multipart accessors, WebSocket upgrade support, and a modify method for creating adjusted request views.

Service tag for the active server-side HTTP request.

When to use

Use to access the request currently being handled by HTTP server routes and middleware.

@categorymodels

@since4.0.0

@categorycontext

@since4.0.0

HttpServerRequest
} from "effect/unstable/http/HttpServerRequest";
import * as
import HttpServerResponse
HttpServerResponse
from "effect/unstable/http/HttpServerResponse";
import {
import Bucket
Bucket
} from "./bucket.ts";
export default
import Cloudflare
Cloudflare
.
const Worker: <{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>;
}, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>(id: string, props: InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never> | Effect.Effect<InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never>, never, never>, impl: Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)
Worker
(
"Worker",
{
main?: Input<string | undefined>

Path to the Worker's entry module. Bundled with rolldown before upload. Mutually exclusive with

script

— provide exactly one.

main
: import.

The type of import.meta.

If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.

meta
.
ImportMeta.filename: string

Alias of import.meta.path. Exists for Node.js compatibility

filename
},
import Effect
Effect
.
const gen: <Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding>, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>;
}>(f: () => Generator<Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding>, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>;
}, never>) => Effect.Effect<...> (+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.R2BucketClient
bucket
= yield*
import Cloudflare
Cloudflare
.
const R2Bucket: ResourceClassWithMethods<Cloudflare.R2Bucket, {
readonly bind: (<Req = never>(args_0: Input<Cloudflare.R2Bucket> | Effect.Effect<Cloudflare.R2Bucket, never, Req>) => Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding | Req>) & ((bucket: Cloudflare.R2Bucket) => Effect.Effect<any, any, any>);
}>

A Cloudflare R2 object storage bucket with S3-compatible API.

R2 provides zero-egress-fee object storage. Create a bucket as a resource, then bind it to a Worker to read and write objects at runtime.

@sectionCreating a Bucket

@example

Basic R2 bucket

const bucket = yield* Cloudflare.R2Bucket("MyBucket");

@example

Bucket with location hint

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
locationHint: "wnam",
});

@sectionBinding to a Worker

@example

Reading and writing objects

const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
// Write an object
yield* bucket.put("hello.txt", "Hello, World!");
// Read an object
const object = yield* bucket.get("hello.txt");
if (object) {
const text = yield* object.text();
}

@example

Streaming upload with content length

const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
yield* bucket.put("upload.bin", request.stream, {
contentLength: Number(request.headers["content-length"] ?? 0),
});

@section

Custom Domains

Attach one or more custom domains to serve bucket objects from a hostname you control. The domain's zone must already exist in your Cloudflare account; the zone is inferred from the hostname when omitted, or you can pass a Cloudflare.Zone resource, a zone ID, or any hostname inside the zone via the zone field.

@example

Single custom domain

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [{ name: "assets.example.com" }],
});

@example

Multiple custom domains

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [
{ name: "assets.example.com" },
{ name: "static.example.com" },
],
});

@example

Disable a custom domain without removing it

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [{ name: "assets.example.com", enabled: false }],
});

@example

Custom domain with explicit zone and TLS settings

const zone = yield* Cloudflare.Zone("ExampleZone", {
name: "example.com",
});
const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [
{
name: "assets.example.com",
zone,
minTLS: "1.2",
},
],
});

@section

Object Lifecycle Rules

Configure lifecycle rules to automatically delete objects, abort incomplete multipart uploads, or transition objects to InfrequentAccess storage. Pass an empty array (or omit) to clear all rules. See the Cloudflare R2 docs for details and limits (max 1000 rules per bucket).

@example

Delete objects 30 days after upload

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "expire-old-objects",
deleteObjectsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 30 },
},
},
],
});

@example

Transition to InfrequentAccess after 60 days, delete after 365

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "archive-then-delete",
prefix: "logs/",
storageClassTransitions: [
{
condition: { type: "Age", maxAge: 60 * 60 * 24 * 60 },
storageClass: "InfrequentAccess",
},
],
deleteObjectsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 365 },
},
},
],
});

@example

Abort incomplete multipart uploads after 7 days

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "abort-stale-uploads",
abortMultipartUploadsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 7 },
},
},
],
});

R2Bucket
.
bind: <never>(args_0: Input<Cloudflare.R2Bucket> | Effect.Effect<Cloudflare.R2Bucket, never, never>) => Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding> (+1 overload)
bind
(
import Bucket
Bucket
);
return {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>
fetch
:
import Effect
Effect
.
const gen: <Effect.Effect<HttpServerRequest, never, HttpServerRequest> | Effect.Effect<Cloudflare.R2Object, Cloudflare.R2Error | HttpServerError, RuntimeContext>, HttpServerResponse.HttpServerResponse>(f: () => Generator<Effect.Effect<HttpServerRequest, never, HttpServerRequest> | Effect.Effect<Cloudflare.R2Object, Cloudflare.R2Error | HttpServerError, RuntimeContext>, HttpServerResponse.HttpServerResponse, never>) => Effect.Effect<...> (+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 request: HttpServerRequest
request
= yield*
const HttpServerRequest: Service<HttpServerRequest, HttpServerRequest>

Server-side representation of an incoming HTTP request.

Details

It extends HttpIncomingMessage with request metadata, parsed cookies, multipart accessors, WebSocket upgrade support, and a modify method for creating adjusted request views.

Service tag for the active server-side HTTP request.

When to use

Use to access the request currently being handled by HTTP server routes and middleware.

@categorymodels

@since4.0.0

@categorycontext

@since4.0.0

HttpServerRequest
;
const
const key: string
key
=
const request: HttpServerRequest
request
.
HttpServerRequest.url: string
url
.
String.split(separator: string | RegExp, limit?: number): string[] (+1 overload)

Split a string into substrings using the specified separator and return them as an array.

@paramseparator A string that identifies character or characters to use in separating the string. If omitted, a single-element array containing the entire string is returned.

@paramlimit A value used to limit the number of elements returned in the array.

split
("/").
Array<string>.pop(): string | undefined

Removes the last element from an array and returns it. If the array is empty, undefined is returned and the array is not modified.

pop
()!;
if (
const request: HttpServerRequest
request
.
HttpServerRequest.method: HttpMethod
method
=== "PUT") {
yield*
const bucket: Cloudflare.R2BucketClient
bucket
.
R2BucketClient.put<HttpServerError>(key: string, value: string | ReadableStream<any> | ArrayBuffer | ArrayBufferView<ArrayBufferLike> | Blob | Stream<Uint8Array<ArrayBufferLike>, HttpServerError, never> | null, options: Cloudflare.R2PutOptions & {
contentLength: number;
}): Effect.Effect<Cloudflare.R2Object, Cloudflare.R2Error | HttpServerError, RuntimeContext> (+2 overloads)
put
(
const key: string
key
,
const request: HttpServerRequest
request
.
HttpIncomingMessage<HttpServerError>.stream: Stream<Uint8Array<ArrayBufferLike>, HttpServerError, never>
stream
, {
contentLength: number
contentLength
:
var Number: NumberConstructor
(value?: any) => number

An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.

Number
(
const request: HttpServerRequest
request
.
HttpIncomingMessage<HttpServerError>.headers: Headers
headers
["content-length"] ?? 0),
});
return
import HttpServerResponse
HttpServerResponse
.
const empty: (options?: HttpServerResponse.Options.WithContent | undefined) => HttpServerResponse.HttpServerResponse

Creates an empty HTTP response.

Details

The default status is 204.

@categoryconstructors

@since4.0.0

empty
({
status?: number | undefined
status
: 201 });
}
return
import HttpServerResponse
HttpServerResponse
.
const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse

Creates an HTTP response whose body is a string.

@categoryconstructors

@since4.0.0

text
("Hello, world!");
}).
Pipeable.pipe<Effect.Effect<HttpServerResponse.HttpServerResponse, Cloudflare.R2Error | HttpServerError, HttpServerRequest | RuntimeContext>, Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<HttpServerResponse.HttpServerResponse, Cloudflare.R2Error | HttpServerError, HttpServerRequest | RuntimeContext>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const catchTag: <"R2Error", Cloudflare.R2Error | HttpServerError, HttpServerResponse.HttpServerResponse, never, never, unassigned, never, never>(k: "R2Error", f: (e: Cloudflare.R2Error) => Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>, orElse?: ((e: HttpServerError) => Effect.Effect<unassigned, never, never>) | undefined) => <A, R>(self: Effect.Effect<A, Cloudflare.R2Error | HttpServerError, R>) => Effect.Effect<...> (+1 overload)

Catches and handles specific errors by their _tag field, which is used as a discriminator.

When to use

Use when you need to recover from one specific tagged error in an effect error channel.

Details

The error type must have a readonly _tag field. catchTag matches that field and only handles errors with the requested tag.

Example (Handling a tagged error)

import { Effect } from "effect"
class NetworkError {
readonly _tag = "NetworkError"
constructor(readonly message: string) {}
}
class ValidationError {
readonly _tag = "ValidationError"
constructor(readonly message: string) {}
}
declare const task: Effect.Effect<string, NetworkError | ValidationError>
const program = Effect.catchTag(
task,
"NetworkError",
(error) => Effect.succeed(`Recovered from network error: ${error.message}`)
)

@seecatchTags for handling multiple tagged errors in one call

@seecatchIf for recovering from errors that match a predicate

@categoryerror handling

@since2.0.0

catchTag
("R2Error", (
error: Cloudflare.R2Error
error
) =>
import Effect
Effect
.
const succeed: <HttpServerResponse.HttpServerResponse>(value: HttpServerResponse.HttpServerResponse) => Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)

@seefail to create an effect that represents a failure.

@categoryconstructors

@since2.0.0

succeed
(
import HttpServerResponse
HttpServerResponse
.
const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse

Creates an HTTP response whose body is a string.

@categoryconstructors

@since4.0.0

text
(
error: Cloudflare.R2Error
error
.
Error.message: string
message
, {
status?: number | undefined
status
: 500 }),
),
),
),
};
}).
Pipeable.pipe<Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>;
}, never, Cloudflare.R2BucketBinding>, Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>;
}, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const provide: <Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>(layer: Layer<Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy | Exclude<...>> (+5 overloads)

Provides dependencies to an effect using layers or a context. Use options.local to build the layer every time; by default, layers are shared between provide calls.

Example (Providing dependencies with a layer)

import { Context, Effect, Layer } from "effect"
interface Database {
readonly query: (sql: string) => Effect.Effect<string>
}
const Database = Context.Service<Database>("Database")
const DatabaseLive = Layer.succeed(Database)({
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
})
const program = Effect.gen(function*() {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
const provided = Effect.provide(program, DatabaseLive)
Effect.runPromise(provided).then(console.log)
// Output: "Result for: SELECT * FROM users"

@categoryenvironment

@since2.0.0

provide
(
import Cloudflare
Cloudflare
.
const R2BucketBindingLive: Layer<Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>
R2BucketBindingLive
)),
);

Effect.catchTag matches errors by their _tag field. If an R2 operation fails at runtime, the Worker returns a 500 instead of crashing — and the type error disappears because R2Error is now fully handled.

Complete the fetch handler by reading objects from the bucket when the request isn’t a PUT:

src/worker.ts
import * as
import Cloudflare
Cloudflare
from "alchemy/Cloudflare";
import * as
import Effect
Effect
from "effect/Effect";
import {
const HttpServerRequest: Service<HttpServerRequest, HttpServerRequest>

Server-side representation of an incoming HTTP request.

Details

It extends HttpIncomingMessage with request metadata, parsed cookies, multipart accessors, WebSocket upgrade support, and a modify method for creating adjusted request views.

Service tag for the active server-side HTTP request.

When to use

Use to access the request currently being handled by HTTP server routes and middleware.

@categorymodels

@since4.0.0

@categorycontext

@since4.0.0

HttpServerRequest
} from "effect/unstable/http/HttpServerRequest";
import * as
import HttpServerResponse
HttpServerResponse
from "effect/unstable/http/HttpServerResponse";
import {
import Bucket
Bucket
} from "./bucket.ts";
export default
import Cloudflare
Cloudflare
.
const Worker: <{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>;
}, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>(id: string, props: InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never> | Effect.Effect<InputProps<Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, never>, never, never>, impl: Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)
Worker
(
"Worker",
{
main?: Input<string | undefined>

Path to the Worker's entry module. Bundled with rolldown before upload. Mutually exclusive with

script

— provide exactly one.

main
: import.

The type of import.meta.

If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.

meta
.
ImportMeta.filename: string

Alias of import.meta.path. Exists for Node.js compatibility

filename
},
import Effect
Effect
.
const gen: <Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding>, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>;
}>(f: () => Generator<Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding>, {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>;
}, never>) => Effect.Effect<...> (+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.R2BucketClient
bucket
= yield*
import Cloudflare
Cloudflare
.
const R2Bucket: ResourceClassWithMethods<Cloudflare.R2Bucket, {
readonly bind: (<Req = never>(args_0: Input<Cloudflare.R2Bucket> | Effect.Effect<Cloudflare.R2Bucket, never, Req>) => Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding | Req>) & ((bucket: Cloudflare.R2Bucket) => Effect.Effect<any, any, any>);
}>

A Cloudflare R2 object storage bucket with S3-compatible API.

R2 provides zero-egress-fee object storage. Create a bucket as a resource, then bind it to a Worker to read and write objects at runtime.

@sectionCreating a Bucket

@example

Basic R2 bucket

const bucket = yield* Cloudflare.R2Bucket("MyBucket");

@example

Bucket with location hint

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
locationHint: "wnam",
});

@sectionBinding to a Worker

@example

Reading and writing objects

const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
// Write an object
yield* bucket.put("hello.txt", "Hello, World!");
// Read an object
const object = yield* bucket.get("hello.txt");
if (object) {
const text = yield* object.text();
}

@example

Streaming upload with content length

const bucket = yield* Cloudflare.R2Bucket.bind(MyBucket);
yield* bucket.put("upload.bin", request.stream, {
contentLength: Number(request.headers["content-length"] ?? 0),
});

@section

Custom Domains

Attach one or more custom domains to serve bucket objects from a hostname you control. The domain's zone must already exist in your Cloudflare account; the zone is inferred from the hostname when omitted, or you can pass a Cloudflare.Zone resource, a zone ID, or any hostname inside the zone via the zone field.

@example

Single custom domain

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [{ name: "assets.example.com" }],
});

@example

Multiple custom domains

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [
{ name: "assets.example.com" },
{ name: "static.example.com" },
],
});

@example

Disable a custom domain without removing it

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [{ name: "assets.example.com", enabled: false }],
});

@example

Custom domain with explicit zone and TLS settings

const zone = yield* Cloudflare.Zone("ExampleZone", {
name: "example.com",
});
const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
domains: [
{
name: "assets.example.com",
zone,
minTLS: "1.2",
},
],
});

@section

Object Lifecycle Rules

Configure lifecycle rules to automatically delete objects, abort incomplete multipart uploads, or transition objects to InfrequentAccess storage. Pass an empty array (or omit) to clear all rules. See the Cloudflare R2 docs for details and limits (max 1000 rules per bucket).

@example

Delete objects 30 days after upload

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "expire-old-objects",
deleteObjectsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 30 },
},
},
],
});

@example

Transition to InfrequentAccess after 60 days, delete after 365

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "archive-then-delete",
prefix: "logs/",
storageClassTransitions: [
{
condition: { type: "Age", maxAge: 60 * 60 * 24 * 60 },
storageClass: "InfrequentAccess",
},
],
deleteObjectsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 365 },
},
},
],
});

@example

Abort incomplete multipart uploads after 7 days

const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
lifecycleRules: [
{
id: "abort-stale-uploads",
abortMultipartUploadsTransition: {
condition: { type: "Age", maxAge: 60 * 60 * 24 * 7 },
},
},
],
});

R2Bucket
.
bind: <never>(args_0: Input<Cloudflare.R2Bucket> | Effect.Effect<Cloudflare.R2Bucket, never, never>) => Effect.Effect<Cloudflare.R2BucketClient, never, Cloudflare.R2BucketBinding> (+1 overload)
bind
(
import Bucket
Bucket
);
return {
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>
fetch
:
import Effect
Effect
.
const gen: <Effect.Effect<HttpServerRequest, never, HttpServerRequest> | Effect.Effect<Cloudflare.R2ObjectBody | null, Cloudflare.R2Error, RuntimeContext> | Effect.Effect<string, Cloudflare.R2Error, never> | Effect.Effect<Cloudflare.R2Object, HttpServerError | Cloudflare.R2Error, RuntimeContext>, HttpServerResponse.HttpServerResponse>(f: () => Generator<...>) => Effect.Effect<...> (+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 request: HttpServerRequest
request
= yield*
const HttpServerRequest: Service<HttpServerRequest, HttpServerRequest>

Server-side representation of an incoming HTTP request.

Details

It extends HttpIncomingMessage with request metadata, parsed cookies, multipart accessors, WebSocket upgrade support, and a modify method for creating adjusted request views.

Service tag for the active server-side HTTP request.

When to use

Use to access the request currently being handled by HTTP server routes and middleware.

@categorymodels

@since4.0.0

@categorycontext

@since4.0.0

HttpServerRequest
;
const
const key: string
key
=
const request: HttpServerRequest
request
.
HttpServerRequest.url: string
url
.
String.split(separator: string | RegExp, limit?: number): string[] (+1 overload)

Split a string into substrings using the specified separator and return them as an array.

@paramseparator A string that identifies character or characters to use in separating the string. If omitted, a single-element array containing the entire string is returned.

@paramlimit A value used to limit the number of elements returned in the array.

split
("/").
Array<string>.pop(): string | undefined

Removes the last element from an array and returns it. If the array is empty, undefined is returned and the array is not modified.

pop
()!;
if (
const request: HttpServerRequest
request
.
HttpServerRequest.method: HttpMethod
method
=== "PUT") {
yield*
const bucket: Cloudflare.R2BucketClient
bucket
.
R2BucketClient.put<HttpServerError>(key: string, value: string | ReadableStream<any> | ArrayBuffer | ArrayBufferView<ArrayBufferLike> | Blob | Stream<Uint8Array<ArrayBufferLike>, HttpServerError, never> | null, options: Cloudflare.R2PutOptions & {
contentLength: number;
}): Effect.Effect<Cloudflare.R2Object, HttpServerError | Cloudflare.R2Error, RuntimeContext> (+2 overloads)
put
(
const key: string
key
,
const request: HttpServerRequest
request
.
HttpIncomingMessage<HttpServerError>.stream: Stream<Uint8Array<ArrayBufferLike>, HttpServerError, never>
stream
, {
contentLength: number
contentLength
:
var Number: NumberConstructor
(value?: any) => number

An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.

Number
(
const request: HttpServerRequest
request
.
HttpIncomingMessage<HttpServerError>.headers: Headers
headers
["content-length"] ?? 0),
});
return
import HttpServerResponse
HttpServerResponse
.
const empty: (options?: HttpServerResponse.Options.WithContent | undefined) => HttpServerResponse.HttpServerResponse

Creates an empty HTTP response.

Details

The default status is 204.

@categoryconstructors

@since4.0.0

empty
({
status?: number | undefined
status
: 201 });
}
const
const object: Cloudflare.R2ObjectBody | null
object
= yield*
const bucket: Cloudflare.R2BucketClient
bucket
.
R2BucketClient.get(key: string, options?: Cloudflare.R2GetOptions): Effect.Effect<Cloudflare.R2ObjectBody | null, Cloudflare.R2Error, RuntimeContext> (+1 overload)
get
(
const key: string
key
);
if (
const object: Cloudflare.R2ObjectBody | null
object
=== null) {
return
import HttpServerResponse
HttpServerResponse
.
const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse

Creates an HTTP response whose body is a string.

@categoryconstructors

@since4.0.0

text
("Not found", {
status?: number | undefined
status
: 404 });
}
const
const text: string
text
= yield*
const object: Cloudflare.R2ObjectBody
object
.
R2ObjectBody.text(): Effect.Effect<string, Cloudflare.R2Error>
text
();
return
import HttpServerResponse
HttpServerResponse
.
const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse

Creates an HTTP response whose body is a string.

@categoryconstructors

@since4.0.0

text
(
const text: string
text
);
return
import HttpServerResponse
HttpServerResponse
.
const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse

Creates an HTTP response whose body is a string.

@categoryconstructors

@since4.0.0

text
("Hello, world!");
}).
Pipeable.pipe<Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError | Cloudflare.R2Error, HttpServerRequest | RuntimeContext>, Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError | Cloudflare.R2Error, HttpServerRequest | RuntimeContext>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const catchTag: <"R2Error", HttpServerError | Cloudflare.R2Error, HttpServerResponse.HttpServerResponse, never, never, unassigned, never, never>(k: "R2Error", f: (e: Cloudflare.R2Error) => Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>, orElse?: ((e: HttpServerError) => Effect.Effect<unassigned, never, never>) | undefined) => <A, R>(self: Effect.Effect<A, HttpServerError | Cloudflare.R2Error, R>) => Effect.Effect<...> (+1 overload)

Catches and handles specific errors by their _tag field, which is used as a discriminator.

When to use

Use when you need to recover from one specific tagged error in an effect error channel.

Details

The error type must have a readonly _tag field. catchTag matches that field and only handles errors with the requested tag.

Example (Handling a tagged error)

import { Effect } from "effect"
class NetworkError {
readonly _tag = "NetworkError"
constructor(readonly message: string) {}
}
class ValidationError {
readonly _tag = "ValidationError"
constructor(readonly message: string) {}
}
declare const task: Effect.Effect<string, NetworkError | ValidationError>
const program = Effect.catchTag(
task,
"NetworkError",
(error) => Effect.succeed(`Recovered from network error: ${error.message}`)
)

@seecatchTags for handling multiple tagged errors in one call

@seecatchIf for recovering from errors that match a predicate

@categoryerror handling

@since2.0.0

catchTag
("R2Error", (
error: Cloudflare.R2Error
error
) =>
import Effect
Effect
.
const succeed: <HttpServerResponse.HttpServerResponse>(value: HttpServerResponse.HttpServerResponse) => Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)

@seefail to create an effect that represents a failure.

@categoryconstructors

@since2.0.0

succeed
(
import HttpServerResponse
HttpServerResponse
.
const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse

Creates an HTTP response whose body is a string.

@categoryconstructors

@since4.0.0

text
(
error: Cloudflare.R2Error
error
.
Error.message: string
message
, {
status?: number | undefined
status
: 500 }),
),
),
),
};
}).
Pipeable.pipe<Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>;
}, never, Cloudflare.R2BucketBinding>, Effect.Effect<{
fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest | RuntimeContext>;
}, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const provide: <Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>(layer: Layer<Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy | Exclude<...>> (+5 overloads)

Provides dependencies to an effect using layers or a context. Use options.local to build the layer every time; by default, layers are shared between provide calls.

Example (Providing dependencies with a layer)

import { Context, Effect, Layer } from "effect"
interface Database {
readonly query: (sql: string) => Effect.Effect<string>
}
const Database = Context.Service<Database>("Database")
const DatabaseLive = Layer.succeed(Database)({
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
})
const program = Effect.gen(function*() {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
const provided = Effect.provide(program, DatabaseLive)
Effect.runPromise(provided).then(console.log)
// Output: "Result for: SELECT * FROM users"

@categoryenvironment

@since2.0.0

provide
(
import Cloudflare
Cloudflare
.
const R2BucketBindingLive: Layer<Cloudflare.R2BucketBinding, never, Cloudflare.WorkerEnvironment | Cloudflare.R2BucketBindingPolicy>
R2BucketBindingLive
)),
);

The Worker now handles two routes:

  • PUT /:key — stores the request body in the bucket
  • GET /:key — retrieves the object, returning 404 if missing

Because bucket.get also returns R2Error, the catchTag you added in the previous step already covers it — no additional error handling needed.

Add the Worker to alchemy.run.ts and expose its URL as a stack output:

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 {
import Bucket
Bucket
} from "./src/bucket.ts";
import
import Worker
Worker
from "./src/worker.ts";
export default
import Alchemy
Alchemy
.
Stack<{
bucketName: any;
url: any;
}, unknown>(stackName: string, options: Alchemy.StackProps<unknown>, eff: Effect.Effect<{
bucketName: any;
url: any;
}, ConfigError, unknown>): Effect.Effect<Alchemy.CompiledStack<{
bucketName: any;
url: any;
}, any>, ConfigError, never> (+2 overloads)
export Stack
Stack
(
"MyApp",
{
StackProps<unknown>.providers: Layer<unknown, 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: <any, {
bucketName: any;
url: any;
}>(f: () => Generator<any, {
bucketName: any;
url: any;
}, never>) => Effect.Effect<{
bucketName: any;
url: any;
}, unknown, unknown> (+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: any
bucket
= yield*
import Bucket
Bucket
;
const
const worker: any
worker
= yield*
import Worker
Worker
;
return {
bucketName: any
bucketName
:
const bucket: any
bucket
.
any
bucketName
,
url: any
url
:
const worker: any
worker
.
any
url
,
};
}),
);

Deploy again. Alchemy detects the new Worker and the unchanged Bucket:

Terminal window
bun alchemy deploy
Plan: 1 to create

+ Worker (Cloudflare.Worker) (1 bindings)
  + Bucket
 Bucket (Cloudflare.R2Bucket)

Proceed?
◉ Yes ○ No
 Bucket (Cloudflare.R2Bucket) no change
 Worker (Cloudflare.Worker) created
  • Uploading worker (14.20 KB) ...
  • Enabling workers.dev subdomain...
{
  bucketName: "myapp-bucket-a1b2c3d4e5",
  url: "https://myapp-worker-dev-you-abc123.workers.dev",
}

Use curl to write and read an object:

Terminal window
# Store an object
curl -X PUT https://myapp-worker-dev-you-abc123.workers.dev/hello.txt \
-d 'Hello, world!'
# Retrieve it
curl https://myapp-worker-dev-you-abc123.workers.dev/hello.txt
# → Hello, world!

You now have:

  • A Cloudflare Worker with GET and PUT routes
  • An R2 Bucket bound to the Worker
  • Stack outputs showing both the bucket name and worker URL

In Part 3, you’ll learn about stages and state stores so multiple developers (and CI) can deploy isolated environments.