1---
2title: Resources
3description: Resources are named cloud entities with input properties and output attributes.
4---
5
6import DAG from "../../../components/DAG.astro";
7
8A **Resource** represents a cloud entity managed by Alchemy — a
9bucket, database, queue, function, DNS record, or anything else that
10has a lifecycle of reconcile and delete.
11
12## Declaring a Resource
13
14Resources are declared with a **logical ID** and optional **input
15properties**:
16
17```typescript
18const bucket = yield* Cloudflare.R2.Bucket("Bucket");
19const queue = yield* AWS.SQS.Queue("Jobs", {
20 fifoQueue: true,
21});
22```
23
24The logical ID (`"Bucket"`, `"Jobs"`) is stable across deploys. It
25identifies this resource within the stack and is used to track state.
26
27## Input properties and output attributes
28
29Every resource has two sides:
30
31- **Input Properties** — the desired configuration you pass in
32 (e.g. `fifoQueue: true`)
33- **Output Attributes** — the values produced after creation
34 (e.g. `queueUrl`, `queueArn`)
35
36Output attributes are available as [`Output`](/infrastructure-as-code/outputs)
37expressions on the resource — lazy, typed references that resolve
38once the upstream resource has been created:
39
40```typescript
41const bucket = yield* Cloudflare.R2.Bucket("Bucket");
42bucket.bucketName; // Output<string>
43```
44
45See [Inputs & Outputs](/infrastructure-as-code/outputs) for the full set of
46operators (`map`, `mapEffect`, `all`, `interpolate`, `ref`).
47
48These are lazy references that resolve after the resource is created.
49You can pass them as inputs to other resources to express
50dependencies.
51
52## Resources are Effects
53
54A resource declaration like `Cloudflare.R2.Bucket("Bucket")` is just
55an `Effect` — calling it doesn't talk to the cloud. `yield*`-ing it
56inside a [Stack](/infrastructure-as-code/stack) doesn't either; it just **registers
57the resource on the stack** and hands you back a typed
58[`Output`](/infrastructure-as-code/outputs) reference for its attributes:
59
60```typescript
61// 1. Build the Effect. No API calls. No state mutation.
62const Bucket = Cloudflare.R2.Bucket("Bucket");
63
64// 2. Register it on the stack. Still no API calls — alchemy is
65// just collecting the desired-state graph.
66export default Alchemy.Stack(
67 "MyApp",
68 { providers: Cloudflare.providers(), state: Cloudflare.state() },
69 Effect.gen(function* () {
70 const bucket = yield* Bucket;
71 return { name: bucket.bucketName };
72 }),
73);
74```
75
76The cloud is only touched later, when `alchemy deploy` runs the
77collected graph through plan and apply. See
78[Resource Lifecycle](/infrastructure-as-code/resource-lifecycle) for what happens
79after registration.
80
81### Sharing across files
82
83Because the declaration is just a value, you can `export` it and
84import it from anywhere — handlers, layers, other resources:
85
86```typescript
87// src/bucket.ts
88export const Bucket = Cloudflare.R2.Bucket("Bucket");
89```
90
91```typescript
92// alchemy.run.ts
93import { Bucket } from "./src/bucket.ts";
94
95export default Alchemy.Stack(
96 "MyApp",
97 { providers: Cloudflare.providers(), state: Cloudflare.state() },
98 Effect.gen(function* () {
99 yield* Bucket;
100 }),
101);
102```
103
104Importing the same `Bucket` from multiple files is safe. Alchemy
105keys resources by their fully qualified name, so even if two
106modules `yield*` it, it registers on the stack exactly once.
107
108## `ref`
109
110Every resource constructor exposes a static `ref` returning a
111typed reference to a resource that's already been deployed
112**elsewhere** — another stage of this stack, or a different
113stack entirely:
114
115```typescript
116const project = yield* Neon.Project.ref("app-db", {
117 stage: "staging",
118});
119// project: Neon.Project — same shape, same typed attributes
120```
121
122Lookup keyed by `{ stack, stage, id }`; both options default to
123the current stack/stage. Lazy — no cloud calls at declaration
124time. Typed — the result has the same interface as a freshly
125deployed resource. Strict — fails fast with
126`InvalidReferenceError` when the target is missing.
127
128A typical pattern: PR-preview stages reference long-lived
129`staging` resources instead of provisioning their own:
130
131```typescript
132const project = stage.startsWith("pr-")
133 ? yield* Neon.Project.ref("app-db", { stage: "staging" })
134 : yield* Neon.Project("app-db", { region: "aws-us-east-1" });
135```
136
137See [References](/infrastructure-as-code/references) for the full reference
138surface and the [Shared database across
139stages](/cloudflare/data/shared-database) guide for the canonical
140walkthrough.
141
142## Logical ID
143
144The first argument you pass to a resource constructor is its
145**logical ID** — a name **you** choose to identify the resource
146within its stack:
147
148```typescript
149const Bucket = Cloudflare.R2.Bucket("Bucket"); // logical ID: "Bucket"
150const Jobs = AWS.SQS.Queue("Jobs"); // logical ID: "Jobs"
151```
152
153The logical ID is how alchemy tracks the resource in state across
154deploys:
155
156- **Stable across deploys** — keep the same ID and alchemy keeps
157 updating the same underlying cloud resource.
158- **Stable across renames** — change the variable name, change the
159 TypeScript class, move the file; as long as the logical ID stays
160 the same, alchemy still recognizes it.
161- **Rename = replace** — change the logical ID and alchemy treats
162 it as a new resource (and deletes the old one on the next deploy)
163 — unless you declare the rename with
164 [`renamedFrom`](/infrastructure-as-code/renaming).
165
166Logical IDs only need to be unique **within a stack**.
167
168## Renaming a resource
169
170Changing a logical ID normally plans a **replacement**: a new
171resource is created under the new ID and the old one — including
172its physical resource, data, and attachments — is deleted. When you
173actually just renamed the resource, declare the rename with
174`Alchemy.renamedFrom` and alchemy migrates the state row instead:
175
176```diff lang="typescript"
177-const bucket = yield* Cloudflare.R2.Bucket("Bucket");
178+const bucket = yield* Cloudflare.R2.Bucket("Assets").pipe(
179+ Alchemy.renamedFrom("Bucket"),
180+);
181```
182
183The instance ID and physical name are preserved; nothing is created
184or deleted in the cloud. See
185[Renaming Resources](/infrastructure-as-code/renaming).
186
187## Physical name
188
189The **physical name** is what the cloud actually sees — `myapp-dev_sam-bucket-a3f1`
190on R2, an ARN suffix on AWS, etc. Alchemy generates it for you from
191three things:
192
193```text
194{stack-name}-{stage}-{logical-id}-{instance-id}
195 "myapp" "dev_sam" "Bucket" "a3f1"
196```
197
198The first three are obvious. The **instance ID** is a short,
199deterministic suffix tied to *this specific instance* of the
200resource. While the resource lives, the instance ID stays the same,
201so re-running create finds the existing resource instead of
202duplicating it.
203
204The whole scheme means:
205
206- **Stages don't collide** — `dev_sam` and `prod` produce different
207 physical names from the same code.
208- **Creates are idempotent** — same logical ID + same instance ID =
209 same physical name on retry.
210- **State can recover** — if persistence fails, alchemy can re-run
211 create and find the existing cloud resource.
212
213The instance ID is the part that **does** change when a resource is
214replaced — which leads us to…
215
216## Replacement
217
218Some property changes can't be applied in place. Changing a
219DynamoDB table's partition key, for example, can't be done on a
220live table — it has to be re-created.
221
222Before:
223
224```typescript
225const Jobs = DynamoDB.Table("Jobs", {
226 partitionKey: "id",
227 attributes: { id: "S" },
228});
229```
230
231After:
232
233```diff lang="typescript"
234 const Jobs = DynamoDB.Table("Jobs", {
235- partitionKey: "id",
236- attributes: { id: "S" },
237+ partitionKey: "tenantId",
238+ attributes: { tenantId: "S" },
239 });
240```
241
242The logical ID (`"Jobs"`) doesn't change, but the **instance ID
243does** — which means the physical name does too:
244
245```text
246before: myapp-prod-jobs-a3f1
247after: myapp-prod-jobs-9b2c
248```
249
250When the next plan runs, alchemy:
251
2521. Creates a new table with the new instance ID (and physical name)
2532. Updates downstream resources to reference the new one
2543. Deletes the old table
255
256The resource's [provider](/infrastructure-as-code/provider) decides which property
257changes trigger replacement vs in-place update (via
258[`diff`](/infrastructure-as-code/provider#diff)). For the full lifecycle
259(reconcile / replace / delete) see
260[Resource Lifecycle](/infrastructure-as-code/resource-lifecycle).
261
262## Defining your own Resource type
263
264A resource is just a typed Effect. To support a new cloud or
265third-party API, declare a `Resource` type with its input props and
266output attributes — then implement its provider as a `Layer`. Same
267engine plans, deploys, and destroys it.
268
269See [Writing a Custom Resource Provider](/infrastructure-as-code/custom-provider)
270for a step-by-step walkthrough of declaring the type and
271implementing each lifecycle hook (`reconcile`, `delete`, `diff`,
272`read`).
273
274```typescript
275// 1. Declare the type + constructor.
276export type StripeProduct = Resource<
277 "Stripe.Product",
278 { name: string; price: number }, // input props
279 { productId: string; priceId: string } // output attrs
280>;
281export const StripeProduct = Resource<StripeProduct>("Stripe.Product");
282
283// 2. Use it like any built-in resource.
284const Pro = yield* StripeProduct("Pro", {
285 name: "Pro plan",
286 price: 2900,
287});
288// ^? typed Pro.productId, Pro.priceId
289```
290
291- **Inputs & outputs are typed** — Props you pass in, attributes the
292 provider returns. Both fully typed, both checked at the call site.
293- **Compose with built-in providers** — Merge your provider Layer with
294 `Cloudflare.providers()` or `AWS.providers()`. One stack, mixed
295 clouds.
296
297The lifecycle hooks the provider implements — `reconcile`,
298`delete`, `diff`, `read` — are documented in
299[Provider](/infrastructure-as-code/provider#lifecycle-operations).
300
301## The resource graph
302
303Every attribute a resource exposes (`Bucket.bucketName`,
304`Worker.url`) is an [Output](/infrastructure-as-code/outputs) — a lazy,
305typed reference that resolves at deploy time. Passing an Output from
306one resource as input to another draws an edge in the dependency
307graph. Take this stack:
308
309```typescript
310const Bucket = yield* Cloudflare.R2.Bucket("Bucket");
311const Sessions = yield* Cloudflare.KV.Namespace("Sessions");
312
313const Queue = yield* AWS.SQS.Queue("Queue", {
314 name: Output.interpolate`${Bucket.bucketName}-events`,
315});
316
317const Worker = yield* Cloudflare.Worker("Worker", {
318 main: import.meta.url,
319 env: { Bucket, Sessions, Queue },
320});
321```
322
323Alchemy reads the Outputs in each resource's props and builds:
324
325<DAG
326 nodes={[
327 { id: "Bucket", type: "Cloudflare.R2.Bucket" },
328 { id: "Sessions", type: "Cloudflare.KV.Namespace" },
329 { id: "Queue", type: "AWS.SQS.Queue" },
330 { id: "Worker", type: "Cloudflare.Worker", tone: "accent" },
331 ]}
332 edges={[
333 { from: "Bucket", to: "Queue" },
334 { from: "Bucket", to: "Worker" },
335 { from: "Sessions", to: "Worker" },
336 { from: "Queue", to: "Worker" },
337 ]}
338/>
339
340It then deploys in topological order:
341
3421. `Bucket` and `Sessions` have no dependencies → created **in
343 parallel**.
3442. `Queue` depends on `Bucket.bucketName` → waits for `Bucket`,
345 then created.
3463. `Worker` depends on all three → created last, after every
347 upstream Output has resolved.
348
349Outputs draw the edges. [Bindings](/infrastructure-as-effects/binding) attach
350resources to Workers and Lambdas — and when bindings form a cycle,
351alchemy handles it with a two-phase plan; see
352[Circular Bindings](/infrastructure-as-effects/circular-bindings).
353
354## Circular references
355
356Real systems have cycles — two Workers that call each other, a
357Lambda that invokes another Lambda. Alchemy resolves them by
358splitting each Function resource ([Runtime](/infrastructure-as-effects/runtime)) into a
359**class** that acts as the Tag (the identity) and a **`.make(...)`**
360Layer that supplies the runtime implementation — so the class can be
361referenced before its implementation exists:
362
363<DAG
364 nodes={[
365 { id: "A", label: "Worker A", type: "Cloudflare.Worker", tone: "accent" },
366 { id: "B", label: "Worker B", type: "Cloudflare.Worker", tone: "accent" },
367 ]}
368 edges={[
369 { from: "A", to: "B", label: "binds" },
370 { from: "B", to: "A", label: "binds" },
371 ]}
372/>
373
374See [Circular Bindings](/infrastructure-as-effects/circular-bindings) for the full A↔B
375build-up and how alchemy plans the two-phase create-then-wire deploy.
376
377## Where next
378
379- [Actions](/infrastructure-as-code/action) — deploy-time work without a lifecycle.
380- [Inputs & Outputs](/infrastructure-as-code/outputs) — the lazy references that draw the graph.
381- [Resource lifecycle](/infrastructure-as-code/resource-lifecycle) — what happens on deploy.