| 1 | --- |
| 2 | title: Resources |
| 3 | description: Resources are named cloud entities with input properties and output attributes. |
| 4 | --- |
| 5 | |
| 6 | import DAG from "../../../components/DAG.astro"; |
| 7 | |
| 8 | A **Resource** represents a cloud entity managed by Alchemy — a |
| 9 | bucket, database, queue, function, DNS record, or anything else that |
| 10 | has a lifecycle of reconcile and delete. |
| 11 | |
| 12 | ## Declaring a Resource |
| 13 | |
| 14 | Resources are declared with a **logical ID** and optional **input |
| 15 | properties**: |
| 16 | |
| 17 | ```typescript |
| 18 | const bucket = yield* Cloudflare.R2.Bucket("Bucket"); |
| 19 | const queue = yield* AWS.SQS.Queue("Jobs", { |
| 20 | fifoQueue: true, |
| 21 | }); |
| 22 | ``` |
| 23 | |
| 24 | The logical ID (`"Bucket"`, `"Jobs"`) is stable across deploys. It |
| 25 | identifies this resource within the stack and is used to track state. |
| 26 | |
| 27 | ## Input properties and output attributes |
| 28 | |
| 29 | Every 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 | |
| 36 | Output attributes are available as [`Output`](/infrastructure-as-code/outputs) |
| 37 | expressions on the resource — lazy, typed references that resolve |
| 38 | once the upstream resource has been created: |
| 39 | |
| 40 | ```typescript |
| 41 | const bucket = yield* Cloudflare.R2.Bucket("Bucket"); |
| 42 | bucket.bucketName; // Output<string> |
| 43 | ``` |
| 44 | |
| 45 | See [Inputs & Outputs](/infrastructure-as-code/outputs) for the full set of |
| 46 | operators (`map`, `mapEffect`, `all`, `interpolate`, `ref`). |
| 47 | |
| 48 | These are lazy references that resolve after the resource is created. |
| 49 | You can pass them as inputs to other resources to express |
| 50 | dependencies. |
| 51 | |
| 52 | ## Resources are Effects |
| 53 | |
| 54 | A resource declaration like `Cloudflare.R2.Bucket("Bucket")` is just |
| 55 | an `Effect` — calling it doesn't talk to the cloud. `yield*`-ing it |
| 56 | inside a [Stack](/infrastructure-as-code/stack) doesn't either; it just **registers |
| 57 | the 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. |
| 62 | const 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. |
| 66 | export 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 | |
| 76 | The cloud is only touched later, when `alchemy deploy` runs the |
| 77 | collected graph through plan and apply. See |
| 78 | [Resource Lifecycle](/infrastructure-as-code/resource-lifecycle) for what happens |
| 79 | after registration. |
| 80 | |
| 81 | ### Sharing across files |
| 82 | |
| 83 | Because the declaration is just a value, you can `export` it and |
| 84 | import it from anywhere — handlers, layers, other resources: |
| 85 | |
| 86 | ```typescript |
| 87 | // src/bucket.ts |
| 88 | export const Bucket = Cloudflare.R2.Bucket("Bucket"); |
| 89 | ``` |
| 90 | |
| 91 | ```typescript |
| 92 | // alchemy.run.ts |
| 93 | import { Bucket } from "./src/bucket.ts"; |
| 94 | |
| 95 | export default Alchemy.Stack( |
| 96 | "MyApp", |
| 97 | { providers: Cloudflare.providers(), state: Cloudflare.state() }, |
| 98 | Effect.gen(function* () { |
| 99 | yield* Bucket; |
| 100 | }), |
| 101 | ); |
| 102 | ``` |
| 103 | |
| 104 | Importing the same `Bucket` from multiple files is safe. Alchemy |
| 105 | keys resources by their fully qualified name, so even if two |
| 106 | modules `yield*` it, it registers on the stack exactly once. |
| 107 | |
| 108 | ## `ref` |
| 109 | |
| 110 | Every resource constructor exposes a static `ref` returning a |
| 111 | typed reference to a resource that's already been deployed |
| 112 | **elsewhere** — another stage of this stack, or a different |
| 113 | stack entirely: |
| 114 | |
| 115 | ```typescript |
| 116 | const project = yield* Neon.Project.ref("app-db", { |
| 117 | stage: "staging", |
| 118 | }); |
| 119 | // project: Neon.Project — same shape, same typed attributes |
| 120 | ``` |
| 121 | |
| 122 | Lookup keyed by `{ stack, stage, id }`; both options default to |
| 123 | the current stack/stage. Lazy — no cloud calls at declaration |
| 124 | time. Typed — the result has the same interface as a freshly |
| 125 | deployed resource. Strict — fails fast with |
| 126 | `InvalidReferenceError` when the target is missing. |
| 127 | |
| 128 | A typical pattern: PR-preview stages reference long-lived |
| 129 | `staging` resources instead of provisioning their own: |
| 130 | |
| 131 | ```typescript |
| 132 | const 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 | |
| 137 | See [References](/infrastructure-as-code/references) for the full reference |
| 138 | surface and the [Shared database across |
| 139 | stages](/cloudflare/data/shared-database) guide for the canonical |
| 140 | walkthrough. |
| 141 | |
| 142 | ## Logical ID |
| 143 | |
| 144 | The first argument you pass to a resource constructor is its |
| 145 | **logical ID** — a name **you** choose to identify the resource |
| 146 | within its stack: |
| 147 | |
| 148 | ```typescript |
| 149 | const Bucket = Cloudflare.R2.Bucket("Bucket"); // logical ID: "Bucket" |
| 150 | const Jobs = AWS.SQS.Queue("Jobs"); // logical ID: "Jobs" |
| 151 | ``` |
| 152 | |
| 153 | The logical ID is how alchemy tracks the resource in state across |
| 154 | deploys: |
| 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 | |
| 166 | Logical IDs only need to be unique **within a stack**. |
| 167 | |
| 168 | ## Renaming a resource |
| 169 | |
| 170 | Changing a logical ID normally plans a **replacement**: a new |
| 171 | resource is created under the new ID and the old one — including |
| 172 | its physical resource, data, and attachments — is deleted. When you |
| 173 | actually 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 | |
| 183 | The instance ID and physical name are preserved; nothing is created |
| 184 | or deleted in the cloud. See |
| 185 | [Renaming Resources](/infrastructure-as-code/renaming). |
| 186 | |
| 187 | ## Physical name |
| 188 | |
| 189 | The **physical name** is what the cloud actually sees — `myapp-dev_sam-bucket-a3f1` |
| 190 | on R2, an ARN suffix on AWS, etc. Alchemy generates it for you from |
| 191 | three things: |
| 192 | |
| 193 | ```text |
| 194 | {stack-name}-{stage}-{logical-id}-{instance-id} |
| 195 | "myapp" "dev_sam" "Bucket" "a3f1" |
| 196 | ``` |
| 197 | |
| 198 | The first three are obvious. The **instance ID** is a short, |
| 199 | deterministic suffix tied to *this specific instance* of the |
| 200 | resource. While the resource lives, the instance ID stays the same, |
| 201 | so re-running create finds the existing resource instead of |
| 202 | duplicating it. |
| 203 | |
| 204 | The 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 | |
| 213 | The instance ID is the part that **does** change when a resource is |
| 214 | replaced — which leads us to… |
| 215 | |
| 216 | ## Replacement |
| 217 | |
| 218 | Some property changes can't be applied in place. Changing a |
| 219 | DynamoDB table's partition key, for example, can't be done on a |
| 220 | live table — it has to be re-created. |
| 221 | |
| 222 | Before: |
| 223 | |
| 224 | ```typescript |
| 225 | const Jobs = DynamoDB.Table("Jobs", { |
| 226 | partitionKey: "id", |
| 227 | attributes: { id: "S" }, |
| 228 | }); |
| 229 | ``` |
| 230 | |
| 231 | After: |
| 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 | |
| 242 | The logical ID (`"Jobs"`) doesn't change, but the **instance ID |
| 243 | does** — which means the physical name does too: |
| 244 | |
| 245 | ```text |
| 246 | before: myapp-prod-jobs-a3f1 |
| 247 | after: myapp-prod-jobs-9b2c |
| 248 | ``` |
| 249 | |
| 250 | When the next plan runs, alchemy: |
| 251 | |
| 252 | 1. Creates a new table with the new instance ID (and physical name) |
| 253 | 2. Updates downstream resources to reference the new one |
| 254 | 3. Deletes the old table |
| 255 | |
| 256 | The resource's [provider](/infrastructure-as-code/provider) decides which property |
| 257 | changes 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 | |
| 264 | A resource is just a typed Effect. To support a new cloud or |
| 265 | third-party API, declare a `Resource` type with its input props and |
| 266 | output attributes — then implement its provider as a `Layer`. Same |
| 267 | engine plans, deploys, and destroys it. |
| 268 | |
| 269 | See [Writing a Custom Resource Provider](/infrastructure-as-code/custom-provider) |
| 270 | for a step-by-step walkthrough of declaring the type and |
| 271 | implementing each lifecycle hook (`reconcile`, `delete`, `diff`, |
| 272 | `read`). |
| 273 | |
| 274 | ```typescript |
| 275 | // 1. Declare the type + constructor. |
| 276 | export type StripeProduct = Resource< |
| 277 | "Stripe.Product", |
| 278 | { name: string; price: number }, // input props |
| 279 | { productId: string; priceId: string } // output attrs |
| 280 | >; |
| 281 | export const StripeProduct = Resource<StripeProduct>("Stripe.Product"); |
| 282 | |
| 283 | // 2. Use it like any built-in resource. |
| 284 | const 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 | |
| 297 | The 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 | |
| 303 | Every attribute a resource exposes (`Bucket.bucketName`, |
| 304 | `Worker.url`) is an [Output](/infrastructure-as-code/outputs) — a lazy, |
| 305 | typed reference that resolves at deploy time. Passing an Output from |
| 306 | one resource as input to another draws an edge in the dependency |
| 307 | graph. Take this stack: |
| 308 | |
| 309 | ```typescript |
| 310 | const Bucket = yield* Cloudflare.R2.Bucket("Bucket"); |
| 311 | const Sessions = yield* Cloudflare.KV.Namespace("Sessions"); |
| 312 | |
| 313 | const Queue = yield* AWS.SQS.Queue("Queue", { |
| 314 | name: Output.interpolate`${Bucket.bucketName}-events`, |
| 315 | }); |
| 316 | |
| 317 | const Worker = yield* Cloudflare.Worker("Worker", { |
| 318 | main: import.meta.url, |
| 319 | env: { Bucket, Sessions, Queue }, |
| 320 | }); |
| 321 | ``` |
| 322 | |
| 323 | Alchemy 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 | |
| 340 | It then deploys in topological order: |
| 341 | |
| 342 | 1. `Bucket` and `Sessions` have no dependencies → created **in |
| 343 | parallel**. |
| 344 | 2. `Queue` depends on `Bucket.bucketName` → waits for `Bucket`, |
| 345 | then created. |
| 346 | 3. `Worker` depends on all three → created last, after every |
| 347 | upstream Output has resolved. |
| 348 | |
| 349 | Outputs draw the edges. [Bindings](/infrastructure-as-effects/binding) attach |
| 350 | resources to Workers and Lambdas — and when bindings form a cycle, |
| 351 | alchemy handles it with a two-phase plan; see |
| 352 | [Circular Bindings](/infrastructure-as-effects/circular-bindings). |
| 353 | |
| 354 | ## Circular references |
| 355 | |
| 356 | Real systems have cycles — two Workers that call each other, a |
| 357 | Lambda that invokes another Lambda. Alchemy resolves them by |
| 358 | splitting each Function resource ([Runtime](/infrastructure-as-effects/runtime)) into a |
| 359 | **class** that acts as the Tag (the identity) and a **`.make(...)`** |
| 360 | Layer that supplies the runtime implementation — so the class can be |
| 361 | referenced 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 | |
| 374 | See [Circular Bindings](/infrastructure-as-effects/circular-bindings) for the full A↔B |
| 375 | build-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. |