Forward References
Reference a resource by name, before or after it's declared.
Normally you reference a resource through the variable it's assigned to, so it must be declared
first: declaration order has to match dependency order. ctx.ref(id) removes that
constraint. It looks a resource up by its logical name (the first argument to its
constructor), wherever it's declared in run:
import { App } from '@anvil-cloud/sdk';
import * as anvil from '@anvil-cloud/sdk';
export default new App({
run(ctx) {
const fn = new anvil.aws.Lambda('processor', {
runtime: 'nodejs24.x',
handler: 'functions/processor/index.handler',
environment: {
// forward reference — `db` is declared below
DSQL_ENDPOINT: ctx.ref<anvil.aws.DSQL>('db').endpoint,
},
});
const db = new anvil.aws.DSQL('db', { /* ... */ });
// Interchangeable with db.endpoint
ctx.export('endpoint', ctx.ref('db').endpoint);
},
});def infra(ctx: anvil.Context):
fn = anvil.aws.Lambda("processor",
runtime=LambdaRuntime.NODEJS24_X,
handler="functions/processor/index.handler",
environment={
# forward reference — `db` is declared below
"DSQL_ENDPOINT": ctx.ref("db").endpoint,
},
)
db = anvil.aws.DSQL("db", schemas=[...])
# Interchangeable with db.endpoint
ctx.export("endpoint", ctx.ref("db").endpoint)
anvil.App(run=infra)anvil.Run(anvil.AppConfig{
Run: func(ctx *anvil.Context) error {
// Go does not yet support forward references (ctx.Ref) —
// declare resources in dependency order.
db, err := anvilaws.NewDSQL(ctx.PulumiCtx(), "db", nil)
if err != nil {
return err
}
_, err = anvilaws.NewLambda(ctx.PulumiCtx(), "processor", &anvilaws.LambdaArgs{
Runtime: anvilaws.LambdaRuntime_Nodejs24_x,
Handler: pulumi.String("functions/processor/index.handler"),
Environment: pulumi.StringMap{"DSQL_ENDPOINT": db.Endpoint},
})
return err
},
})ctx.ref('db').endpoint resolves to the same output as db.endpoint,
so the two are interchangeable. In TypeScript, the generic (ctx.ref<anvil.aws.DSQL>) is
only there for editor autocomplete and has no effect at runtime.
Forward references are an escape hatch. Most programs declare resources in dependency order and
never need one. Use ctx.ref when one resource occasionally needs something declared
later and reordering the file would make it harder to read.
How it works
- Resources are still created immediately, in the order you write them. Nothing is deferred.
- Each component registers itself by logical name as it's constructed.
ctx.ref('db').endpointreturns an output that resolves oncedbregisters, which can be later in the samerun.- Because the result is a real Pulumi output, Pulumi still works out the dependency graph, deploy order and parallelism. Anvil doesn't build its own graph.
Keep references flowing one way
Because you can reference in either direction, you can create a cycle that ordered code wouldn't let
you write. For example, a queue whose consumer is ctx.ref('worker').arn and a Lambda whose
environment uses ctx.ref('jobs').url. Both references resolve, but each resource then
waits on the other's output.
A cycle is not reported as an error. The deploy creates everything outside the cycle and then hangs. If a deploy stalls with resources stuck waiting on each other, look for two forward references that point at each other, and remove one. (In the queue example, an SQS-triggered Lambda receives messages through its event source mapping and doesn't need the queue URL.)
Blocks
Forward references work across Blocks with no extra setup, because
Blocks group resources without changing deploy order. A reference to a Block reaches only its published outputs (what it exposes with this.output(...)), not the
resources inside it:
class Storage extends anvil.Block {
constructor(name: string) {
super(name);
const events = new anvil.aws.Bucket('events');
this.output('bucketName', events.bucketName); // the block's public surface
}
}
// elsewhere in run():
ctx.ref<Storage>('storage').bucketName; // ok — a published output
new Storage('storage');class Storage(anvil.Block):
def __init__(self, name: str):
super().__init__(name)
events = anvil.aws.Bucket("events")
self.output("bucket_name", events.bucket_name) # the block's public surface
# elsewhere in infra():
ctx.ref("storage").bucket_name # ok — a published output
Storage("storage")// Go does not yet support forward references.
// Read a block's outputs from the block value, after it is created.The same publish-and-reach pattern applies at every level:
| Level | Publish | Reach |
|---|---|---|
| App | ctx.export('x', v) | Stack output |
| Block | this.output('x', v) | ctx.ref('block').x |
| Resource | Its own outputs | ctx.ref('res').arn |
Errors
| Mistake | What happens |
|---|---|
| Referencing a name that's never declared (e.g. a typo) | A clear error after run finishes, before anything deploys |
| Two resources with the same name | An error as soon as the second one is constructed |
| Reading an output the resource doesn't have | An error during deploy: "id" has no output "x" |
| Two references that point at each other | The deploy hangs (see above) |
Limits
- Pass outputs, don't transform them. You can pass a forward-referenced output into another
resource's arguments or into
ctx.export. Calling.apply()orinterpolateon one isn't supported yet. - Not a grant target.
bucket.grantRead(ctx.ref('fn'))won't work. Call grants on the resource object itself. - TypeScript and Python only. In Go, declare resources in dependency order.