Implementing Custom AWS Configurations with CDK Custom Resource
In this article, we’ll show how to use CDK’s Custom Resource feature to add configurations for AWS resources or capabilities that aren’t natively supported by CDK.
For example, CDK doesn’t provide built-in support for adding event notifications to an existing S3 bucket. The example below demonstrates how to dynamically attach an S3 Notification to an existing bucket via a Custom Resource so that when files with specified suffixes are uploaded, a Lambda function is invoked automatically.
1. Writing the Lambda to Configure S3 Notifications
First, we need a Lambda function whose sole responsibility is to set up the Notification on the S3 bucket. It reads a list of suffixes (suffixList), builds the appropriate PutBucketNotificationConfigurationCommand, and sends it to S3.
// lambda/s3-notification-setting/index.ts
import { LambdaFunctionConfiguration, PutBucketNotificationConfigurationCommand, S3Client } from '@aws-sdk/client-s3';
import { CdkCustomResourceEvent, Context } from 'aws-lambda';
const createUpdateCommand = (suffixList: string[]) => {
const configurations: LambdaFunctionConfiguration[] = suffixList.map((suffix) => ({
LambdaFunctionArn: process.env.INVOKE_FUNC_ARN!,
Events: ['s3:ObjectCreated:*'],
Filter: {
Key: {
FilterRules: [{ Name: 'suffix', Value: suffix }],
},
},
}));
return new PutBucketNotificationConfigurationCommand({
Bucket: process.env.TARGET_BUCKET_NAME!,
NotificationConfiguration: {
LambdaFunctionConfigurations: configurations,
},
});
};
export const handler = async (event: CdkCustomResourceEvent, context: Context) => {
const suffixList = event.ResourceProperties.suffixList as string[];
const bucketName = process.env.TARGET_BUCKET_NAME!;
const s3Client = new S3Client({});
// Prepare an empty configuration for Delete operations
let command = new PutBucketNotificationConfigurationCommand({
Bucket: bucketName,
NotificationConfiguration: {},
});
// On Create or Update, build and send the real notification config
if (event.RequestType === 'Create' || event.RequestType === 'Update') {
command = createUpdateCommand(suffixList);
}
await s3Client.send(command);
return { PhysicalResourceId: `${bucketName}-notification` };
};
Key points:
- The
INVOKE_FUNC_ARNenvironment variable holds the ARN of the Lambda to be invoked by S3 events. - The
TARGET_BUCKET_NAMEenvironment variable specifies the target S3 bucket. - We use the same logic for both
CreateandUpdaterequests; forDelete, we send an empty configuration to clear any existing notifications.
2. Integrating the Custom Resource into Your CDK Stack
Next, in your CDK stack you define two Lambdas: one to handle actual S3 upload events (s3-notification-invoke), and another to configure the notifications (s3-notification-setting). You then register the configuration Lambda as the handler for a Custom Resource via custom-resources.Provider.
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Provider } from 'aws-cdk-lib/custom-resources';
import { Construct } from 'constructs';
export class S3NotificationSettingStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// 1. Lambda to handle S3 upload events
const s3NotificationInvokeFunc = new lambda.Function(this, 's3-notification-invoke', {
runtime: lambda.Runtime.NODEJS_22_X,
code: lambda.Code.fromAsset('lambda/s3-notification-invoke'),
handler: 'index.handler',
functionName: 's3-notification-invoke',
});
// 2. Lambda for configuring S3 notifications via Custom Resource
const s3NotificationSettingFunc = new lambda.Function(this, 's3-notification-setting', {
runtime: lambda.Runtime.NODEJS_22_X,
code: lambda.Code.fromAsset('lambda/s3-notification-setting'),
handler: 'index.handler',
functionName: 's3-notification-setting',
environment: {
INVOKE_FUNC_ARN: s3NotificationInvokeFunc.functionArn,
TARGET_BUCKET_NAME: 'your-existing-bucket-name', // replace with your bucket name
},
});
// Grant S3 permission to invoke the notification handler Lambda
s3NotificationInvokeFunc.addPermission('AllowS3Invoke', {
action: 'lambda:InvokeFunction',
principal: new iam.ServicePrincipal('s3.amazonaws.com'),
sourceArn: `arn:aws:s3:::your-existing-bucket-name`, // replace with your bucket ARN
});
// 3. Define a Custom Resource Provider backed by the configuration Lambda
const provider = new Provider(this, 'S3NotificationProvider', {
onEventHandler: s3NotificationSettingFunc,
});
// 4. Create the Custom Resource, passing in the suffix list
new cdk.CustomResource(this, 'S3NotificationCustomResource', {
serviceToken: provider.serviceToken,
properties: {
suffixList: ['.jpg', '.pdf'], // add or adjust suffixes as needed
},
});
}
}
Steps overview:
- Deploy the notification handler Lambda: (
s3-notification-invoke) to process events from S3. - Deploy the configuration Lambda: (
s3-notification-setting) to callPutBucketNotificationConfigurationon the existing bucket. - Grant permissions: so that S3 can invoke the notification handler Lambda.
- Register the Custom Resource: CDK will invoke your configuration Lambda during
deployto create, update, or delete the S3 notification setup.
3. Conclusion
With this pattern, you can leverage CDK’s Custom Resource mechanism to programmatically configure AWS features that aren’t directly supported by CDK out of the box—such as adding notifications to existing resources. Feel free to extend this approach to any scenario where you need to call native SDK APIs or customize AWS resource behavior beyond CDK’s native capabilities.