log
Swift Code Chronicles

Implementing Custom AWS Configurations with CDK Custom Resource

Published on April 25, 2025
Updated on April 25, 2025
30 min read
AWS

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_ARN environment variable holds the ARN of the Lambda to be invoked by S3 events.
  • The TARGET_BUCKET_NAME environment variable specifies the target S3 bucket.
  • We use the same logic for both Create and Update requests; for Delete, 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 call PutBucketNotificationConfiguration on 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 deploy to 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.

About

A personal blog sharing technical insights, experiences and thoughts

Quick Links

Contact

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 Swift Code Chronicles. All rights reserved