CDK Property Injectors in Python
Table of Contents
What are Property Injectors?
CDK Property Injectors are a mechanism for organizations to define default property values that are automatically applied to constructs at synthesis time. They were introduced in CDK v2.196.0 (May 2025) as part of the Blueprint Property Injection RFC and allow platform teams to enforce organizational standards without requiring individual developers to remember (or even know about) every configuration requirement.
The idea is simple: you write a class that implements IPropertyInjector, attach it to a Stack (or App, or Stage), and every construct within that scope that matches the injector’s target will have its props modified before construction. This lets you do things like:
- Enforce encryption settings on all S3 buckets
- Set default retention policies on log groups
- Disable public access on API keys
- Apply any organizational default you can express as a construct property
Injectors are composable. You can attach multiple injectors to a scope, and developers can still override injected values by explicitly passing props. This makes them a much better fit for organizational governance than Aspects, which modify resources after construction and can’t influence the constructor logic of L2 constructs.
The Documentation Problem
If you want to write a property injector in Python, you will find exactly one example on the entire internet: the AWS CDK Python API Reference.
Here is the example from that page:
@jsii.implements(IPropertyInjector)
class ApiKeyPropsInjector:
def __init__(self):
self.construct_unique_id = api.ApiKey.PROPERTY_INJECTION_ID
def inject(self, original_props, *, scope, id):
return api.ApiKeyProps(
enabled=False,
api_key_name=original_props.api_key_name,
customer_id=original_props.customer_id,
# ... remaining props ...
)
This example does not work.
The problem is that CDK Python props classes (like ApiKeyProps) are jsii structs. They are not regular Python classes you can instantiate directly in this way. When you try to return a newly constructed props object from the inject method, you will get errors because the jsii runtime does not handle the round-trip correctly. The props objects returned from inject must be plain dictionaries, not jsii struct instances.
Additionally, construct_unique_id must be defined as a Python @property, not assigned as a plain instance variable in __init__.
The Correct Approach
To write a working property injector in Python, you need to do the following:
- Use the
@jsii.implements(IPropertyInjector)decorator on your class. - Set
construct_unique_idas a property (not just an instance variable) that returns the target construct’sPROPERTY_INJECTION_ID. - In the
injectmethod, return a plain dictionary containing the merged props, not a props class instance. The keys should be the jsii names (camelCase) of the properties, not the Python snake_case names. - Copy all properties from
original_propsinto your return dictionary, overriding only the values you want to change.
Note that while the top-level return value must be a plain dictionary, the values within that dictionary can be jsii struct instances (like BundlingOptions). The restriction only applies to the object returned from inject itself.
Working Example
Here is an example that sets default memory size and bundling options for all Python Lambda functions:
from typing import Any, Dict
import jsii
from aws_cdk import IPropertyInjector
from aws_cdk.aws_lambda_python_alpha import BundlingOptions, PythonFunction
from constructs import Construct
_DEFAULT_ASSET_EXCLUDES = [
".venv",
"tests",
".gitignore",
]
@jsii.implements(IPropertyInjector)
class PythonLambdaFunctionDefaults:
@property
def construct_unique_id(self) -> str:
return PythonFunction.PROPERTY_INJECTION_ID
def inject(self, original_props: Dict[str, Any], *, scope: Construct, id: str) -> Dict[str, Any]:
return {
**original_props,
"memorySize": original_props["memorySize"] if "memorySize" in original_props else 256,
"bundling": original_props["bundling"] if "bundling" in original_props else BundlingOptions(asset_excludes=_DEFAULT_ASSET_EXCLUDES),
}
Usage
Once you have a working injector, you attach it to a scope just as the documentation suggests:
stack = Stack(app, "my-stack", property_injectors=[PythonLambdaFunctionDefaults()])
All constructs of the PythonFunction type, created within that scope, will have their properties modified by your injector. Note that in our example, if the bundling option was set in the original_props, we retain that over our default value. To force a value regardless of what the developer passes, simply omit the code that checks if a value was passed:
"memorySize": 256,
"bundling": BundlingOptions(asset_excludes=_DEFAULT_ASSET_EXCLUDES),
Property injectors are a powerful tool for platform teams, and it’s unfortunate that the only Python example in existence offers no real guidance. Hopefully this article saves someone else the trouble of figuring out how to make them work.