Identifiers within AWS CDK

Archive post - originally published 30 September 2021 on devsintheshed.com. The example is CDK v1 in JavaScript, and I’ve transcribed the code from the original article’s screenshots (the two lines of requires and the class declaration above the visible area are the standard cdk init template). The concept of construct IDs and unique IDs is unchanged in v2, but the imports have moved and there’s a renaming trap worth knowing about, both covered in the 2026 appendix at the end.

Objects in a CDK construct #

When working within a Construct, AWS resources are referenced as objects when created. In the code below, this.acme_vpc is an example of an object.

const cdk = require('@aws-cdk/core');
const ec2 = require('@aws-cdk/aws-ec2');

class MyWidgetServiceStack extends cdk.Stack {
  /**
   *
   * @param {cdk.Construct} scope
   * @param {string} id
   * @param {cdk.StackProps=} props
   */
  constructor(scope, id, props) {
    super(scope, id, props);

    // The code that defines your stack goes here
    this.acme_vpc = new ec2.Vpc(this, 'ACMEVPC', {
      natGateways: 0,
      subnetConfiguration: [{
        cidrMask: 24,
        name: "asterisk",
        subnetType: ec2.SubnetType.PUBLIC
      }]
    });

    this.acme_sg = new ec2.SecurityGroup(this, 'ACMESG', {
      vpc: this.acme_vpc,
      allowAllOutbound: false,
      securityGroupName: 'ACME-SG',
    });
    this.acme_sg.addIngressRule(ec2.Peer.ipv4('10.0.0.0/16'), ec2.Port.tcp(3306));
  }
}

You can also see that I refer to the this.acme_vpc object on line 25 to set the context of where a security group rule should be applied.

Construct ID’s and Unique ID’s #

In the code above, the second argument passed into this.acme_vpc is ‘ACMEVPC’.

ACMEVPC is a Construct ID, and it’s unique to the scope of the stack it was created in. If you create multiple stacks based on this stack as a template that’s still fine, because when you run a ‘cdk synth’ or ‘cdk deploy’ command a Unique ID is created, generating an 8 digit hash on the end of your Construct ID within your Cloudformation.

Below is the Cloudformation generated with a ‘cdk synth’ command run on the acme_vpc we built in CDK above.

{
    "Resources": {
        "ACMEVPC6C5210C7": {
            "Type": "AWS::EC2::VPC",
            "Properties": {
                "CidrBlock": "10.0.0.0/16",
                "EnableDnsHostnames": true,
                "EnableDnsSupport": true,
                "InstanceTenancy": "default",
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": "MyWidgetServiceStack/ACMEVPC"
                    }
                ]
            },
            "Metadata": {
                "aws:cdk:path": "MyWidgetServiceStack/ACMEVPC/Resource"
            }
        },
        "ACMEVPCasteriskSubnet1Subnet98240AD5": {
            "Type": "AWS::EC2::Subnet",
            ...
        }
    }
}

You can see the Construct ID ACMEVPC has had an 8 digit hash (6C5210C7) appended to it to form the Unique ID ACMEVPC6C5210C7.

As mentioned above, if you had created multiple stacks based on this primary stack as a template, you would see the same construct id followed by the unique 8 digit hash for each resource making up the unique id within Cloudformation.

If you’d like to go deeper on construct IDs and unique IDs, the AWS CDK Identifiers documentation covers the above in more depth.

– Matt Coles

Originally published at https://www.devsintheshed.com

Appendix (2026): what’s changed in CDK v2 #

The idea above is one of the most durable things in CDK: a construct ID is the name you give a construct within its scope, and at synth time CDK turns the full path of construct IDs into a logical ID, your ID plus that 8-character hash (ACMEVPCACMEVPC6C5210C7). That hasn’t changed in v2 at all. What’s changed is the code around it, plus one consequence of the identifier model that the original post should have spelled out.

The imports moved (and v1 is dead). CDK v1 went end-of-support in June 2023, so the require('@aws-cdk/core') / require('@aws-cdk/aws-ec2') style from the example no longer installs. v2 ships a single aws-cdk-lib, with Construct now coming from the separate constructs package. The same JavaScript stack in v2 starts like this:

const { Stack, aws_ec2: ec2 } = require('aws-cdk-lib');
const { Construct } = require('constructs');

class MyWidgetServiceStack extends Stack {
  // ...everything inside is identical
}

The new ec2.Vpc(this, 'ACMEVPC', { ... }) line and the construct-ID behaviour are byte-for-byte the same, only where Vpc, Stack, and Construct come from changed. One small note: natGateways and subnetConfiguration still work, but in v2 the cidr prop on Vpc is deprecated in favour of ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16').

Renaming a construct ID replaces the resource. Because the logical ID is derived from the construct ID, changing the ID (say 'ACMEVPC' to 'AcmeVpc') produces a different logical ID, and CloudFormation has no idea it’s the same thing. It sees one resource removed and a new one added, so it deletes the old resource and creates a fresh one, and if you do that to a live database you’ve lost the data. This was just as true in v1, but it bites people constantly, so it’s worth saying plainly.

Two ways out, both better in v2 than they were in 2021:

  • construct.overrideLogicalId('ACMEVPC') pins the logical ID so you can rename the construct in code without CloudFormation noticing, which is handy for keeping an existing resource while tidying up names.
  • cdk refactor (recent) moves and renames constructs while telling CloudFormation the resource is the same one, so it’s relocated in the tree instead of deleted and recreated. This is the proper fix, and it didn’t exist when I first wrote this.

The docs link changed too. The cdk/latest URL now redirects to v2, and the current Identifiers guide lives at https://docs.aws.amazon.com/cdk/v2/guide/identifiers.html.

Beyond that, nothing about identifiers changed. Install aws-cdk-lib, import Construct from constructs, and run cdk synth, and you’ll see the same construct-ID-plus-hash logical IDs in cdk.out that the original post walked through. Name a construct ID once and try very hard not to rename it.

$ comments --load