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').endpoint returns an output that resolves once db registers, which can be later in the same run.
  • 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:

LevelPublishReach
Appctx.export('x', v)Stack output
Blockthis.output('x', v)ctx.ref('block').x
ResourceIts own outputsctx.ref('res').arn

Errors

MistakeWhat 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 nameAn error as soon as the second one is constructed
Reading an output the resource doesn't haveAn error during deploy: "id" has no output "x"
Two references that point at each otherThe 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() or interpolate on 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.