AWS DevOps & Developer Productivity Blog
Standardizing construct properties with AWS CDK Property Injection
Standardizing CDK construct properties across a large organization requires repetitive manual effort that scales poorly as teams and repositories grow. Development teams working with AWS Cloud Development Kit (AWS CDK) must apply the same configuration properties across similar resources to meet security, compliance, and operational standards but manual configuration leads to drift, maintenance burden, and compliance gaps. In this post, you learn how to use Property Injection, a feature introduced in AWS CDK v2.196.0, to automatically apply default properties to constructs without modifying existing code.
The Challenge of Infrastructure Standardization
Organizations implementing infrastructure as code face a fundamental tension between developer productivity and operational consistency. CDK provides abstractions for defining cloud resources, but ensuring compliance with organizational security policies, compliance requirements, and operational standards requires repetitive manual configuration.
Consider this scenario: an organization’s security policy requires that all SecurityGroups disable outbound traffic by default. Development teams must apply these settings to every SecurityGroup:
new SecurityGroup(stack, 'api-sg', {
vpc: myVpc,
allowAllOutbound: false, // Required by security policy
allowAllIpv6Outbound: false // Required by security policy
});
new SecurityGroup(stack, 'db-sg', {
vpc: myVpc,
allowAllOutbound: false, // Same configuration repeated
allowAllIpv6Outbound: false // Same configuration repeated
});
This manual approach creates four specific problems:
- Configuration drift: Teams omit required properties or apply them inconsistently
- Maintenance burden: Policy updates require coordinated changes across multiple repositories and teams
- Developer friction: Repetitive configuration tasks slow development velocity and increase cognitive load
- Compliance gaps: Manual processes introduce human error, creating security or compliance violations
Custom construct libraries address these challenges but require refactoring every construct instantiation in existing code and create learning curves for development teams already familiar with standard CDK patterns.
Introducing Property Injection
AWS CDK Property Injection addresses these challenges by automatically applying default properties to constructs without requiring changes to existing code.
Property Injection is a feature introduced in AWS CDK v2.196.0 that intercepts construct creation and automatically applies organizational defaults. With this approach, you can enforce standards consistently while preserving existing development workflows and code patterns.
After implementing Property Injection, the same SecurityGroup creation requires only the vpc parameter, security defaults are applied automatically:
// Your existing code remains unchanged
new SecurityGroup(stack, 'my-sg', {
vpc: myVpc
// Security defaults applied automatically by Property Injection
});
The key benefits of this approach include:
- Zero-impact adoption: Existing CDK code continues to work without modification
- Centralized policy management: Standards are defined once and applied automatically
- Consistent enforcement: Policies are applied uniformly across all applications and teams
- Reduced maintenance overhead: Policy updates require changes in only one location
Figure 1: CDK Property Injection Mechanism
Property Injection operates transparently within CDK, intercepting construct creation to apply predefined defaults before merging them with any properties explicitly provided by developers. This ensures that organizational standards are consistently applied while maintaining the flexibility for developers to override defaults when specific use cases require it.
Understanding the Implementation Approach
Property Injection works by implementing the IPropertyInjector interface, which allows you to define default properties for specific construct types. These injectors are registered with CDK stacks and automatically apply their defaults during construct instantiation.
The implementation follows three steps: define the defaults you want to apply, register the injector with your stack, and let CDK handle the automatic application of these defaults to matching constructs.
Implementation Guide
This section shows you how to implement Property Injection for SecurityGroup constructs.
Step 1: Create a Property Injector
Create a class that implements the IPropertyInjector interface:
import { IPropertyInjector, InjectionContext } from 'aws-cdk-lib';
import { SecurityGroup, SecurityGroupProps } from 'aws-cdk-lib/aws-ec2';
export class SecurityGroupDefaults implements IPropertyInjector {
readonly constructUniqueId: string;
constructor() {
this.constructUniqueId = SecurityGroup.PROPERTY_INJECTION_ID;
}
inject(originalProps: SecurityGroupProps, context: InjectionContext): SecurityGroupProps {
return {
// Apply organizational defaults
allowAllIpv6Outbound: false,
allowAllOutbound: false,
// Original properties override defaults when specified
...originalProps,
};
}
}
Step 2: Add the Injector to Your Stack
Apply the injector to your CDK stack:
import { Stack } from 'aws-cdk-lib';
import { SecurityGroupDefaults } from './security-defaults';
const stack = new Stack(app, 'MyStack', {
propertyInjectors: [
new SecurityGroupDefaults()
]
});
Step 3: Use Constructs Normally
Create constructs as usual. The injector applies defaults automatically:
// This SecurityGroup receives the injected defaults:
// - allowAllOutbound: false
// - allowAllIpv6Outbound: false
new SecurityGroup(stack, 'my-sg', {
vpc: myVpc
});
// You can override defaults when necessary
new SecurityGroup(stack, 'special-sg', {
vpc: myVpc,
allowAllOutbound: true // Overrides the injected default
});
Figure 2: CDK Code Before vs After Property Injection
Property Injection vs L2 Constructs
You can achieve the same enforcement of default properties by creating custom L2 constructs with built-in defaults. However, Property Injection is better suited for standardizing existing codebases without refactoring, while L2 Constructs are better suited for new projects where you want custom APIs and multi-resource abstractions.
Figure 3: Decision Tree – Property Injection vs L2 Constructs
Implementation Comparison
Consider an application with multiple SecurityGroup instantiations that need standardized security defaults.
L2 Construct approach requires creating a custom construct and updating each instantiation:
// Step 1: Create custom L2 construct
export class SecureSecurityGroup extends SecurityGroup {
constructor(scope: Construct, id: string, props: SecurityGroupProps) {
super(scope, id, {
allowAllOutbound: false,
allowAllIpv6Outbound: false,
...props
});
}
}
// Step 2: Update each instantiation throughout your codebase
// Change from:
new SecurityGroup(stack, 'sg1', { vpc: myVpc })
new SecurityGroup(stack, 'sg2', { vpc: myVpc })
new SecurityGroup(stack, 'sg3', { vpc: myVpc })
// To:
new SecureSecurityGroup(stack, 'sg1', { vpc: myVpc })
new SecureSecurityGroup(stack, 'sg2', { vpc: myVpc })
new SecureSecurityGroup(stack, 'sg3', { vpc: myVpc })
Property Injection approach requires one-time stack configuration:
// Step 1: Add injector to stack configuration
stack.propertyInjectors = [new SecurityGroupDefaults()];
// Step 2: Existing SecurityGroup calls receive defaults automatically
new SecurityGroup(stack, 'sg1', { vpc: myVpc }) // Gets defaults
new SecurityGroup(stack, 'sg2', { vpc: myVpc }) // Gets defaults
new SecurityGroup(stack, 'sg3', { vpc: myVpc }) // Gets defaults
Key Differences
Property Injection works with existing construct calls, requiring no changes to how developers instantiate SecurityGroups or other constructs. This approach overrides constructs from external libraries and can be implemented without modifying existing code. Developers continue using familiar CDK APIs without learning new interfaces.
L2 Constructs require updating all constructor calls throughout your codebase. This approach cannot modify third-party construct creation since you must change each instantiation to use your custom construct. Implementation requires refactoring existing code and developers must learn your custom construct APIs instead of standard CDK interfaces. L2 constructs serve multiple purposes beyond complex business logic – simple L2 constructs provide domain-specific naming conventions and cleaner APIs, while complex L2 constructs orchestrate three or more resources and implement business rules.
When to Choose Each Approach
Choose Property Injection when you need to standardize existing infrastructure. Property Injection excels in scenarios where you already have CDK applications deployed and need to apply consistent defaults retroactively. Property Injection works transparently with existing code, requiring no changes to how developers instantiate constructs. This makes it useful when you have existing CDK applications that you want to standardize without disrupting current development workflows.
Property Injection also solves the challenge of applying defaults to constructs from third-party libraries. Since you cannot modify external library code, Property Injection enforces organizational standards on any construct type, regardless of its source. Additionally, when you want to implement standards without changing existing code, Property Injection operates at the framework level, automatically applying defaults during construct instantiation without requiring developers to modify their existing implementations.
Choose L2 Constructs when you need custom APIs or multi-resource patterns. L2 Constructs provide the right abstraction when you want to create purpose-built interfaces that differ from standard CDK APIs. This includes simple wrappers with domain-specific naming, complex business logic, validation rules, or multi-resource orchestration patterns. L2 Constructs excel when you want to create opinionated APIs that simplify common patterns by hiding complexity behind intuitive interfaces.
L2 Constructs suit new application development where you can design the API from the start. This approach creates purpose-built abstractions that match your organization’s specific use cases and terminology. Unlike Property Injection, which applies defaults to existing construct APIs, with L2 Constructs you can design entirely new APIs that directly represent your business domain and operational patterns.
Implementation Patterns
Stack Integration Methods
The CDK provides two methods for adding Property Injectors to stacks:
Figure 4: CDK Stack Integration Methods
Method 1: Stack Constructor
const stack = new Stack(app, 'MyStack', {
propertyInjectors: [new SecurityGroupDefaults()]
});
Method 2: PropertyInjectors.of()
const stack = new Stack(app, 'MyStack');
PropertyInjectors.of(stack).add(new SecurityGroupDefaults());
Both methods produce the same result. Choose the method that best fits your existing code structure. For more details, see the PropertyInjectors API documentation.
Organization-Wide Implementation
For organization-wide standardization, create a shared library of injectors:
// @myorg/cdk-injectors package
export const ORGANIZATION_INJECTORS: IPropertyInjector[] = [
new SecurityGroupDefaults(),
new LambdaFunctionDefaults(),
new S3BucketDefaults(),
];
// Teams import and use the shared injectors
import { ORGANIZATION_INJECTORS } from '@myorg/cdk-injectors';
const stack = new Stack(app, 'TeamStack', {
propertyInjectors: ORGANIZATION_INJECTORS
});
Scope Hierarchy
Property Injectors can be applied at different levels in the CDK construct tree:

