AWS
Lambda
S3
CloudFormation
DevOps

Enable Lambda function to an S3 bucket using cloudformation

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

To trigger a Lambda function from an S3 bucket in CloudFormation, you need more than just the bucket and the function. The working setup requires three pieces: the Lambda function, permission that allows S3 to invoke it, and the bucket notification configuration that points S3 events at the function.

The Required Resource Relationships

An S3 to Lambda integration has two directions of configuration:

  1. S3 must know which Lambda function to call.
  2. Lambda must allow the S3 bucket to invoke it.

If either side is missing, the stack may create successfully but uploads will not trigger the function.

The usual CloudFormation resources are:

  • 'AWS::Lambda::Function'
  • 'AWS::IAM::Role'
  • 'AWS::Lambda::Permission'
  • 'AWS::S3::Bucket'

Minimal Working Template

The template below creates a bucket, a Python Lambda function, permission for the bucket to invoke it, and an object-created notification.

yaml
1AWSTemplateFormatVersion: '2010-09-09'
2Resources:
3  ProcessingRole:
4    Type: AWS::IAM::Role
5    Properties:
6      AssumeRolePolicyDocument:
7        Version: '2012-10-17'
8        Statement:
9          - Effect: Allow
10            Principal:
11              Service: lambda.amazonaws.com
12            Action: sts:AssumeRole
13      ManagedPolicyArns:
14        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
15
16  ProcessingFunction:
17    Type: AWS::Lambda::Function
18    Properties:
19      Runtime: python3.12
20      Handler: index.handler
21      Role: !GetAtt ProcessingRole.Arn
22      Timeout: 30
23      Code:
24        ZipFile: |
25          import json
26
27          def handler(event, context):
28              print(json.dumps(event))
29              return {"statusCode": 200}
30
31  BucketInvokePermission:
32    Type: AWS::Lambda::Permission
33    Properties:
34      Action: lambda:InvokeFunction
35      FunctionName: !Ref ProcessingFunction
36      Principal: s3.amazonaws.com
37      SourceArn: !GetAtt UploadBucket.Arn
38
39  UploadBucket:
40    Type: AWS::S3::Bucket
41    DependsOn: BucketInvokePermission
42    Properties:
43      NotificationConfiguration:
44        LambdaConfigurations:
45          - Event: s3:ObjectCreated:*
46            Function: !GetAtt ProcessingFunction.Arn

The DependsOn matters because S3 validates the invocation target while applying the notification configuration. If the permission is not in place yet, stack creation can fail.

Why AWS::Lambda::Permission Is Required

Many first attempts define the bucket notification and assume that is enough. It is not. S3 needs explicit permission to invoke the function.

This is the relevant part:

yaml
1BucketInvokePermission:
2  Type: AWS::Lambda::Permission
3  Properties:
4    Action: lambda:InvokeFunction
5    FunctionName: !Ref ProcessingFunction
6    Principal: s3.amazonaws.com
7    SourceArn: !GetAtt UploadBucket.Arn

Without that permission, S3 events do not have the right to call the function even if the notification exists.

Filtering by Prefix or Suffix

You can restrict the notification to certain keys. For example, trigger only on images uploaded under an incoming/ prefix:

yaml
1NotificationConfiguration:
2  LambdaConfigurations:
3    - Event: s3:ObjectCreated:*
4      Function: !GetAtt ProcessingFunction.Arn
5      Filter:
6        S3Key:
7          Rules:
8            - Name: prefix
9              Value: incoming/
10            - Name: suffix
11              Value: .jpg

That reduces unnecessary invocations and keeps event routing predictable.

What the Lambda Receives

When S3 triggers the function, the event contains bucket and object metadata. A simple handler can extract the bucket name and key:

python
1def handler(event, context):
2    record = event["Records"][0]
3    bucket = record["s3"]["bucket"]["name"]
4    key = record["s3"]["object"]["key"]
5    print(f"Processing {key} from {bucket}")
6    return {"ok": True}

That is enough to begin validation, image processing, metadata extraction, or downstream orchestration.

Common Pitfalls

The biggest CloudFormation mistake is circular or invalid ordering. The bucket references the function, while the permission references the bucket. Using DependsOn on the bucket is the practical fix for that creation sequence.

Another common issue is giving the Lambda execution role permission to read from the bucket and assuming that also grants S3 permission to invoke the function. Those are different permission paths.

It is also easy to forget that some bucket updates replace the resource if the bucket name is fixed externally. Be careful when applying changes to production buckets that already contain data.

Summary

  • A working S3 to Lambda setup needs the function, bucket notification, and invocation permission.
  • 'AWS::Lambda::Permission is required so S3 can call the function.'
  • Put the bucket notification in NotificationConfiguration.
  • Use DependsOn so permission exists before S3 validates the target.
  • Add prefix or suffix filters when only certain objects should trigger the function.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.