Blocks
Group related resources into one named unit.
A Block is an optional grouping for Anvil resources. You write a class that
extends anvil.Block, create the resources it needs inside its constructor, and
then use the class like any other resource. Blocks are optional: flat top-level resources work
the same way. In the project below, SharedResourceBlock creates an S3 bucket and a
DSQL database, and anvil.config.ts uses it like any other resource: it creates a
Lambda and an HTTP API, connects the Lambda to the Block's database, and exports the API URL.
block-demo/anvil.config.ts · 42 lines
import { App } from '@anvil-cloud/sdk';
import * as anvil from '@anvil-cloud/sdk';
import { SharedResourceBlock } from './blocks/sharedResourceBlock';
export default new App({
defaults: {
tags: {
costCenter: 'platform-eng',
project: 'block-demo',
},
},
providers: {
aws: { region: 'ap-southeast-2' },
},
run(ctx) {
const shared = new SharedResourceBlock('shared');
const getItems = new anvil.aws.Lambda('get-items', {
runtime: 'nodejs24.x',
handler: 'handlers/getItems.handler',
environment: {
DSQL_ENDPOINT: shared.database.endpoint,
},
});
shared.database.grantConnect(getItems, {
schemas: [{ schema: 'app', roleName: 'app_read' }],
});
const api = new anvil.aws.HttpApi('api', {
routes: [
{
method: 'GET',
path: '/items',
consumer: { lambda: { arn: getItems.arn } },
},
],
});
ctx.export('apiUrl', api.url);
ctx.export('bucketName', shared.assets.bucketName);
},
});
block-demo/anvil.config.py · 30 lines
import anvil_cloud as anvil
from blocks.shared_resource_block import SharedResourceBlock
def infra(ctx: anvil.Context):
shared = SharedResourceBlock("shared")
get_items = anvil.aws.Lambda("get-items",
runtime="nodejs24.x",
handler="handlers/getItems.handler",
environment={"DSQL_ENDPOINT": shared.database.endpoint},
)
shared.database.grant_connect(get_items,
schemas=[{"schema": "app", "role_name": "app_read"}],
)
api = anvil.aws.HttpApi("api",
routes=[{
"method": "GET",
"path": "/items",
"consumer": {"lambda_": {"arn": get_items.arn}},
}],
)
ctx.export("api_url", api.url)
ctx.export("bucket_name", shared.assets.bucket_name)
anvil.App(run=infra)
block-demo/main.go · 59 lines
package main
import (
"block-demo/blocks"
"github.com/anvil-cloud/anvil/sdk/go/anvil"
anvilaws "github.com/anvil-cloud/anvil/sdk/go/anvil/aws"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
anvil.Run(anvil.AppConfig{
Run: func(ctx *anvil.Context) error {
shared, err := blocks.NewSharedResourceBlock(ctx.PulumiCtx(), "shared")
if err != nil {
return err
}
getItems, err := anvilaws.NewLambda(ctx.PulumiCtx(), "get-items", &anvilaws.LambdaArgs{
Runtime: anvilaws.LambdaRuntime_Nodejs24_x,
Handler: pulumi.String("handlers/getItems.handler"),
Environment: pulumi.StringMap{"DSQL_ENDPOINT": shared.Database.Endpoint},
})
if err != nil {
return err
}
_, err = anvilaws.NewDSQLConnect(ctx.PulumiCtx(), "get-items-connect", &anvilaws.DSQLConnectArgs{
ClusterArns: shared.Database.ClusterArns,
TargetRoleArn: getItems.RoleArn,
TargetName: pulumi.String("get-items"),
DbRoles: pulumi.StringArray{pulumi.String("app_read")},
RolesTableName: shared.Database.RolesTableName,
})
if err != nil {
return err
}
api, err := anvilaws.NewHttpApi(ctx.PulumiCtx(), "api", &anvilaws.HttpApiArgs{
Routes: anvilaws.HttpApiRouteArray{
anvilaws.HttpApiRouteArgs{
Method: anvilaws.HttpApiMethod("GET"),
Path: pulumi.String("/items"),
Consumer: anvilaws.HttpApiRouteConsumerArgs{
Lambda: &anvilaws.HttpApiLambdaConsumerArgs{Arn: getItems.Arn},
},
},
},
})
if err != nil {
return err
}
ctx.Export("apiUrl", api.Url)
ctx.Export("bucketName", shared.Assets.BucketName)
return nil
},
})
}
Anvil's Lambda runtimes are Node.js only (nodejs24.x, nodejs22.x), so the
function code stays in TypeScript in every language. Only the infrastructure is written in each
language.
What a Block gives you
- Parenting. Resources created inside the constructor belong to the Block. In
TypeScript and Python this is automatic. In Go, pass
r.Opts()to each child. stageandproject. Every Block can read the current stage and project name, so names can include the stage without hardcoding it.- Published outputs. A Block can expose selected values with
this.output(name, value). Other code can reach them throughctx.ref(see App & Context). - Automatic tags. Default tags from
Appapply to resources inside a Block too.
Outputs
Only publish values from resources the Block owns. A Block is a container for its own resources, not a relay for resources it doesn't create. Published outputs are how other parts of the program read the Block's results without reaching into its internals.