- Figure 5: CDK Scope Hierarchy & Injector Resolution
- App level: Applies to all stacks in the application
- Stage level: Applies to all stacks within a specific stage
- Stack level: Applies only to constructs within a specific stack
CDK searches for applicable injectors starting from the construct’s immediate parent scope and moving upward. The first matching injector for each construct type is used.
Best Practices
When implementing Property Injection, begin with high-impact constructs like SecurityGroups, VPCs, and Lambda functions that require repetitive configuration.
These constructs have the highest frequency of misconfiguration and the most direct compliance impact, making them the most valuable targets for early adoption.
Document your defaults by explaining what properties your injectors provide and why. Include examples and link to relevant policies that drive the requirements. With this documentation, developers can understand standards and make informed override decisions.
Write automated tests using CDK testing utilities to verify that injectors apply expected defaults. Test both standard scenarios and cases where developers override properties to prevent regressions when updating injector logic.
Version injectors carefully using semantic versioning principles because changes affect all applications. Coordinate updates across teams and provide migration guides for breaking changes or changes to default values.
Design override mechanisms so that developers can handle edge cases while benefiting from organizational standards. Property Injection operates as defaults, not restrictions, so design injectors to merge gracefully with developer-specified properties.
Limitations and Considerations
Property Injection operates as a default mechanism rather than a compliance enforcement system. Developers retain the ability to override injected properties, which means organizations cannot rely solely on Property Injection for strict compliance requirements. For teams that need mandatory compliance, combine Property Injection with CDK Aspects or AWS Config rules to validate and enforce standards.
The feature works exclusively with L2 constructs, as documented in the official AWS CDK guidance. The IPropertyInjector interface targets specific L2 construct types, and L1 (CloudFormation) constructs use different instantiation patterns that bypass the property injection mechanism entirely. Organizations with L1 construct usage need alternative standardization approaches.
Property Injection introduces debugging complexity because injected properties do not appear directly in application code. Developers troubleshooting construct behavior must understand which injectors apply to specific construct types and how those injectors modify properties. This hidden behavior requires documentation that lists each injector, the properties it sets, and the policy it enforces, along with clear naming conventions to maintain code clarity.
The feature requires CDK v2.196.0 or later, which affects adoption timelines for organizations using older CDK versions. Teams must plan upgrade paths and test compatibility before implementing Property Injection across their applications.
Conclusion
Property Injection provides a mechanism for applying consistent default properties to CDK constructs without requiring changes to existing code. This approach reduces repetitive configuration, improves consistency, and simplifies maintenance of CDK applications.
Property Injection is the right choice for organizations that need to standardize construct configurations across existing codebases while preserving developer workflows. When combined with proper testing and documentation, Property Injection becomes a reliable foundation for infrastructure governance across your organization.