The aws:s3/bucketNotification:BucketNotification resource, part of the Pulumi AWS provider, configures S3 event notifications that trigger when objects are created, deleted, or modified in a bucket. This guide focuses on three capabilities: Lambda function invocation, SQS queue delivery, and EventBridge routing.
S3 buckets support only one notification configuration resource. Declaring multiple BucketNotification resources for the same bucket causes configuration conflicts. Notification configurations reference Lambda functions, SQS queues, or SNS topics that must exist separately, along with appropriate IAM permissions. The examples are intentionally small. Combine them with your own functions, queues, and access policies.
Invoke Lambda functions when objects are created
Many serverless workflows begin when files land in S3. Lambda functions process uploads immediately, transforming data or triggering downstream pipelines.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const assumeRole = aws.iam.getPolicyDocument({
statements: [{
effect: "Allow",
principals: [{
type: "Service",
identifiers: ["lambda.amazonaws.com"],
}],
actions: ["sts:AssumeRole"],
}],
});
const iamForLambda = new aws.iam.Role("iam_for_lambda", {
name: "iam_for_lambda",
assumeRolePolicy: assumeRole.then(assumeRole => assumeRole.json),
});
const func = new aws.lambda.Function("func", {
code: new pulumi.asset.FileArchive("your-function.zip"),
name: "example_lambda_name",
role: iamForLambda.arn,
handler: "exports.example",
runtime: aws.lambda.Runtime.NodeJS20dX,
});
const bucket = new aws.s3.Bucket("bucket", {bucket: "your-bucket-name"});
const allowBucket = new aws.lambda.Permission("allow_bucket", {
statementId: "AllowExecutionFromS3Bucket",
action: "lambda:InvokeFunction",
"function": func.arn,
principal: "s3.amazonaws.com",
sourceArn: bucket.arn,
});
const bucketNotification = new aws.s3.BucketNotification("bucket_notification", {
bucket: bucket.id,
lambdaFunctions: [{
lambdaFunctionArn: func.arn,
events: ["s3:ObjectCreated:*"],
filterPrefix: "AWSLogs/",
filterSuffix: ".log",
}],
}, {
dependsOn: [allowBucket],
});
import pulumi
import pulumi_aws as aws
assume_role = aws.iam.get_policy_document(statements=[{
"effect": "Allow",
"principals": [{
"type": "Service",
"identifiers": ["lambda.amazonaws.com"],
}],
"actions": ["sts:AssumeRole"],
}])
iam_for_lambda = aws.iam.Role("iam_for_lambda",
name="iam_for_lambda",
assume_role_policy=assume_role.json)
func = aws.lambda_.Function("func",
code=pulumi.FileArchive("your-function.zip"),
name="example_lambda_name",
role=iam_for_lambda.arn,
handler="exports.example",
runtime=aws.lambda_.Runtime.NODE_JS20D_X)
bucket = aws.s3.Bucket("bucket", bucket="your-bucket-name")
allow_bucket = aws.lambda_.Permission("allow_bucket",
statement_id="AllowExecutionFromS3Bucket",
action="lambda:InvokeFunction",
function=func.arn,
principal="s3.amazonaws.com",
source_arn=bucket.arn)
bucket_notification = aws.s3.BucketNotification("bucket_notification",
bucket=bucket.id,
lambda_functions=[{
"lambda_function_arn": func.arn,
"events": ["s3:ObjectCreated:*"],
"filter_prefix": "AWSLogs/",
"filter_suffix": ".log",
}],
opts = pulumi.ResourceOptions(depends_on=[allow_bucket]))
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lambda"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
assumeRole, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
Statements: []iam.GetPolicyDocumentStatement{
{
Effect: pulumi.StringRef("Allow"),
Principals: []iam.GetPolicyDocumentStatementPrincipal{
{
Type: "Service",
Identifiers: []string{
"lambda.amazonaws.com",
},
},
},
Actions: []string{
"sts:AssumeRole",
},
},
},
}, nil)
if err != nil {
return err
}
iamForLambda, err := iam.NewRole(ctx, "iam_for_lambda", &iam.RoleArgs{
Name: pulumi.String("iam_for_lambda"),
AssumeRolePolicy: pulumi.String(assumeRole.Json),
})
if err != nil {
return err
}
_func, err := lambda.NewFunction(ctx, "func", &lambda.FunctionArgs{
Code: pulumi.NewFileArchive("your-function.zip"),
Name: pulumi.String("example_lambda_name"),
Role: iamForLambda.Arn,
Handler: pulumi.String("exports.example"),
Runtime: pulumi.String(lambda.RuntimeNodeJS20dX),
})
if err != nil {
return err
}
bucket, err := s3.NewBucket(ctx, "bucket", &s3.BucketArgs{
Bucket: pulumi.String("your-bucket-name"),
})
if err != nil {
return err
}
allowBucket, err := lambda.NewPermission(ctx, "allow_bucket", &lambda.PermissionArgs{
StatementId: pulumi.String("AllowExecutionFromS3Bucket"),
Action: pulumi.String("lambda:InvokeFunction"),
Function: _func.Arn,
Principal: pulumi.String("s3.amazonaws.com"),
SourceArn: bucket.Arn,
})
if err != nil {
return err
}
_, err = s3.NewBucketNotification(ctx, "bucket_notification", &s3.BucketNotificationArgs{
Bucket: bucket.ID(),
LambdaFunctions: s3.BucketNotificationLambdaFunctionArray{
&s3.BucketNotificationLambdaFunctionArgs{
LambdaFunctionArn: _func.Arn,
Events: pulumi.StringArray{
pulumi.String("s3:ObjectCreated:*"),
},
FilterPrefix: pulumi.String("AWSLogs/"),
FilterSuffix: pulumi.String(".log"),
},
},
}, pulumi.DependsOn([]pulumi.Resource{
allowBucket,
}))
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var assumeRole = Aws.Iam.GetPolicyDocument.Invoke(new()
{
Statements = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
{
Effect = "Allow",
Principals = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
{
Type = "Service",
Identifiers = new[]
{
"lambda.amazonaws.com",
},
},
},
Actions = new[]
{
"sts:AssumeRole",
},
},
},
});
var iamForLambda = new Aws.Iam.Role("iam_for_lambda", new()
{
Name = "iam_for_lambda",
AssumeRolePolicy = assumeRole.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
});
var func = new Aws.Lambda.Function("func", new()
{
Code = new FileArchive("your-function.zip"),
Name = "example_lambda_name",
Role = iamForLambda.Arn,
Handler = "exports.example",
Runtime = Aws.Lambda.Runtime.NodeJS20dX,
});
var bucket = new Aws.S3.Bucket("bucket", new()
{
BucketName = "your-bucket-name",
});
var allowBucket = new Aws.Lambda.Permission("allow_bucket", new()
{
StatementId = "AllowExecutionFromS3Bucket",
Action = "lambda:InvokeFunction",
Function = func.Arn,
Principal = "s3.amazonaws.com",
SourceArn = bucket.Arn,
});
var bucketNotification = new Aws.S3.BucketNotification("bucket_notification", new()
{
Bucket = bucket.Id,
LambdaFunctions = new[]
{
new Aws.S3.Inputs.BucketNotificationLambdaFunctionArgs
{
LambdaFunctionArn = func.Arn,
Events = new[]
{
"s3:ObjectCreated:*",
},
FilterPrefix = "AWSLogs/",
FilterSuffix = ".log",
},
},
}, new CustomResourceOptions
{
DependsOn =
{
allowBucket,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.iam.Role;
import com.pulumi.aws.iam.RoleArgs;
import com.pulumi.aws.lambda.Function;
import com.pulumi.aws.lambda.FunctionArgs;
import com.pulumi.aws.s3.Bucket;
import com.pulumi.aws.s3.BucketArgs;
import com.pulumi.aws.lambda.Permission;
import com.pulumi.aws.lambda.PermissionArgs;
import com.pulumi.aws.s3.BucketNotification;
import com.pulumi.aws.s3.BucketNotificationArgs;
import com.pulumi.aws.s3.inputs.BucketNotificationLambdaFunctionArgs;
import com.pulumi.asset.FileArchive;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
final var assumeRole = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
.statements(GetPolicyDocumentStatementArgs.builder()
.effect("Allow")
.principals(GetPolicyDocumentStatementPrincipalArgs.builder()
.type("Service")
.identifiers("lambda.amazonaws.com")
.build())
.actions("sts:AssumeRole")
.build())
.build());
var iamForLambda = new Role("iamForLambda", RoleArgs.builder()
.name("iam_for_lambda")
.assumeRolePolicy(assumeRole.json())
.build());
var func = new Function("func", FunctionArgs.builder()
.code(new FileArchive("your-function.zip"))
.name("example_lambda_name")
.role(iamForLambda.arn())
.handler("exports.example")
.runtime("nodejs20.x")
.build());
var bucket = new Bucket("bucket", BucketArgs.builder()
.bucket("your-bucket-name")
.build());
var allowBucket = new Permission("allowBucket", PermissionArgs.builder()
.statementId("AllowExecutionFromS3Bucket")
.action("lambda:InvokeFunction")
.function(func.arn())
.principal("s3.amazonaws.com")
.sourceArn(bucket.arn())
.build());
var bucketNotification = new BucketNotification("bucketNotification", BucketNotificationArgs.builder()
.bucket(bucket.id())
.lambdaFunctions(BucketNotificationLambdaFunctionArgs.builder()
.lambdaFunctionArn(func.arn())
.events("s3:ObjectCreated:*")
.filterPrefix("AWSLogs/")
.filterSuffix(".log")
.build())
.build(), CustomResourceOptions.builder()
.dependsOn(allowBucket)
.build());
}
}
resources:
iamForLambda:
type: aws:iam:Role
name: iam_for_lambda
properties:
name: iam_for_lambda
assumeRolePolicy: ${assumeRole.json}
allowBucket:
type: aws:lambda:Permission
name: allow_bucket
properties:
statementId: AllowExecutionFromS3Bucket
action: lambda:InvokeFunction
function: ${func.arn}
principal: s3.amazonaws.com
sourceArn: ${bucket.arn}
func:
type: aws:lambda:Function
properties:
code:
fn::FileArchive: your-function.zip
name: example_lambda_name
role: ${iamForLambda.arn}
handler: exports.example
runtime: nodejs20.x
bucket:
type: aws:s3:Bucket
properties:
bucket: your-bucket-name
bucketNotification:
type: aws:s3:BucketNotification
name: bucket_notification
properties:
bucket: ${bucket.id}
lambdaFunctions:
- lambdaFunctionArn: ${func.arn}
events:
- s3:ObjectCreated:*
filterPrefix: AWSLogs/
filterSuffix: .log
options:
dependsOn:
- ${allowBucket}
variables:
assumeRole:
fn::invoke:
function: aws:iam:getPolicyDocument
arguments:
statements:
- effect: Allow
principals:
- type: Service
identifiers:
- lambda.amazonaws.com
actions:
- sts:AssumeRole
When an object matching the filter lands in the bucket, S3 invokes the Lambda function. The lambdaFunctions property defines the function ARN, event types to watch, and optional prefix/suffix filters. The lambda.Permission resource grants S3 permission to invoke your function; without it, invocations fail with access denied errors.
Route different prefixes to separate Lambda functions
Applications often need different processing logic for different file types or paths. A single notification configuration routes uploads to multiple Lambda functions based on prefix patterns.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const assumeRole = aws.iam.getPolicyDocument({
statements: [{
effect: "Allow",
principals: [{
type: "Service",
identifiers: ["lambda.amazonaws.com"],
}],
actions: ["sts:AssumeRole"],
}],
});
const iamForLambda = new aws.iam.Role("iam_for_lambda", {
name: "iam_for_lambda",
assumeRolePolicy: assumeRole.then(assumeRole => assumeRole.json),
});
const func1 = new aws.lambda.Function("func1", {
code: new pulumi.asset.FileArchive("your-function1.zip"),
name: "example_lambda_name1",
role: iamForLambda.arn,
handler: "exports.example",
runtime: aws.lambda.Runtime.NodeJS20dX,
});
const bucket = new aws.s3.Bucket("bucket", {bucket: "your-bucket-name"});
const allowBucket1 = new aws.lambda.Permission("allow_bucket1", {
statementId: "AllowExecutionFromS3Bucket1",
action: "lambda:InvokeFunction",
"function": func1.arn,
principal: "s3.amazonaws.com",
sourceArn: bucket.arn,
});
const func2 = new aws.lambda.Function("func2", {
code: new pulumi.asset.FileArchive("your-function2.zip"),
name: "example_lambda_name2",
role: iamForLambda.arn,
handler: "exports.example",
});
const allowBucket2 = new aws.lambda.Permission("allow_bucket2", {
statementId: "AllowExecutionFromS3Bucket2",
action: "lambda:InvokeFunction",
"function": func2.arn,
principal: "s3.amazonaws.com",
sourceArn: bucket.arn,
});
const bucketNotification = new aws.s3.BucketNotification("bucket_notification", {
bucket: bucket.id,
lambdaFunctions: [
{
lambdaFunctionArn: func1.arn,
events: ["s3:ObjectCreated:*"],
filterPrefix: "AWSLogs/",
filterSuffix: ".log",
},
{
lambdaFunctionArn: func2.arn,
events: ["s3:ObjectCreated:*"],
filterPrefix: "OtherLogs/",
filterSuffix: ".log",
},
],
}, {
dependsOn: [
allowBucket1,
allowBucket2,
],
});
import pulumi
import pulumi_aws as aws
assume_role = aws.iam.get_policy_document(statements=[{
"effect": "Allow",
"principals": [{
"type": "Service",
"identifiers": ["lambda.amazonaws.com"],
}],
"actions": ["sts:AssumeRole"],
}])
iam_for_lambda = aws.iam.Role("iam_for_lambda",
name="iam_for_lambda",
assume_role_policy=assume_role.json)
func1 = aws.lambda_.Function("func1",
code=pulumi.FileArchive("your-function1.zip"),
name="example_lambda_name1",
role=iam_for_lambda.arn,
handler="exports.example",
runtime=aws.lambda_.Runtime.NODE_JS20D_X)
bucket = aws.s3.Bucket("bucket", bucket="your-bucket-name")
allow_bucket1 = aws.lambda_.Permission("allow_bucket1",
statement_id="AllowExecutionFromS3Bucket1",
action="lambda:InvokeFunction",
function=func1.arn,
principal="s3.amazonaws.com",
source_arn=bucket.arn)
func2 = aws.lambda_.Function("func2",
code=pulumi.FileArchive("your-function2.zip"),
name="example_lambda_name2",
role=iam_for_lambda.arn,
handler="exports.example")
allow_bucket2 = aws.lambda_.Permission("allow_bucket2",
statement_id="AllowExecutionFromS3Bucket2",
action="lambda:InvokeFunction",
function=func2.arn,
principal="s3.amazonaws.com",
source_arn=bucket.arn)
bucket_notification = aws.s3.BucketNotification("bucket_notification",
bucket=bucket.id,
lambda_functions=[
{
"lambda_function_arn": func1.arn,
"events": ["s3:ObjectCreated:*"],
"filter_prefix": "AWSLogs/",
"filter_suffix": ".log",
},
{
"lambda_function_arn": func2.arn,
"events": ["s3:ObjectCreated:*"],
"filter_prefix": "OtherLogs/",
"filter_suffix": ".log",
},
],
opts = pulumi.ResourceOptions(depends_on=[
allow_bucket1,
allow_bucket2,
]))
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lambda"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
assumeRole, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
Statements: []iam.GetPolicyDocumentStatement{
{
Effect: pulumi.StringRef("Allow"),
Principals: []iam.GetPolicyDocumentStatementPrincipal{
{
Type: "Service",
Identifiers: []string{
"lambda.amazonaws.com",
},
},
},
Actions: []string{
"sts:AssumeRole",
},
},
},
}, nil)
if err != nil {
return err
}
iamForLambda, err := iam.NewRole(ctx, "iam_for_lambda", &iam.RoleArgs{
Name: pulumi.String("iam_for_lambda"),
AssumeRolePolicy: pulumi.String(assumeRole.Json),
})
if err != nil {
return err
}
func1, err := lambda.NewFunction(ctx, "func1", &lambda.FunctionArgs{
Code: pulumi.NewFileArchive("your-function1.zip"),
Name: pulumi.String("example_lambda_name1"),
Role: iamForLambda.Arn,
Handler: pulumi.String("exports.example"),
Runtime: pulumi.String(lambda.RuntimeNodeJS20dX),
})
if err != nil {
return err
}
bucket, err := s3.NewBucket(ctx, "bucket", &s3.BucketArgs{
Bucket: pulumi.String("your-bucket-name"),
})
if err != nil {
return err
}
allowBucket1, err := lambda.NewPermission(ctx, "allow_bucket1", &lambda.PermissionArgs{
StatementId: pulumi.String("AllowExecutionFromS3Bucket1"),
Action: pulumi.String("lambda:InvokeFunction"),
Function: func1.Arn,
Principal: pulumi.String("s3.amazonaws.com"),
SourceArn: bucket.Arn,
})
if err != nil {
return err
}
func2, err := lambda.NewFunction(ctx, "func2", &lambda.FunctionArgs{
Code: pulumi.NewFileArchive("your-function2.zip"),
Name: pulumi.String("example_lambda_name2"),
Role: iamForLambda.Arn,
Handler: pulumi.String("exports.example"),
})
if err != nil {
return err
}
allowBucket2, err := lambda.NewPermission(ctx, "allow_bucket2", &lambda.PermissionArgs{
StatementId: pulumi.String("AllowExecutionFromS3Bucket2"),
Action: pulumi.String("lambda:InvokeFunction"),
Function: func2.Arn,
Principal: pulumi.String("s3.amazonaws.com"),
SourceArn: bucket.Arn,
})
if err != nil {
return err
}
_, err = s3.NewBucketNotification(ctx, "bucket_notification", &s3.BucketNotificationArgs{
Bucket: bucket.ID(),
LambdaFunctions: s3.BucketNotificationLambdaFunctionArray{
&s3.BucketNotificationLambdaFunctionArgs{
LambdaFunctionArn: func1.Arn,
Events: pulumi.StringArray{
pulumi.String("s3:ObjectCreated:*"),
},
FilterPrefix: pulumi.String("AWSLogs/"),
FilterSuffix: pulumi.String(".log"),
},
&s3.BucketNotificationLambdaFunctionArgs{
LambdaFunctionArn: func2.Arn,
Events: pulumi.StringArray{
pulumi.String("s3:ObjectCreated:*"),
},
FilterPrefix: pulumi.String("OtherLogs/"),
FilterSuffix: pulumi.String(".log"),
},
},
}, pulumi.DependsOn([]pulumi.Resource{
allowBucket1,
allowBucket2,
}))
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var assumeRole = Aws.Iam.GetPolicyDocument.Invoke(new()
{
Statements = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
{
Effect = "Allow",
Principals = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
{
Type = "Service",
Identifiers = new[]
{
"lambda.amazonaws.com",
},
},
},
Actions = new[]
{
"sts:AssumeRole",
},
},
},
});
var iamForLambda = new Aws.Iam.Role("iam_for_lambda", new()
{
Name = "iam_for_lambda",
AssumeRolePolicy = assumeRole.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
});
var func1 = new Aws.Lambda.Function("func1", new()
{
Code = new FileArchive("your-function1.zip"),
Name = "example_lambda_name1",
Role = iamForLambda.Arn,
Handler = "exports.example",
Runtime = Aws.Lambda.Runtime.NodeJS20dX,
});
var bucket = new Aws.S3.Bucket("bucket", new()
{
BucketName = "your-bucket-name",
});
var allowBucket1 = new Aws.Lambda.Permission("allow_bucket1", new()
{
StatementId = "AllowExecutionFromS3Bucket1",
Action = "lambda:InvokeFunction",
Function = func1.Arn,
Principal = "s3.amazonaws.com",
SourceArn = bucket.Arn,
});
var func2 = new Aws.Lambda.Function("func2", new()
{
Code = new FileArchive("your-function2.zip"),
Name = "example_lambda_name2",
Role = iamForLambda.Arn,
Handler = "exports.example",
});
var allowBucket2 = new Aws.Lambda.Permission("allow_bucket2", new()
{
StatementId = "AllowExecutionFromS3Bucket2",
Action = "lambda:InvokeFunction",
Function = func2.Arn,
Principal = "s3.amazonaws.com",
SourceArn = bucket.Arn,
});
var bucketNotification = new Aws.S3.BucketNotification("bucket_notification", new()
{
Bucket = bucket.Id,
LambdaFunctions = new[]
{
new Aws.S3.Inputs.BucketNotificationLambdaFunctionArgs
{
LambdaFunctionArn = func1.Arn,
Events = new[]
{
"s3:ObjectCreated:*",
},
FilterPrefix = "AWSLogs/",
FilterSuffix = ".log",
},
new Aws.S3.Inputs.BucketNotificationLambdaFunctionArgs
{
LambdaFunctionArn = func2.Arn,
Events = new[]
{
"s3:ObjectCreated:*",
},
FilterPrefix = "OtherLogs/",
FilterSuffix = ".log",
},
},
}, new CustomResourceOptions
{
DependsOn =
{
allowBucket1,
allowBucket2,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.iam.Role;
import com.pulumi.aws.iam.RoleArgs;
import com.pulumi.aws.lambda.Function;
import com.pulumi.aws.lambda.FunctionArgs;
import com.pulumi.aws.s3.Bucket;
import com.pulumi.aws.s3.BucketArgs;
import com.pulumi.aws.lambda.Permission;
import com.pulumi.aws.lambda.PermissionArgs;
import com.pulumi.aws.s3.BucketNotification;
import com.pulumi.aws.s3.BucketNotificationArgs;
import com.pulumi.aws.s3.inputs.BucketNotificationLambdaFunctionArgs;
import com.pulumi.asset.FileArchive;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
final var assumeRole = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
.statements(GetPolicyDocumentStatementArgs.builder()
.effect("Allow")
.principals(GetPolicyDocumentStatementPrincipalArgs.builder()
.type("Service")
.identifiers("lambda.amazonaws.com")
.build())
.actions("sts:AssumeRole")
.build())
.build());
var iamForLambda = new Role("iamForLambda", RoleArgs.builder()
.name("iam_for_lambda")
.assumeRolePolicy(assumeRole.json())
.build());
var func1 = new Function("func1", FunctionArgs.builder()
.code(new FileArchive("your-function1.zip"))
.name("example_lambda_name1")
.role(iamForLambda.arn())
.handler("exports.example")
.runtime("nodejs20.x")
.build());
var bucket = new Bucket("bucket", BucketArgs.builder()
.bucket("your-bucket-name")
.build());
var allowBucket1 = new Permission("allowBucket1", PermissionArgs.builder()
.statementId("AllowExecutionFromS3Bucket1")
.action("lambda:InvokeFunction")
.function(func1.arn())
.principal("s3.amazonaws.com")
.sourceArn(bucket.arn())
.build());
var func2 = new Function("func2", FunctionArgs.builder()
.code(new FileArchive("your-function2.zip"))
.name("example_lambda_name2")
.role(iamForLambda.arn())
.handler("exports.example")
.build());
var allowBucket2 = new Permission("allowBucket2", PermissionArgs.builder()
.statementId("AllowExecutionFromS3Bucket2")
.action("lambda:InvokeFunction")
.function(func2.arn())
.principal("s3.amazonaws.com")
.sourceArn(bucket.arn())
.build());
var bucketNotification = new BucketNotification("bucketNotification", BucketNotificationArgs.builder()
.bucket(bucket.id())
.lambdaFunctions(
BucketNotificationLambdaFunctionArgs.builder()
.lambdaFunctionArn(func1.arn())
.events("s3:ObjectCreated:*")
.filterPrefix("AWSLogs/")
.filterSuffix(".log")
.build(),
BucketNotificationLambdaFunctionArgs.builder()
.lambdaFunctionArn(func2.arn())
.events("s3:ObjectCreated:*")
.filterPrefix("OtherLogs/")
.filterSuffix(".log")
.build())
.build(), CustomResourceOptions.builder()
.dependsOn(
allowBucket1,
allowBucket2)
.build());
}
}
resources:
iamForLambda:
type: aws:iam:Role
name: iam_for_lambda
properties:
name: iam_for_lambda
assumeRolePolicy: ${assumeRole.json}
allowBucket1:
type: aws:lambda:Permission
name: allow_bucket1
properties:
statementId: AllowExecutionFromS3Bucket1
action: lambda:InvokeFunction
function: ${func1.arn}
principal: s3.amazonaws.com
sourceArn: ${bucket.arn}
func1:
type: aws:lambda:Function
properties:
code:
fn::FileArchive: your-function1.zip
name: example_lambda_name1
role: ${iamForLambda.arn}
handler: exports.example
runtime: nodejs20.x
allowBucket2:
type: aws:lambda:Permission
name: allow_bucket2
properties:
statementId: AllowExecutionFromS3Bucket2
action: lambda:InvokeFunction
function: ${func2.arn}
principal: s3.amazonaws.com
sourceArn: ${bucket.arn}
func2:
type: aws:lambda:Function
properties:
code:
fn::FileArchive: your-function2.zip
name: example_lambda_name2
role: ${iamForLambda.arn}
handler: exports.example
bucket:
type: aws:s3:Bucket
properties:
bucket: your-bucket-name
bucketNotification:
type: aws:s3:BucketNotification
name: bucket_notification
properties:
bucket: ${bucket.id}
lambdaFunctions:
- lambdaFunctionArn: ${func1.arn}
events:
- s3:ObjectCreated:*
filterPrefix: AWSLogs/
filterSuffix: .log
- lambdaFunctionArn: ${func2.arn}
events:
- s3:ObjectCreated:*
filterPrefix: OtherLogs/
filterSuffix: .log
options:
dependsOn:
- ${allowBucket1}
- ${allowBucket2}
variables:
assumeRole:
fn::invoke:
function: aws:iam:getPolicyDocument
arguments:
statements:
- effect: Allow
principals:
- type: Service
identifiers:
- lambda.amazonaws.com
actions:
- sts:AssumeRole
Each entry in the lambdaFunctions array defines a separate routing rule. Here, files under “AWSLogs/” trigger func1, while “OtherLogs/” files trigger func2. Each Lambda function needs its own Permission resource. The dependsOn ensures permissions exist before S3 attempts invocations.
Send event notifications to SQS queues
Teams building decoupled architectures route S3 events through SQS queues, allowing multiple consumers to process uploads at their own pace.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const bucket = new aws.s3.Bucket("bucket", {bucket: "your-bucket-name"});
const queue = aws.iam.getPolicyDocumentOutput({
statements: [{
effect: "Allow",
principals: [{
type: "*",
identifiers: ["*"],
}],
actions: ["sqs:SendMessage"],
resources: ["arn:aws:sqs:*:*:s3-event-notification-queue"],
conditions: [{
test: "ArnEquals",
variable: "aws:SourceArn",
values: [bucket.arn],
}],
}],
});
const queueQueue = new aws.sqs.Queue("queue", {
name: "s3-event-notification-queue",
policy: queue.apply(queue => queue.json),
});
const bucketNotification = new aws.s3.BucketNotification("bucket_notification", {
bucket: bucket.id,
queues: [{
queueArn: queueQueue.arn,
events: ["s3:ObjectCreated:*"],
filterSuffix: ".log",
}],
});
import pulumi
import pulumi_aws as aws
bucket = aws.s3.Bucket("bucket", bucket="your-bucket-name")
queue = aws.iam.get_policy_document_output(statements=[{
"effect": "Allow",
"principals": [{
"type": "*",
"identifiers": ["*"],
}],
"actions": ["sqs:SendMessage"],
"resources": ["arn:aws:sqs:*:*:s3-event-notification-queue"],
"conditions": [{
"test": "ArnEquals",
"variable": "aws:SourceArn",
"values": [bucket.arn],
}],
}])
queue_queue = aws.sqs.Queue("queue",
name="s3-event-notification-queue",
policy=queue.json)
bucket_notification = aws.s3.BucketNotification("bucket_notification",
bucket=bucket.id,
queues=[{
"queue_arn": queue_queue.arn,
"events": ["s3:ObjectCreated:*"],
"filter_suffix": ".log",
}])
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/s3"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/sqs"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
bucket, err := s3.NewBucket(ctx, "bucket", &s3.BucketArgs{
Bucket: pulumi.String("your-bucket-name"),
})
if err != nil {
return err
}
queue := iam.GetPolicyDocumentOutput(ctx, iam.GetPolicyDocumentOutputArgs{
Statements: iam.GetPolicyDocumentStatementArray{
&iam.GetPolicyDocumentStatementArgs{
Effect: pulumi.String("Allow"),
Principals: iam.GetPolicyDocumentStatementPrincipalArray{
&iam.GetPolicyDocumentStatementPrincipalArgs{
Type: pulumi.String("*"),
Identifiers: pulumi.StringArray{
pulumi.String("*"),
},
},
},
Actions: pulumi.StringArray{
pulumi.String("sqs:SendMessage"),
},
Resources: pulumi.StringArray{
pulumi.String("arn:aws:sqs:*:*:s3-event-notification-queue"),
},
Conditions: iam.GetPolicyDocumentStatementConditionArray{
&iam.GetPolicyDocumentStatementConditionArgs{
Test: pulumi.String("ArnEquals"),
Variable: pulumi.String("aws:SourceArn"),
Values: pulumi.StringArray{
bucket.Arn,
},
},
},
},
},
}, nil)
queueQueue, err := sqs.NewQueue(ctx, "queue", &sqs.QueueArgs{
Name: pulumi.String("s3-event-notification-queue"),
Policy: pulumi.String(queue.ApplyT(func(queue iam.GetPolicyDocumentResult) (*string, error) {
return &queue.Json, nil
}).(pulumi.StringPtrOutput)),
})
if err != nil {
return err
}
_, err = s3.NewBucketNotification(ctx, "bucket_notification", &s3.BucketNotificationArgs{
Bucket: bucket.ID(),
Queues: s3.BucketNotificationQueueArray{
&s3.BucketNotificationQueueArgs{
QueueArn: queueQueue.Arn,
Events: pulumi.StringArray{
pulumi.String("s3:ObjectCreated:*"),
},
FilterSuffix: pulumi.String(".log"),
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var bucket = new Aws.S3.Bucket("bucket", new()
{
BucketName = "your-bucket-name",
});
var queue = Aws.Iam.GetPolicyDocument.Invoke(new()
{
Statements = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
{
Effect = "Allow",
Principals = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
{
Type = "*",
Identifiers = new[]
{
"*",
},
},
},
Actions = new[]
{
"sqs:SendMessage",
},
Resources = new[]
{
"arn:aws:sqs:*:*:s3-event-notification-queue",
},
Conditions = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementConditionInputArgs
{
Test = "ArnEquals",
Variable = "aws:SourceArn",
Values = new[]
{
bucket.Arn,
},
},
},
},
},
});
var queueQueue = new Aws.Sqs.Queue("queue", new()
{
Name = "s3-event-notification-queue",
Policy = queue.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
});
var bucketNotification = new Aws.S3.BucketNotification("bucket_notification", new()
{
Bucket = bucket.Id,
Queues = new[]
{
new Aws.S3.Inputs.BucketNotificationQueueArgs
{
QueueArn = queueQueue.Arn,
Events = new[]
{
"s3:ObjectCreated:*",
},
FilterSuffix = ".log",
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.s3.Bucket;
import com.pulumi.aws.s3.BucketArgs;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.sqs.Queue;
import com.pulumi.aws.sqs.QueueArgs;
import com.pulumi.aws.s3.BucketNotification;
import com.pulumi.aws.s3.BucketNotificationArgs;
import com.pulumi.aws.s3.inputs.BucketNotificationQueueArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var bucket = new Bucket("bucket", BucketArgs.builder()
.bucket("your-bucket-name")
.build());
final var queue = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
.statements(GetPolicyDocumentStatementArgs.builder()
.effect("Allow")
.principals(GetPolicyDocumentStatementPrincipalArgs.builder()
.type("*")
.identifiers("*")
.build())
.actions("sqs:SendMessage")
.resources("arn:aws:sqs:*:*:s3-event-notification-queue")
.conditions(GetPolicyDocumentStatementConditionArgs.builder()
.test("ArnEquals")
.variable("aws:SourceArn")
.values(bucket.arn())
.build())
.build())
.build());
var queueQueue = new Queue("queueQueue", QueueArgs.builder()
.name("s3-event-notification-queue")
.policy(queue.applyValue(_queue -> _queue.json()))
.build());
var bucketNotification = new BucketNotification("bucketNotification", BucketNotificationArgs.builder()
.bucket(bucket.id())
.queues(BucketNotificationQueueArgs.builder()
.queueArn(queueQueue.arn())
.events("s3:ObjectCreated:*")
.filterSuffix(".log")
.build())
.build());
}
}
resources:
queueQueue:
type: aws:sqs:Queue
name: queue
properties:
name: s3-event-notification-queue
policy: ${queue.json}
bucket:
type: aws:s3:Bucket
properties:
bucket: your-bucket-name
bucketNotification:
type: aws:s3:BucketNotification
name: bucket_notification
properties:
bucket: ${bucket.id}
queues:
- queueArn: ${queueQueue.arn}
events:
- s3:ObjectCreated:*
filterSuffix: .log
variables:
queue:
fn::invoke:
function: aws:iam:getPolicyDocument
arguments:
statements:
- effect: Allow
principals:
- type: '*'
identifiers:
- '*'
actions:
- sqs:SendMessage
resources:
- arn:aws:sqs:*:*:s3-event-notification-queue
conditions:
- test: ArnEquals
variable: aws:SourceArn
values:
- ${bucket.arn}
The queues property sends event notifications to an SQS queue instead of invoking Lambda directly. The queue’s policy must grant S3 permission to send messages (shown in the queue policy document). This pattern decouples producers from consumers, enabling multiple workers to process events independently.
Route events through EventBridge for flexible routing
EventBridge provides centralized event routing that can fan out to multiple targets without configuring each destination in the bucket notification.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const bucket = new aws.s3.Bucket("bucket", {bucket: "your-bucket-name"});
const bucketNotification = new aws.s3.BucketNotification("bucket_notification", {
bucket: bucket.id,
eventbridge: true,
});
import pulumi
import pulumi_aws as aws
bucket = aws.s3.Bucket("bucket", bucket="your-bucket-name")
bucket_notification = aws.s3.BucketNotification("bucket_notification",
bucket=bucket.id,
eventbridge=True)
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
bucket, err := s3.NewBucket(ctx, "bucket", &s3.BucketArgs{
Bucket: pulumi.String("your-bucket-name"),
})
if err != nil {
return err
}
_, err = s3.NewBucketNotification(ctx, "bucket_notification", &s3.BucketNotificationArgs{
Bucket: bucket.ID(),
Eventbridge: pulumi.Bool(true),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var bucket = new Aws.S3.Bucket("bucket", new()
{
BucketName = "your-bucket-name",
});
var bucketNotification = new Aws.S3.BucketNotification("bucket_notification", new()
{
Bucket = bucket.Id,
Eventbridge = true,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.s3.Bucket;
import com.pulumi.aws.s3.BucketArgs;
import com.pulumi.aws.s3.BucketNotification;
import com.pulumi.aws.s3.BucketNotificationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var bucket = new Bucket("bucket", BucketArgs.builder()
.bucket("your-bucket-name")
.build());
var bucketNotification = new BucketNotification("bucketNotification", BucketNotificationArgs.builder()
.bucket(bucket.id())
.eventbridge(true)
.build());
}
}
resources:
bucket:
type: aws:s3:Bucket
properties:
bucket: your-bucket-name
bucketNotification:
type: aws:s3:BucketNotification
name: bucket_notification
properties:
bucket: ${bucket.id}
eventbridge: true
Setting eventbridge to true sends all bucket events to EventBridge. From there, EventBridge rules route events to Lambda, SQS, SNS, or other targets based on event patterns. This approach centralizes routing logic outside the bucket configuration.
Beyond these examples
These snippets focus on specific notification configuration features: Lambda function invocation with prefix/suffix filtering, SQS queue delivery, and EventBridge integration. They’re intentionally minimal rather than full event-driven architectures.
The examples reference pre-existing infrastructure such as Lambda functions with execution roles, SQS queues with appropriate policies, and Lambda permissions granting S3 invoke access. They focus on configuring the notification rather than provisioning everything around it.
To keep things focused, common notification patterns are omitted, including:
- SNS topic notifications (topics property)
- Multiple event types beyond s3:ObjectCreated:*
- Complex filter patterns (filterPrefix and filterSuffix combinations)
- Event notification IDs for tracking (id property)
These omissions are intentional: the goal is to illustrate how each notification feature is wired, not provide drop-in event processing modules. See the S3 BucketNotification resource reference for all available configuration options.
Let's configure AWS S3 Bucket Event Notifications
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Common Pitfalls & Limitations
BucketNotification resources for the same bucket causes perpetual configuration drift. Instead, use a single BucketNotification resource with arrays for lambdaFunctions, queues, or topics to configure multiple triggers.BucketNotification resource.IAM Permissions & Setup
aws.lambda.Permission resource with action set to lambda:InvokeFunction, principal set to s3.amazonaws.com, and sourceArn set to the bucket ARN. Add dependsOn to the BucketNotification to ensure the permission is created first.SNS:Publish action from principal s3.amazonaws.com with a condition that checks aws:SourceArn matches the bucket ARN.sqs:SendMessage action with a condition that checks aws:SourceArn equals the bucket ARN.Configuration & Filtering
BucketNotification resource with an array of entries in lambdaFunctions. Each entry can have different filterPrefix or filterSuffix values to route events to different functions. Remember to create separate aws.lambda.Permission resources for each function and include them in dependsOn.filterPrefix to filter by object key path (e.g., images/ or AWSLogs/) and filterSuffix to filter by file extension (e.g., .log or .jpg).eventbridge to true in the BucketNotification resource to enable Amazon EventBridge notifications.queues with multiple entries, each specifying the same queueArn but different filterPrefix values and unique id values to distinguish them.