(core): add DisplayNames API for automatic Name tag assignment
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
### Describe the feature
Many AWS resources — especially in EC2/VPC — do not have a physical name property and rely on the `Name` tag as their display name in the AWS Console. When creating infrastructure with CDK, resources such as Internet Gateways, NAT Gateways, Route Tables, and VPC Endpoints are often left without a `Name` tag, making them difficult to identify in the Console.
Some L2 constructs (e.g., `Vpc` and `Subnet`) do set a `Name` tag on themselves, but their child resources (e.g., the `CfnInternetGateway` created inside a `Vpc`) typically receive the parent's `Name` tag through aspect propagation rather than getting their own meaningful name.
This proposal adds a `DisplayNames` API to `aws-cdk-lib/core` that applies `Name` tags as an Aspect, targeting resources that lack a physical name property. By default, the construct's `node.path` is used as the `Name` tag value, and existing `Name` tags set by L2 constructs are preserved.
```ts
// Fill in missing Name tags across the entire stack
DisplayNames.of(stack).apply();
// Overwrite all Name tags with node.path
DisplayNames.of(stack).apply({ overwrite: true });
// Only target EC2/VPC resources
DisplayNames.of(stack).applyToEc2();
// Use a custom name resolver
DisplayNames.of(stack).apply({
nameResolver: (node) => node.node.path.split('/').slice(-2).join('/'),
});
```
### Use Case
When I deploy a VPC with CDK, the VPC and Subnets get `Name` tags automatically, but many of the child resources — Internet Gateways, NAT Gateways, Route Tables, Elastic IPs, etc. — end up unnamed or all sharing the parent VPC's name through tag propagation. In the AWS Console, I see a list of Internet Gateways all labeled with the same VPC name, or worse, with no name at all. Identifying which resource belongs to which construct becomes a manual process of cross-referencing resource IDs with the CloudFormation stack.
This is especially painful during incident response or cost investigation, where quickly identifying resources in the Console matters. Today, the only workaround is to manually add `Tags.of(resource).add('Name', ...)` to every resource that needs a display name. However, users typically don't know — and shouldn't need to know — which resources have a physical name property and which rely on the `Name` tag for identification. This makes it impractical to add `Name` tags on a per-resource basis; it's easy to miss resources, and the list of unnamed resources changes as the stack evolves.
Ideally, CDK would consistently apply a unique `Name` tag to every resource that needs one. However, changing the default tagging behavior of existing L2 constructs would be a breaking change — it would modify CloudFormation templates for all existing stacks, potentially triggering unexpected tag updates on deployed resources. A separate opt-in API avoids this backward compatibility concern while giving users a simple, one-line way to fill in the gaps.
### Proposed Solution
Implement a `DisplayNames` class in `aws-cdk-lib/core` that uses the CDK Aspects mechanism to walk the construct tree and apply `Name` tags to resources that lack a physical name property.
**Why in core, not a user-land Aspect:**
While users can write their own Aspect to add `Name` tags, doing so correctly requires handling several non-trivial concerns. `DisplayNames` encapsulates all of them as built-in helper functionality:
1. **Automatic physical name detection** — Users would need to determine which resources already have a physical name property and which rely on the `Name` tag. `DisplayNames` handles this automatically by inspecting L1 CloudFormation properties at runtime via `TreeInspector`.
2. **Scoped convenience methods** — `applyToEc2()` targets only `AWS::EC2::*` resources, covering the most common use case in a single call:
```ts
// Instead of manually listing resource types:
DisplayNames.of(stack).applyToEc2();
```
3. **Overwrite control with correct Aspect ordering** — Respecting existing `Name` tags set by L2 constructs requires running after Tag aspects. `DisplayNames` handles this by setting an appropriate Aspect priority higher than `MUTATING`:
```ts
// Safe default: preserves Name tags set by Vpc, Subnet, etc.
DisplayNames.of(stack).apply();
// Explicitly overwrite all Name tags with node.path
DisplayNames.of(stack).apply({ overwrite: true });
```
4. **Custom name resolver** — Users can customize the `Name` tag value per resource, or return `undefined` to skip specific resources:
```ts
// Use only the last two path segments as the Name
DisplayNames.of(stack).apply({
nameResolver: (node) => node.node.path.split('/').slice(-2).join('/'),
});
// Skip specific resources by returning undefined
DisplayNames.of(stack).apply({
nameResolver: (node) => {
if (node.cfnResourceType === 'AWS::EC2::EIP') return undefined;
return node.node.path;
},
});
```
5. **Resource type filtering** — Fine-grained include/exclude filters for handling edge cases:
```ts
// Only target specific resource types
DisplayNames.of(stack).apply({
applyToResourceTypes: ['AWS::EC2::VPC', 'AWS::EC2::Subnet', 'AWS::EC2::InternetGateway'],
});
// Exclude specific resource types from the default behavior
DisplayNames.of(stack).apply({
excludeResourceTypes: ['AWS::EC2::VPCEndpointService'],
});
```
Placing this in `aws-cdk-lib/core` alongside `Tags` provides a consistent API surface and allows direct access to core internals (`TagManager`, `CfnResource`, `TreeInspector`) without cross-module dependencies:
```ts
// Familiar pattern — consistent with Tags.of()
Tags.of(stack).add('Environment', 'prod');
DisplayNames.of(stack).apply();
```
**How physical name detection works:**
The heuristic inspects each resource's CloudFormation properties for keys ending in `name` (case-insensitive), then checks whether the prefix portion matches the resource type suffix — e.g., `launchTemplateName` → prefix `launchtemplate` matches type `LaunchTemplate`, so it's recognized as a physical name; `serviceName` → prefix `service` does NOT match `VPCEndpoint`, so it's not.
This is not a perfect solution — it relies on CloudFormation's naming conventions being consistent. However, it has been verified against the major EC2/VPC resource types and correctly handles all of them:
- Correctly identified as having a physical name (skipped): `SecurityGroup` (`groupName`), `KeyPair` (`keyName`), `LaunchTemplate` (`launchTemplateName`), `PrefixList` (`prefixListName`)
- Correctly identified as NOT having a physical name (tagged): `VPC`, `Subnet`, `InternetGateway`, `NatGateway`, `RouteTable`, `NetworkAcl`, `EIP`, `PlacementGroup`
- Correctly rejected false positives — properties ending in `name` that are NOT physical names: `VPCEndpoint` (`serviceName`), `DHCPOptions` (`domainName`), `FlowLog` (`logGroupName`), `Instance` (`keyName`), `CustomerGateway` (`deviceName`)
For any edge cases in other service namespaces, users can override the behavior via `applyToResourceTypes` or `excludeResourceTypes`.
I already have a working implementation with tests. If this proposal looks good, I'll open a PR.
### Other Information
_No response_
### Acknowledgements
- [x] I may be able to implement this feature request
- [ ] This feature might incur a breaking change
### AWS CDK Library version (aws-cdk-lib)
2.x
### AWS CDK CLI version
2.x
### Environment details (OS name and version, etc.)
all
Contributor guide
Research direction
Review the core APIs around Aspects, Tags, CfnResource, and TreeInspector described in the issue, then inspect the author's existing implementation and tests. Verify physical-name detection, tag preservation and overwrite ordering, EC2 filtering, custom resolvers, and resource-type include/exclude behavior; done means the proposed API and tests cover these cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- cloud, infrastructure, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100