What is a Token in AWS CDK

avatar
Borislav Hadzhiev

Last updated: Jan 27, 2024
4 min

banner

# Tokens in AWS CDK

Some values in our CDK code can't get resolved at synthesis time, but rather get resolved at deployment time. These values are called Tokens.

Tokens are encoded values, most commonly, attributes of resources we define in our CDK stack that will only get resolved at deployment time.

For example, let's define a simple Lambda function and try to access its functionName property in our CDK code.

const myFunction = new NodejsFunction(this, id, { runtime: lambda.Runtime.NODEJS_18_X, handler: 'main', entry: path.join(__dirname, `/../src/path/index.js`), }); console.log('function name ๐Ÿ‘‰', myFunction.functionName);

If we run npx aws-cdk deploy we get a result like ${Token[Token.123]}.

function name token

We can see that the value of the functionName attribute is a token at synthesis time.

# Explicitly setting Resource names

Even if we explicitly set the functionName property on the Lambda we wouldn't be able to access the resolved value in our code.

Let's add a Dynamodb table to our code and explicitly set the tableName and functionName properties on the resources.

const myTable = new dynamodb.Table(this, 'my-table', { // ๐Ÿ‘‡ explicitly setting tableName tableName: 'my-table', partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: cdk.RemovalPolicy.DESTROY, }); console.log('table name ๐Ÿ‘‰', myTable.tableName); const myFunction = new NodejsFunction(this, id, { // ๐Ÿ‘‡ explicitly setting functionName functionName: 'my-function', runtime: lambda.Runtime.NODEJS_18_X, handler: 'main', entry: path.join(__dirname, `/../src/path/index.js`), }); console.log('function name ๐Ÿ‘‰', myFunction.functionName);

If we run npx aws-cdk deploy, we can see that we still get the unresolved token values.

with set names

Obviously, if we hardcode the table and function names, we have access to these values in our CDK code, but trying to access the attributes still returns a Token.

# How CDK Tokens actually work

Since CDK gets compiled down to CloudFormation code, all these tokens are represented as CloudFormation Ref functions.

At deployment time CloudFormation goes and replaces these references with the actual values - in our case the table and function names.

Let's update our code to remove the explicitly set resource names and pass the DynamoDB table name as an environment variable to the lambda and inspect our CloudFormation template.

const myTable = new dynamodb.Table(this, 'my-table', { partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: cdk.RemovalPolicy.DESTROY, }); const myFunction = new NodejsFunction(this, id, { runtime: lambda.Runtime.NODEJS_18_X, handler: 'main', entry: path.join(__dirname, `/../src/path/index.js`), // ๐Ÿ‘‡ passing in tableName as env variable environment: { tableName: myTable.tableName, }, });

Let's synth our CDK stack and look at the produced CloudFormation template.

shell
npx aws-cdk synth --no-staging > template.yaml

The important parts of the CloudFormation template are the following.

template.yaml
Resources: mytable0324D45C: Type: AWS::DynamoDB::Table mycdkstack4E08F0DD: Type: AWS::Lambda::Function Properties: Environment: Variables: tableName: Ref: mytable0324D45C

Notice how the table name environment variable points to a reference that will eventually get replaced with the real value from CloudFormation.

When we deploy our stack, we can see that the value of the environment variable is resolved and accessible in our Lambda function.

resolved value in lambda

# Caveats with CDK Tokens

A common source of confusion with Tokens in CDK is when we try to access the region, accountId or availabilityZones properties in an environment agnostic stack and get Token values back.

It's a common requirement to conditionally update resource types based on the account or region we are deploying to.

const app = new cdk.App(); new MyCdkStack(app, 'my-cdk-stack', { stackName: `my-cdk-stack`, // ๐Ÿ‘‡ not specifying the environment // env: { // region: process.env.CDK_DEFAULT_REGION, // account: process.env.CDK_DEFAULT_ACCOUNT, // }, });

If we now try to access the region, accountId or availabilityZones in our CDK code:

export class MyCdkStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ๐Ÿ‘‡ recommended way to get access to the properties console.log('accountId: ', cdk.Stack.of(this).account); console.log('region: ', cdk.Stack.of(this).region); console.log('availability zones: ', cdk.Stack.of(this).availabilityZones); } }

We would simply get back unresolved Token values:

env agnostic stack props

The way to solve this is to uncomment the commented lines in the snippet above and explicitly set the environment for our stack. I have written an article on this - Get region and AccountID in CDK

# Exporting Token values as Outputs

It's a common requirement to export resource attributes as Outputs so that they can be accessed from our frontend code.

For example, to provide our frontend code access to an API's URL, we would use Outputs and write the Outputs to a JSON file, which the frontend code can import and use.

Let's see how we can export our lambda and table names and when the values get resolved.

const myTable = new dynamodb.Table(this, 'my-table', { partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: cdk.RemovalPolicy.DESTROY, }); const myFunction = new NodejsFunction(this, id, { runtime: lambda.Runtime.NODEJS_18_X, handler: 'main', entry: path.join(__dirname, `/../src/path/index.js`), environment: { tableName: myTable.tableName, }, }); // ๐Ÿ‘‡ export values as Outputs new cdk.CfnOutput(this, 'tableName', {value: myTable.tableName}); new cdk.CfnOutput(this, 'lambdaName', {value: myFunction.functionName});

Let's deploy our stack and see if the values are solved in the file:

shell
npx aws-cdk deploy --outputs-file ./cdk-exports.json

If we open the cdk-exports.json file after the deployment, the result looks as follows.

cdk-exports.json
{ "my-cdk-stack": { "tableName": "my-cdk-stack-mytable0324D45C-11VORISY29H3L", "lambdaName": "my-cdk-stack-mycdkstack4E08F0DD-KVZpFjrsLC3F" } }

The concept is the same, our CloudFormation template has specified references to these resources, which will eventually be replaced with the real values at deployment time.

template.yaml
Resources: mytable0324D45C: Type: AWS::DynamoDB::Table mycdkstack4E08F0DD: Type: AWS::Lambda::Function # ๐Ÿ‘‡ Outputs reference the resources Outputs: tableName: Value: Ref: mytable0324D45C lambdaName: Value: Ref: mycdkstack4E08F0DD

# Discussion

Tokens are placeholder values that get replaced with the real value at deployment time by CloudFormation.

If we try to access resource attributes in our CDK code, i.e. in conditional statements, we would get an encoded Token value.

The most common source of confusion is trying to access the accountId, region or availabilityZones properties and getting Token values, because we have not set the environment of the stack explicitly - How to get accountId and region in AWS CDK.

# Additional Resources

You can learn more about the related topics by checking out the following tutorials:

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.

Copyright ยฉ 2024 Borislav Hadzhiev