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 IDs and unique IDs #

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 central idea has survived CDK v2 unchanged. 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). The surrounding code has moved, and there’s one nasty consequence of that identifier model I should have called out in the original post.

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 comes from the construct ID, changing 'ACMEVPC' to 'AcmeVpc' produces a different logical ID. CloudFormation can’t tell that you only tidied a name. It sees one resource removed and another added, then deletes the old one and creates a fresh one. Do that to a live database and you can lose the data. This was just as true in v1, but it still catches people out.

There are now two sensible ways around it:

  • 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. It lives on the underlying L1 resource (CfnElement), so for an L2 like the VPC you call it on the default child: (acme_vpc.node.defaultChild).overrideLogicalId('ACMEVPC').
  • cdk refactor (recent) moves and renames constructs while telling CloudFormation it’s still the same resource, so it moves in the tree instead of being deleted and recreated. This command didn’t exist when I first wrote the post, and it’s the option I’d reach for now.

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, identifiers work as they did in 2021. Install aws-cdk-lib, import Construct from constructs, run cdk synth, and cdk.out will contain the same construct-ID-plus-hash logical IDs shown above. Pick construct IDs carefully, because changing one later is rarely the harmless cleanup it looks like.

Discussion