1---
2title: Inputs & Outputs
3description: 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
6A [Resource](/infrastructure-as-code/resource)'s **inputs** are the props you pass
7in. Its **outputs** are the attributes the cloud returns after
8creation. The catch: outputs don't exist when you write the code.
9They only exist after the resource is deployed.
10
11Alchemy bridges this with **`Output<T>`** — a lazy, typed reference
12that resolves once the upstream resource has run. You can read
13properties off it, transform it with `pipe`, combine it with other
14outputs, and feed it as input to the next resource. The resource
15graph is built from these dependencies.
16
17This page is a reference for every operator. For the bigger picture
18of how the graph deploys, see
19[Resource Lifecycle](/infrastructure-as-code/resource-lifecycle).
20
21## What an Output is
22
23```typescript
24const bucket = yield* Cloudflare.R2.Bucket("Bucket");
25
26bucket.bucketName;
27// ^? Output<string>
28```
29
30Three things to know:
31
321. **Lazy** — `bucket.bucketName` doesn't have a value yet. It's a
33 description of "the bucket's name once it's created."
342. **Typed** — TypeScript still knows it's `string`, even though
35 you can't `console.log` it directly.
363. **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
40You almost never construct an `Output` yourself — they fall out of
41resource declarations and the operators below.
42
43## Property access
44
45Reading a property off a resource (or any `Output`) returns another
46`Output`:
47
48```typescript
49const bucket = yield* Cloudflare.R2.Bucket("Bucket");
50
51bucket.bucketName; // Output<string>
52bucket.bucketArn; // Output<string>
53bucket.tags?.environment; // Output<string | undefined>
54```
55
56Nested access works too — `expr.nested.deep` walks down the object
57without forcing the value.
58
59## `literal`
60
61When you need an `Output` shape but the value is already known:
62
63```typescript
64import * as Output from "alchemy/Output";
65
66Output.literal("hello"); // Output<string>
67Output.literal(42); // Output<number>
68Output.literal({ a: 1 }); // Output<{ a: number }>
69```
70
71Useful as a default for an optional output, or as a placeholder in
72templates and `Output.all` calls.
73
74## `asOutput`
75
76Lifts a plain value, an `Effect`, or an existing `Output` into an
77`Output`:
78
79```typescript
80Output.asOutput("foo"); // wraps as a literal
81Output.asOutput(Effect.succeed(123)); // wraps as an EffectExpr
82Output.asOutput(existingOutput); // returns it unchanged
83```
84
85Use this when writing helpers that should accept "anything output-y."
86
87## `fromEffect`
88
89Lifts a plan-time Effect into an `Output`. The effect runs when the
90stack resolves the Output during plan/deploy — with the stack's
91services (cloud credentials, region, ...) provided — and never inside
92a deployed runtime:
93
94```typescript
95Output.fromEffect(lookupLatestAmi()); // Output<string>
96```
97
98This is the low-level seam for lookup helpers that read cloud state to
99produce a prop value. Because constructing the Output is inert, such
100helpers are safe to call from composition code that is re-executed
101inside 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
105finders (`AWS.EC2.getAmi`, `AWS.EC2.amazonLinux2023()`,
106`AWS.EC2.ubuntu2404()`, ...) are built on:
107
108```typescript
109const instance = yield* AWS.EC2.Instance("web", {
110 imageId: AWS.EC2.amazonLinux2023(),
111 instanceType: "t3.micro",
112 subnetId: subnet.subnetId,
113});
114```
115
116The effect must not fail (`E = never`) — die with a descriptive error
117for unresolvable lookups.
118
119## `map`
120
121`Output.map` transforms an `Output<A>` into an `Output<B>` without
122forcing it. The function runs once, after the upstream resource
123resolves:
124
125```typescript
126import * as Output from "alchemy/Output";
127
128const upper = bucket.bucketName.pipe(
129 Output.map((name) => name.toUpperCase()),
130);
131// Output<string>
132```
133
134It also supports a data-first form when you don't want to use
135`pipe`:
136
137```typescript
138Output.map(bucket.bucketName, (name) => name.toUpperCase());
139```
140
141`map` composes — pipe several together to build up a chain:
142
143```typescript
144const slug = bucket.bucketName.pipe(
145 Output.map((s) => s.toLowerCase()),
146 Output.map((s) => s.replaceAll("_", "-")),
147);
148```
149
150The function only runs once (per evaluation) and only after every
151upstream `Output` is ready. There is no risk of "the function ran
152before 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 —
158read a file, hit an API, decode a JWT — and you want it to live in
159the Effect graph.
160
161```typescript
162const 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
173Chain 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
183Requirements (the `R` channel of the inner Effects) are tracked in
184the resulting `Output`'s requirements, so anything those Effects
185need 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
190to a tuple (preserving the input shape and types):
191
192```typescript
193const both = Output.all(bucket.bucketName, queue.queueUrl);
194// Output<[string, string]>
195
196const url = both.pipe(
197 Output.map(([name, queue]) => `s3://${name}?dlq=${queue}`),
198);
199```
200
201If you pass an array of `Output<T>` of unknown length, the result
202is `Output<T[]>` instead of a fixed tuple.
203
204## `interpolate`
205
206The most common combination of `all` + `map` is "build a string
207from outputs." `Output.interpolate` is a tagged template literal
208for exactly that:
209
210```typescript
211const arn = Output.interpolate`arn:aws:s3:::${bucket.bucketName}/objects/*`;
212// Output<string>
213
214const dsn = Output.interpolate`postgres://${db.host}:${db.port}/${db.name}`;
215```
216
217Nullish interpolated values render as the empty string.
218
219Behind the scenes this is `Output.all(...args).pipe(Output.map(...))` —
220so the dependency graph is wired up automatically.
221
222## `of`
223
224You rarely call this directly because resources already act like
225`Output`s, but it's how the conversion happens internally:
226
227```typescript
228const bucketOut = Output.of(bucket); // Output<Bucket>
229const refOut = Output.of(Ref({ id: "B" })); // for cross-stack refs
230```
231
232Use `Output.of(Ref(...))` (or [`Output.ref`](#ref))
233to read a resource's attributes from another stack — see below.
234
235## `ref`
236
237`Output.ref` produces an `Output` referencing a deployed
238resource's attributes in another stack or stage:
239
240```typescript
241Output.ref<typeof Bucket>("Bucket", { stack: "shared-infra", stage: "prod" });
242```
243
244See [References](/infrastructure-as-code/references) for the full reference
245surface (`Output.ref`, `Resource.ref`, `Output.stackRef`,
246`Stack.stage`) and how each is resolved.
247
248## How Outputs compose in props
249
250You can pass an `Output` (or any structure containing Outputs) as
251the input prop of another resource. Alchemy walks the structure,
252collects upstream dependencies, and waits for them to resolve
253before calling the provider.
254
255```typescript
256const 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
268Plain values, Outputs, nested objects, and arrays are all valid —
269the 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
282won't call them directly, but they're useful when writing custom
283providers.
284
285## Redacted values
286
287Alchemy preserves `Redacted<T>` (Effect's secret wrapper) through
288evaluation. Logs and console output show `<redacted>` instead of
289the underlying value:
290
291```typescript
292import * as Redacted from "effect/Redacted";
293
294const apiKey = Redacted.make(env.API_KEY);
295
296yield* MyService("svc", {
297 apiKey, // stays Redacted in state and logs
298});
299```
300
301## Evaluation semantics
302
303When alchemy needs the actual value of an `Output` (to call a
304provider, to print outputs at the end of a deploy, to satisfy a
305binding), it runs `Output.evaluate(expr, upstream)`:
306
3071. **Resource expressions** look up the resolved attributes of
308 their upstream resource.
3092. **Property expressions** evaluate their parent and read the
310 property.
3113. **Apply / EffectExpr** evaluate the parent first, then run the
312 user function.
3134. **All** evaluates its children in parallel.
3145. **Ref** reads from the state store using `{ stack, stage, id }`.
3156. **Plain values** (objects, arrays, primitives) are walked
316 recursively so Outputs inside them get evaluated too.
3177. **`Redacted`** values are preserved as-is.
318
319You normally never call `Output.evaluate` yourself — alchemy invokes
320it during plan and apply. But understanding the ordering helps:
321**every Output is lazy until alchemy decides to resolve it**, and
322the deploy graph is exactly the set of dependencies your Outputs
323declared.
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.