In this article, I will introduce a collection of tips for centrally and collectively applying configurations to the properties of multiple resources in AWS CDK.
The tips for bulk property application covered in this article use the following four features:
- Aspects
- PropertyInjectors
- Mixins
- RemovalPolicies
1. Aspects
The first feature I will introduce is Aspects.
Aspects is a feature that allows you to apply the same operation to all resources within any given scope, such as a stack or construct.
1-1. How to Use Aspects
You can apply properties to multiple resources at once by defining a class that implements the IAspect interface and writing property override logic for specific resource types in the visit method, as shown below.
// Aspects are implemented by implementing the IAspect interface
export class BucketVersioningUpdater implements cdk.IAspect {
public visit(node: IConstruct): void {
// The visit method is called for every resource within the construct passed to Aspects,
// so we add a condition to only execute for CfnBucket resources
if (node instanceof CfnBucket) {
// If bucket versioning is disabled, override it to enabled
if (
!node.versioningConfiguration ||
(!cdk.Tokenization.isResolvable(node.versioningConfiguration) &&
node.versioningConfiguration.status !== 'Enabled')
) {
node.addPropertyOverride('VersioningConfiguration', {
Status: 'Enabled',
});
}
}
}
}
const app = new cdk.App();
const stack = new CdkSampleStack(app, 'CdkSampleStack');
// Apply BucketVersioningUpdater to all resources in CdkSampleStack
cdk.Aspects.of(stack).add(new BucketVersioningUpdater());
1-2. Reference Article for Aspects
Aspects can be used not only for overriding properties but also for property validation.
For details on how to validate properties with Aspects, please refer to my article How to Choose Validation Approaches in AWS CDK.
1-3. Adjusting the Execution Order of Multiple Aspects
When applying multiple Aspects to the same scope, you can adjust the execution order of each Aspect by specifying a priority. This property was introduced in AWS CDK v2.172.0.
You specify a numeric value for priority, where a smaller number means higher priority and earlier execution. There is also a class called AspectPriority that can be used like an enum, offering static variables such as MUTATING (=200), DEFAULT (=500), and READONLY (=1000). However, if you assign the same priority value to multiple Aspects, their execution order is not guaranteed, so it is recommended to assign different values to each Aspect.
For example, suppose you apply multiple Aspects to a single stack as shown below. This is an example where property overrides are performed first and property validation is performed last. In this case, the Aspects are executed in the order: AspectForOverride -> AspectForOther1 -> AspectForOther2 -> AspectForValidation.
const app = new cdk.App();
const stack = new CdkSampleStack(app, 'CdkSampleStack');
// priority = 1000
cdk.Aspects.of(stack).add(new AspectForValidation(), { priority: cdk.AspectPriority.READONLY });
// priority = 200
cdk.Aspects.of(stack).add(new AspectForOverride(), { priority: cdk.AspectPriority.MUTATING });
// priority = 500
cdk.Aspects.of(stack).add(new AspectForOther1(), { priority: cdk.AspectPriority.DEFAULT });
// priority = 600
cdk.Aspects.of(stack).add(new AspectForOther2(), { priority: 600 });
When using a library like cdk-nag that validates CDK stacks through Aspects, you can also set its priority to be lower (i.e., a larger priority number) so that validation runs after the other Aspects have been executed.
2. PropertyInjectors
The next feature I will introduce is PropertyInjectors.
This feature was introduced in May 2025 with AWS CDK v2.196.0.
PropertyInjectors is a feature that centrally rewrites the props of all resources of any given type of L2 Construct (including some L3 Constructs) within a given scope.
For more details, please refer to Configure constructs with CDK Blueprints in the AWS CDK Developer Guide.
NOTE: The Developer Guide mentioned above introduces this as an approach called "CDK Blueprints," but in this article, I will consistently use the name PropertyInjectors, which is the actual CDK class name, just as I do with the other features (Aspects, RemovalPolicies).
2-1. How to Use PropertyInjectors
For example, suppose you have a requirement to set blockPublicAccess to BLOCK_ALL for all Amazon S3 buckets in a stack. Here is an example of using PropertyInjectors to meet this requirement.
First, create a MyBucketPropsInjector class that implements IPropertyInjector. Specify the PROPERTY_INJECTION_ID exposed by the target L2 Construct in this.constructUniqueId within the constructor, and have the inject method return a new props object with the desired properties overridden.
export class MyBucketPropsInjector implements IPropertyInjector {
public readonly constructUniqueId: string;
constructor() {
// Specify the PROPERTY_INJECTION_ID exposed by the Bucket construct
this.constructUniqueId = Bucket.PROPERTY_INJECTION_ID;
}
// Return new props with the desired properties overridden
public inject(originalProps: BucketProps, _context: InjectionContext): BucketProps {
return {
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
...originalProps,
};
}
}
Next, add MyBucketPropsInjector to the desired scope as shown below. This allows you to collectively override blockPublicAccess to BLOCK_ALL for all buckets within the scope.
const app = new App();
// When applying to the App
PropertyInjectors.of(app).add(new MyBucketPropsInjector());
// When applying to a specific stack
new Stack(app, 'MyStack', {
propertyInjectors: [new MyBucketPropsInjector()],
});
2-2. Avoiding Infinite Loops
Suppose you want to set serverAccessLogsBucket, which is also of type Bucket, in the props of a Bucket using PropertyInjectors.
PropertyInjectors are executed at the time the target Construct is created.
Therefore, if you implement this requirement using the approach introduced above, when a new Bucket is created for serverAccessLogsBucket, PropertyInjectors will be triggered again. Then, the serverAccessLogsBucket creation process will also be executed within that Bucket, causing an infinite loop that results in an error.
To avoid this, you can implement a skip mechanism like the one shown below to prevent the infinite loop.
export class SpecialBucketInjector implements IPropertyInjector {
public readonly constructUniqueId: string;
// Flag to determine whether to skip
private _skip: boolean;
constructor() {
this._skip = false;
this.constructUniqueId = Bucket.PROPERTY_INJECTION_ID;
}
public inject(originalProps: BucketProps, context: InjectionContext): BucketProps {
// If the skip flag is true, return the original props as-is
if (this._skip) {
return originalProps;
}
let accessLogBucket = originalProps?.serverAccessLogsBucket;
if (!accessLogBucket) {
// Since PropertyInjectors are executed when a new Construct is created,
// set the skip flag to true here
this._skip = true;
// Because the skip flag is true, property overriding will not occur for this instance,
// meaning `serverAccessLogsBucket` will not be overridden for this Bucket
accessLogBucket = new Bucket(context.scope, 'my-access-log', {
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
});
// Reset the skip flag to false
this._skip = false;
}
return {
serverAccessLogsBucket: accessLogBucket,
...originalProps,
};
}
}
2-3. Differences Between Aspects and PropertyInjectors
Both Aspects and PropertyInjectors can apply properties collectively to multiple resources within any given scope, but there are several differences:
- Difference in what they target
- Difference in when they are applied
First, regarding the difference in what they target: while Aspects overrides the configuration properties of L1 Constructs, PropertyInjectors rewrites the props of L2 or L3 Constructs themselves. This is the key distinction.
Next, regarding the difference in when they are applied: Aspects are executed during the second phase of the CDK application lifecycle, the "Prepare phase."
Most Constructs are primarily created during the first phase, the "Construct phase." In other words, Aspects are executed by traversing the construct tree after most of the CDK application's Constructs have been created.
On the other hand, since PropertyInjectors directly rewrite the props of L2 or L3 Constructs, the value overrides are applied at the time the Construct is created (during the first "Construct phase" of the lifecycle).
2-4. When to Use Aspects vs PropertyInjectors
Here are some use cases to consider when choosing between Aspects and PropertyInjectors:
Cases where Aspects are more suitable:
- When the target resources use L1 Constructs
- When you want to make overrides at the granularity of AWS CloudFormation properties
- When you want to centrally inspect resource properties (without overriding them)
Cases where PropertyInjectors are more suitable:
- When you want to make more abstract overrides at the granularity of L2 or L3 Construct props, rather than at the L1 Construct (CloudFormation) property level
- When you want to override properties to prevent errors caused by built-in validation within L2 Constructs (since Aspects are executed after Construct creation is complete, only PropertyInjectors can prevent errors within L2 Constructs)
2-5. Notes on PropertyInjectors Application Timing
As mentioned earlier, PropertyInjectors are executed at the time the target Construct is created.
If you apply PropertyInjectors using PropertyInjectors.of().add after the target Stack has already been created, as shown below, the override process will not be executed.
const app = new App();
new CdkSampleStack(app, 'CdkSampleStack');
// Applying PropertyInjectors here will not work because CdkSampleStack has already been created
PropertyInjectors.of(app).add(new MyBucketPropsInjector());
Instead, call it before the Stack is created, or specify it in the propertyInjectors option of the App or Stack props, as shown below.
const app = new App();
// Apply PropertyInjectors before the stack is created
PropertyInjectors.of(app).add(new MyBucketPropsInjector());
new CdkSampleStack(app, 'CdkSampleStack');
// Or specify it in the stack props
new CdkSampleStack(app, 'CdkSampleStack', {
propertyInjectors: [new MyBucketPropsInjector()],
});
3. Mixins
The third feature I will introduce is Mixins.
Mixins was introduced as a developer preview feature in AWS CDK v2.229.0. In March 2026, with CDK v2.241.0, the core Mixins functionality was moved to the stable package (aws-cdk-lib) and became Generally Available (GA).
Mixins is a mechanism for adding functionality to constructs through composable abstractions. It enables you to selectively apply capabilities that were traditionally bundled into L2 Constructs to L1 Constructs, or conversely, to partially apply L1 properties to L2 Constructs.
For more details, please check out my article AWS CDK Introduces Mixins: A Major Feature for Flexible Construct Composition (Developer Preview) (NOTE: This article was written when Mixins was still in Developer Preview).
3-1. How to Use Mixins
Mixins are applied using the Mixins.of() method or the with() method of each construct.
import { App, Stack, Mixins } from 'aws-cdk-lib';
import { CfnBucket } from 'aws-cdk-lib/aws-s3';
import { BucketVersioning, BucketAutoDeleteObjects } from 'aws-cdk-lib/aws-s3/mixins';
const app = new App();
const stack = new Stack(app, 'CdkSampleStack');
const bucket = new CfnBucket(stack, 'MyBucketApply');
Mixins.of(bucket) // Mixins
.apply(new BucketVersioning()) // Apply BucketVersioning to bucket
.apply(new BucketAutoDeleteObjects()); // Apply BucketAutoDeleteObjects to bucket
new CfnBucket(stack, 'MyBucketWith')
.with(new BucketVersioning()) // Apply BucketVersioning with Construct's with method
.with(new BucketAutoDeleteObjects()); // Apply BucketAutoDeleteObjects with Construct's with method
You can also apply Mixins to all resources under a specific scope, similar to Aspects.
import { App, Stack, Mixins, ConstructSelector } from 'aws-cdk-lib';
import { CfnBucket } from 'aws-cdk-lib/aws-s3';
import { BucketVersioning } from 'aws-cdk-lib/aws-s3/mixins';
import { Construct } from 'constructs';
const app = new App();
const stack = new Stack(app, 'CdkSampleStack');
const construct = new Construct(stack, 'MyConstruct');
// Apply to all resources under construct
Mixins.of(construct).apply(new BucketVersioning());
// Apply only to specific resource types under construct
Mixins.of(construct, ConstructSelector.resourcesOfType(CfnBucket.CFN_RESOURCE_TYPE_NAME)) // Limited to CfnBucket
.apply(new BucketVersioning());
3-2. Creating Custom Mixins
You can easily create custom Mixins by extending the Mixin abstract class.
class CustomVersioningMixin extends Mixin {
supports(construct: any): boolean {
return construct instanceof s3.CfnBucket;
}
applyTo(bucket: any): void {
bucket.versioningConfiguration = {
status: 'Enabled',
};
}
}
const bucket = new s3.CfnBucket(stack, 'MyBucket');
Mixins.of(bucket).apply(new CustomVersioningMixin());
3-3. Differences Between Mixins and Aspects
Mixins are similar to Aspects in that they modify properties of resources in a specific scope, but they differ in timing of application.
As explained in the Aspects vs PropertyInjectors comparison (section 2-3), Aspects are executed later in the CDK application lifecycle (in the Prepare phase), after all Constructs have been created. On the other hand, like PropertyInjectors, Mixins are executed immediately when they are called.
// Aspects
const construct = new Construct(scope, 'MyConstruct');
new Bucket(construct, 'MyBucket1');
// Executed after Constructs below this are created
Aspects.of(construct).add(new CustomVersioningAspect());
// Aspects are also applied to MyBucket2
new Bucket(construct, 'MyBucket2');
// Mixins
const construct = new Construct(scope, 'MyConstruct');
new Bucket(construct, 'MyBucket1');
// Executed at this point
Mixins.of(construct).apply(new CustomVersioningMixin());
// Mixins are not applied to MyBucket2
new Bucket(construct, 'MyBucket2');
According to the Mixins RFC, it is recommended to use Mixins to make changes and to use Aspects to validate behaviors.
Additionally, a mechanism to convert between Aspects and Mixins is also provided. You can use Shims.asMixin to apply an existing Aspect immediately as a Mixin, or use Shims.asAspect to delay the application of a Mixin to a later phase during synthesis like an Aspect.
import { Mixins, Shims, Aspects } from 'aws-cdk-lib';
// Applies the aspect immediately
Mixins.of(scope).apply(Shims.asMixin(new SomeAspect()));
// Delays application of the Mixin to a later phase during synthesis
Aspects.of(scope).add(Shims.asAspect(new SomeMixin()));
3-4. Differences Between Mixins and PropertyInjectors
While PropertyInjectors is also similar to Mixins, it is recommended to use Mixins when you want to apply abstractions, and PropertyInjectors when you want to change default behaviors (i.e., when you want to use it as a blueprint).
There are also the following differences:
- Mixins
- Target: L1 Constructs + L2 Constructs
- Often used with narrow scope (specific Constructs, etc.)
- PropertyInjectors
- Target: L2 Constructs (including some L3 Constructs)
- Often used with broad scope (App level, etc.)
4. RemovalPolicies
The fourth feature I will introduce is RemovalPolicies.
This feature was introduced in AWS CDK v2.183.0.
With RemovalPolicies, you can apply a RemovalPolicy to all resources within any given scope at once.
4-1. How to Use RemovalPolicies
For example, if you want to centrally set the RemovalPolicy for all resources, you can write it as follows:
const app = new App();
// Set the RemovalPolicy to DESTROY for all resources
RemovalPolicies.of(app).destroy();
// Set the RemovalPolicy to RETAIN for all resources
RemovalPolicies.of(app).retain();
4-2. MissingRemovalPolicies
Some resources may already have a RemovalPolicy set internally by L2 Constructs, or you may have explicitly set a RemovalPolicy yourself. If you want to apply a RemovalPolicy collectively to all resources except those that already have one, you can use MissingRemovalPolicies.
const app = new App();
// Set the RemovalPolicy to DESTROY for resources that do not yet have one
MissingRemovalPolicies.of(app).destroy();
Conclusion
In this article, I introduced a collection of tips for centrally and collectively applying configurations to the properties of multiple resources in AWS CDK using the following features:
- Aspects
- PropertyInjectors
- Mixins
- RemovalPolicies
By leveraging these features, you can efficiently manage the properties of multiple resources and improve the readability and maintainability of your code.

Top comments (0)