| 1 | --- |
| 2 | title: Inputs & Outputs |
| 3 | description: Output<T> is alchemy's lazy reference type — the lazy values that flow between resources, get composed with .pipe, mapped, interpolated, and resolved during deploy. |
| 4 | --- |
| 5 | |
| 6 | A [Resource](/infrastructure-as-code/resource)'s **inputs** are the props you pass |
| 7 | in. Its **outputs** are the attributes the cloud returns after |
| 8 | creation. The catch: outputs don't exist when you write the code. |
| 9 | They only exist after the resource is deployed. |
| 10 | |
| 11 | Alchemy bridges this with **`Output<T>`** — a lazy, typed reference |
| 12 | that resolves once the upstream resource has run. You can read |
| 13 | properties off it, transform it with `pipe`, combine it with other |
| 14 | outputs, and feed it as input to the next resource. The resource |
| 15 | graph is built from these dependencies. |
| 16 | |
| 17 | This page is a reference for every operator. For the bigger picture |
| 18 | of how the graph deploys, see |
| 19 | [Resource Lifecycle](/infrastructure-as-code/resource-lifecycle). |
| 20 | |
| 21 | ## What an Output is |
| 22 | |
| 23 | ```typescript |
| 24 | const bucket = yield* Cloudflare.R2.Bucket("Bucket"); |
| 25 | |
| 26 | bucket.bucketName; |
| 27 | // ^? Output<string> |
| 28 | ``` |
| 29 | |
| 30 | Three things to know: |
| 31 | |
| 32 | 1. **Lazy** — `bucket.bucketName` doesn't have a value yet. It's a |
| 33 | description of "the bucket's name once it's created." |
| 34 | 2. **Typed** — TypeScript still knows it's `string`, even though |
| 35 | you can't `console.log` it directly. |
| 36 | 3. **Tracked** — passing it as input to another resource registers |
| 37 | a dependency edge. Alchemy uses these edges to deploy in the |
| 38 | right order. |
| 39 | |
| 40 | You almost never construct an `Output` yourself — they fall out of |
| 41 | resource declarations and the operators below. |
| 42 | |
| 43 | ## Property access |
| 44 | |
| 45 | Reading a property off a resource (or any `Output`) returns another |
| 46 | `Output`: |
| 47 | |
| 48 | ```typescript |
| 49 | const bucket = yield* Cloudflare.R2.Bucket("Bucket"); |
| 50 | |
| 51 | bucket.bucketName; // Output<string> |
| 52 | bucket.bucketArn; // Output<string> |
| 53 | bucket.tags?.environment; // Output<string | undefined> |
| 54 | ``` |
| 55 | |
| 56 | Nested access works too — `expr.nested.deep` walks down the object |
| 57 | without forcing the value. |
| 58 | |
| 59 | ## `literal` |
| 60 | |
| 61 | When you need an `Output` shape but the value is already known: |
| 62 | |
| 63 | ```typescript |
| 64 | import * as Output from "alchemy/Output"; |
| 65 | |
| 66 | Output.literal("hello"); // Output<string> |
| 67 | Output.literal(42); // Output<number> |
| 68 | Output.literal({ a: 1 }); // Output<{ a: number }> |
| 69 | ``` |
| 70 | |
| 71 | Useful as a default for an optional output, or as a placeholder in |
| 72 | templates and `Output.all` calls. |
| 73 | |
| 74 | ## `asOutput` |
| 75 | |
| 76 | Lifts a plain value, an `Effect`, or an existing `Output` into an |
| 77 | `Output`: |
| 78 | |
| 79 | ```typescript |
| 80 | Output.asOutput("foo"); // wraps as a literal |
| 81 | Output.asOutput(Effect.succeed(123)); // wraps as an EffectExpr |
| 82 | Output.asOutput(existingOutput); // returns it unchanged |
| 83 | ``` |
| 84 | |
| 85 | Use this when writing helpers that should accept "anything output-y." |
| 86 | |
| 87 | ## `fromEffect` |
| 88 | |
| 89 | Lifts a plan-time Effect into an `Output`. The effect runs when the |
| 90 | stack resolves the Output during plan/deploy — with the stack's |
| 91 | services (cloud credentials, region, ...) provided — and never inside |
| 92 | a deployed runtime: |
| 93 | |
| 94 | ```typescript |
| 95 | Output.fromEffect(lookupLatestAmi()); // Output<string> |
| 96 | ``` |
| 97 | |
| 98 | This is the low-level seam for lookup helpers that read cloud state to |
| 99 | produce a prop value. Because constructing the Output is inert, such |
| 100 | helpers are safe to call from composition code that is re-executed |
| 101 | inside a deployed Function, Worker, or Instance bundle — no |
| 102 | `__ALCHEMY_RUNTIME__` guard needed. The higher-level form is |
| 103 | [`Capability.execute`](/infrastructure-as-effects/binding#plan-time-invokes) |
| 104 | — invoking a binding as a plan-time data source — which the AMI |
| 105 | finders (`AWS.EC2.getAmi`, `AWS.EC2.amazonLinux2023()`, |
| 106 | `AWS.EC2.ubuntu2404()`, ...) are built on: |
| 107 | |
| 108 | ```typescript |
| 109 | const instance = yield* AWS.EC2.Instance("web", { |
| 110 | imageId: AWS.EC2.amazonLinux2023(), |
| 111 | instanceType: "t3.micro", |
| 112 | subnetId: subnet.subnetId, |
| 113 | }); |
| 114 | ``` |
| 115 | |
| 116 | The effect must not fail (`E = never`) — die with a descriptive error |
| 117 | for unresolvable lookups. |
| 118 | |
| 119 | ## `map` |
| 120 | |
| 121 | `Output.map` transforms an `Output<A>` into an `Output<B>` without |
| 122 | forcing it. The function runs once, after the upstream resource |
| 123 | resolves: |
| 124 | |
| 125 | ```typescript |
| 126 | import * as Output from "alchemy/Output"; |
| 127 | |
| 128 | const upper = bucket.bucketName.pipe( |
| 129 | Output.map((name) => name.toUpperCase()), |
| 130 | ); |
| 131 | // Output<string> |
| 132 | ``` |
| 133 | |
| 134 | It also supports a data-first form when you don't want to use |
| 135 | `pipe`: |
| 136 | |
| 137 | ```typescript |
| 138 | Output.map(bucket.bucketName, (name) => name.toUpperCase()); |
| 139 | ``` |
| 140 | |
| 141 | `map` composes — pipe several together to build up a chain: |
| 142 | |
| 143 | ```typescript |
| 144 | const slug = bucket.bucketName.pipe( |
| 145 | Output.map((s) => s.toLowerCase()), |
| 146 | Output.map((s) => s.replaceAll("_", "-")), |
| 147 | ); |
| 148 | ``` |
| 149 | |
| 150 | The function only runs once (per evaluation) and only after every |
| 151 | upstream `Output` is ready. There is no risk of "the function ran |
| 152 | before the resource existed." |
| 153 | |
| 154 | ## `mapEffect` |
| 155 | |
| 156 | `Output.mapEffect` is the same idea but the transform returns an |
| 157 | `Effect`. Use this when the transformation needs to do real work — |
| 158 | read a file, hit an API, decode a JWT — and you want it to live in |
| 159 | the Effect graph. |
| 160 | |
| 161 | ```typescript |
| 162 | const decoded = secret.value.pipe( |
| 163 | Output.mapEffect((s) => |
| 164 | Effect.gen(function* () { |
| 165 | const result = yield* JwtDecoder.decode(s); |
| 166 | return result.payload; |
| 167 | }), |
| 168 | ), |
| 169 | ); |
| 170 | // Output<Payload> |
| 171 | ``` |
| 172 | |
| 173 | Chain them just like `map`: |
| 174 | |
| 175 | ```typescript |
| 176 | "a".pipe( |
| 177 | Output.mapEffect((s) => Effect.succeed(s + "b")), |
| 178 | Output.mapEffect((s) => Effect.succeed(s + "c")), |
| 179 | ); |
| 180 | // resolves to "abc" |
| 181 | ``` |
| 182 | |
| 183 | Requirements (the `R` channel of the inner Effects) are tracked in |
| 184 | the resulting `Output`'s requirements, so anything those Effects |
| 185 | need must be provided wherever the Output is finally evaluated. |
| 186 | |
| 187 | ## `all` |
| 188 | |
| 189 | `Output.all` zips several `Output`s into one. The result resolves |
| 190 | to a tuple (preserving the input shape and types): |
| 191 | |
| 192 | ```typescript |
| 193 | const both = Output.all(bucket.bucketName, queue.queueUrl); |
| 194 | // Output<[string, string]> |
| 195 | |
| 196 | const url = both.pipe( |
| 197 | Output.map(([name, queue]) => `s3://${name}?dlq=${queue}`), |
| 198 | ); |
| 199 | ``` |
| 200 | |
| 201 | If you pass an array of `Output<T>` of unknown length, the result |
| 202 | is `Output<T[]>` instead of a fixed tuple. |
| 203 | |
| 204 | ## `interpolate` |
| 205 | |
| 206 | The most common combination of `all` + `map` is "build a string |
| 207 | from outputs." `Output.interpolate` is a tagged template literal |
| 208 | for exactly that: |
| 209 | |
| 210 | ```typescript |
| 211 | const arn = Output.interpolate`arn:aws:s3:::${bucket.bucketName}/objects/*`; |
| 212 | // Output<string> |
| 213 | |
| 214 | const dsn = Output.interpolate`postgres://${db.host}:${db.port}/${db.name}`; |
| 215 | ``` |
| 216 | |
| 217 | Nullish interpolated values render as the empty string. |
| 218 | |
| 219 | Behind the scenes this is `Output.all(...args).pipe(Output.map(...))` — |
| 220 | so the dependency graph is wired up automatically. |
| 221 | |
| 222 | ## `of` |
| 223 | |
| 224 | You rarely call this directly because resources already act like |
| 225 | `Output`s, but it's how the conversion happens internally: |
| 226 | |
| 227 | ```typescript |
| 228 | const bucketOut = Output.of(bucket); // Output<Bucket> |
| 229 | const refOut = Output.of(Ref({ id: "B" })); // for cross-stack refs |
| 230 | ``` |
| 231 | |
| 232 | Use `Output.of(Ref(...))` (or [`Output.ref`](#ref)) |
| 233 | to read a resource's attributes from another stack — see below. |
| 234 | |
| 235 | ## `ref` |
| 236 | |
| 237 | `Output.ref` produces an `Output` referencing a deployed |
| 238 | resource's attributes in another stack or stage: |
| 239 | |
| 240 | ```typescript |
| 241 | Output.ref<typeof Bucket>("Bucket", { stack: "shared-infra", stage: "prod" }); |
| 242 | ``` |
| 243 | |
| 244 | See [References](/infrastructure-as-code/references) for the full reference |
| 245 | surface (`Output.ref`, `Resource.ref`, `Output.stackRef`, |
| 246 | `Stack.stage`) and how each is resolved. |
| 247 | |
| 248 | ## How Outputs compose in props |
| 249 | |
| 250 | You can pass an `Output` (or any structure containing Outputs) as |
| 251 | the input prop of another resource. Alchemy walks the structure, |
| 252 | collects upstream dependencies, and waits for them to resolve |
| 253 | before calling the provider. |
| 254 | |
| 255 | ```typescript |
| 256 | const queue = yield* AWS.SQS.Queue("Jobs", { |
| 257 | name: Output.interpolate`${bucket.bucketName}-events`, |
| 258 | tags: { |
| 259 | bucket: bucket.bucketName, |
| 260 | region: Output.literal("us-west-2"), |
| 261 | }, |
| 262 | deadLetter: { |
| 263 | queueUrl: dlq.queueUrl, |
| 264 | }, |
| 265 | }); |
| 266 | ``` |
| 267 | |
| 268 | Plain values, Outputs, nested objects, and arrays are all valid — |
| 269 | the engine evaluates them recursively. |
| 270 | |
| 271 | ## Helpers |
| 272 | |
| 273 | | Helper | Returns | |
| 274 | | ---------------------- | --------------------------------------------- | |
| 275 | | `Output.isOutput(v)` | `true` if `v` is an `Output<T>` | |
| 276 | | `Output.isExpr(v)` | `true` for any internal expression node | |
| 277 | | `Output.upstream(o)` | Map of upstream resources `o` depends on | |
| 278 | | `Output.hasOutputs(v)` | `true` if `v` (or anything inside) is lazy | |
| 279 | | `Output.toEnvKey(id, suffix)` | `"my-bucket" + "name"` → `"MY_BUCKET_NAME"` | |
| 280 | |
| 281 | `upstream` and `hasOutputs` power the dependency graph — most code |
| 282 | won't call them directly, but they're useful when writing custom |
| 283 | providers. |
| 284 | |
| 285 | ## Redacted values |
| 286 | |
| 287 | Alchemy preserves `Redacted<T>` (Effect's secret wrapper) through |
| 288 | evaluation. Logs and console output show `<redacted>` instead of |
| 289 | the underlying value: |
| 290 | |
| 291 | ```typescript |
| 292 | import * as Redacted from "effect/Redacted"; |
| 293 | |
| 294 | const apiKey = Redacted.make(env.API_KEY); |
| 295 | |
| 296 | yield* MyService("svc", { |
| 297 | apiKey, // stays Redacted in state and logs |
| 298 | }); |
| 299 | ``` |
| 300 | |
| 301 | ## Evaluation semantics |
| 302 | |
| 303 | When alchemy needs the actual value of an `Output` (to call a |
| 304 | provider, to print outputs at the end of a deploy, to satisfy a |
| 305 | binding), it runs `Output.evaluate(expr, upstream)`: |
| 306 | |
| 307 | 1. **Resource expressions** look up the resolved attributes of |
| 308 | their upstream resource. |
| 309 | 2. **Property expressions** evaluate their parent and read the |
| 310 | property. |
| 311 | 3. **Apply / EffectExpr** evaluate the parent first, then run the |
| 312 | user function. |
| 313 | 4. **All** evaluates its children in parallel. |
| 314 | 5. **Ref** reads from the state store using `{ stack, stage, id }`. |
| 315 | 6. **Plain values** (objects, arrays, primitives) are walked |
| 316 | recursively so Outputs inside them get evaluated too. |
| 317 | 7. **`Redacted`** values are preserved as-is. |
| 318 | |
| 319 | You normally never call `Output.evaluate` yourself — alchemy invokes |
| 320 | it during plan and apply. But understanding the ordering helps: |
| 321 | **every Output is lazy until alchemy decides to resolve it**, and |
| 322 | the deploy graph is exactly the set of dependencies your Outputs |
| 323 | declared. |
| 324 | |
| 325 | ## Cheat sheet |
| 326 | |
| 327 | | You want to… | Use | |
| 328 | | ------------------------------------------- | -------------------------------------------- | |
| 329 | | Reference an attribute that doesn't exist yet | `resource.attr` | |
| 330 | | Wrap a constant as an Output | `Output.literal(value)` | |
| 331 | | Coerce a value / Effect / Output to Output | `Output.asOutput(x)` | |
| 332 | | Transform an Output | `output.pipe(Output.map(fn))` | |
| 333 | | Transform with an Effect | `output.pipe(Output.mapEffect(fn))` | |
| 334 | | Combine several Outputs | `Output.all(a, b, c)` | |
| 335 | | Build a string from Outputs | `` Output.interpolate`a/${b}/c` `` | |
| 336 | | Read a resource from another stack | `Output.ref<typeof X>("id", { stack, stage })` | |
| 337 | | Inspect dependencies | `Output.upstream(output)` | |
| 338 | |
| 339 | ## Where next |
| 340 | |
| 341 | - [References](/infrastructure-as-code/references) — read Outputs across |
| 342 | stacks and stages. Next page. |
| 343 | - [Resource lifecycle](/infrastructure-as-code/resource-lifecycle) — how |
| 344 | the graph deploys. |
| 345 | - [Resources](/infrastructure-as-code/resource) — what an Output actually |
| 346 | flows into. |
| 347 | - [Layers](/infrastructure-as-effects/layers) — hide resources and their |
| 348 | Outputs behind a service interface. |