Configure AWS S3 Bucket Event Notifications

The aws:s3/bucketNotification:BucketNotification resource, part of the Pulumi AWS provider, configures S3 event notifications that trigger when objects are created, removed, or restored in a bucket. This guide focuses on four capabilities: Lambda function invocation, SNS topic publishing, SQS queue delivery, and EventBridge routing.

Each S3 bucket supports only one notification configuration resource. Notifications require destination resources with policies granting S3 permission to invoke or publish. The examples are intentionally small. Combine them with your own Lambda functions, topics, queues, and IAM 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 objects matching the filter land in the bucket, S3 invokes the Lambda function. The lambdaFunctions array specifies the function ARN, event types to monitor, and optional filterPrefix and filterSuffix for path-based routing. The lambda.Permission resource grants S3 permission to invoke the 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 objects 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, objects under “AWSLogs/” trigger func1, while “OtherLogs/” objects trigger func2. The dependsOn ensures both Permission resources exist before creating the notification configuration, preventing race conditions during deployment.

Publish events to SNS topics for fan-out

SNS topics enable fan-out patterns where multiple subscribers receive the same S3 event, supporting workflows where uploads trigger notifications to multiple systems or teams.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const bucket = new aws.s3.Bucket("bucket", {bucket: "your-bucket-name"});
const topic = aws.iam.getPolicyDocumentOutput({
    statements: [{
        effect: "Allow",
        principals: [{
            type: "Service",
            identifiers: ["s3.amazonaws.com"],
        }],
        actions: ["SNS:Publish"],
        resources: ["arn:aws:sns:*:*:s3-event-notification-topic"],
        conditions: [{
            test: "ArnLike",
            variable: "aws:SourceArn",
            values: [bucket.arn],
        }],
    }],
});
const topicTopic = new aws.sns.Topic("topic", {
    name: "s3-event-notification-topic",
    policy: topic.apply(topic => topic.json),
});
const bucketNotification = new aws.s3.BucketNotification("bucket_notification", {
    bucket: bucket.id,
    topics: [{
        topicArn: topicTopic.arn,
        events: ["s3:ObjectCreated:*"],
        filterSuffix: ".log",
    }],
});
import pulumi
import pulumi_aws as aws

bucket = aws.s3.Bucket("bucket", bucket="your-bucket-name")
topic = aws.iam.get_policy_document_output(statements=[{
    "effect": "Allow",
    "principals": [{
        "type": "Service",
        "identifiers": ["s3.amazonaws.com"],
    }],
    "actions": ["SNS:Publish"],
    "resources": ["arn:aws:sns:*:*:s3-event-notification-topic"],
    "conditions": [{
        "test": "ArnLike",
        "variable": "aws:SourceArn",
        "values": [bucket.arn],
    }],
}])
topic_topic = aws.sns.Topic("topic",
    name="s3-event-notification-topic",
    policy=topic.json)
bucket_notification = aws.s3.BucketNotification("bucket_notification",
    bucket=bucket.id,
    topics=[{
        "topic_arn": topic_topic.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/sns"
	"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
		}
		topic := iam.GetPolicyDocumentOutput(ctx, iam.GetPolicyDocumentOutputArgs{
			Statements: iam.GetPolicyDocumentStatementArray{
				&iam.GetPolicyDocumentStatementArgs{
					Effect: pulumi.String("Allow"),
					Principals: iam.GetPolicyDocumentStatementPrincipalArray{
						&iam.GetPolicyDocumentStatementPrincipalArgs{
							Type: pulumi.String("Service"),
							Identifiers: pulumi.StringArray{
								pulumi.String("s3.amazonaws.com"),
							},
						},
					},
					Actions: pulumi.StringArray{
						pulumi.String("SNS:Publish"),
					},
					Resources: pulumi.StringArray{
						pulumi.String("arn:aws:sns:*:*:s3-event-notification-topic"),
					},
					Conditions: iam.GetPolicyDocumentStatementConditionArray{
						&iam.GetPolicyDocumentStatementConditionArgs{
							Test:     pulumi.String("ArnLike"),
							Variable: pulumi.String("aws:SourceArn"),
							Values: pulumi.StringArray{
								bucket.Arn,
							},
						},
					},
				},
			},
		}, nil)
		topicTopic, err := sns.NewTopic(ctx, "topic", &sns.TopicArgs{
			Name: pulumi.String("s3-event-notification-topic"),
			Policy: pulumi.String(topic.ApplyT(func(topic iam.GetPolicyDocumentResult) (*string, error) {
				return &topic.Json, nil
			}).(pulumi.StringPtrOutput)),
		})
		if err != nil {
			return err
		}
		_, err = s3.NewBucketNotification(ctx, "bucket_notification", &s3.BucketNotificationArgs{
			Bucket: bucket.ID(),
			Topics: s3.BucketNotificationTopicArray{
				&s3.BucketNotificationTopicArgs{
					TopicArn: topicTopic.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 topic = 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[]
                        {
                            "s3.amazonaws.com",
                        },
                    },
                },
                Actions = new[]
                {
                    "SNS:Publish",
                },
                Resources = new[]
                {
                    "arn:aws:sns:*:*:s3-event-notification-topic",
                },
                Conditions = new[]
                {
                    new Aws.Iam.Inputs.GetPolicyDocumentStatementConditionInputArgs
                    {
                        Test = "ArnLike",
                        Variable = "aws:SourceArn",
                        Values = new[]
                        {
                            bucket.Arn,
                        },
                    },
                },
            },
        },
    });

    var topicTopic = new Aws.Sns.Topic("topic", new()
    {
        Name = "s3-event-notification-topic",
        Policy = topic.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
    });

    var bucketNotification = new Aws.S3.BucketNotification("bucket_notification", new()
    {
        Bucket = bucket.Id,
        Topics = new[]
        {
            new Aws.S3.Inputs.BucketNotificationTopicArgs
            {
                TopicArn = topicTopic.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.sns.Topic;
import com.pulumi.aws.sns.TopicArgs;
import com.pulumi.aws.s3.BucketNotification;
import com.pulumi.aws.s3.BucketNotificationArgs;
import com.pulumi.aws.s3.inputs.BucketNotificationTopicArgs;
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 topic = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
            .statements(GetPolicyDocumentStatementArgs.builder()
                .effect("Allow")
                .principals(GetPolicyDocumentStatementPrincipalArgs.builder()
                    .type("Service")
                    .identifiers("s3.amazonaws.com")
                    .build())
                .actions("SNS:Publish")
                .resources("arn:aws:sns:*:*:s3-event-notification-topic")
                .conditions(GetPolicyDocumentStatementConditionArgs.builder()
                    .test("ArnLike")
                    .variable("aws:SourceArn")
                    .values(bucket.arn())
                    .build())
                .build())
            .build());

        var topicTopic = new Topic("topicTopic", TopicArgs.builder()
            .name("s3-event-notification-topic")
            .policy(topic.applyValue(_topic -> _topic.json()))
            .build());

        var bucketNotification = new BucketNotification("bucketNotification", BucketNotificationArgs.builder()
            .bucket(bucket.id())
            .topics(BucketNotificationTopicArgs.builder()
                .topicArn(topicTopic.arn())
                .events("s3:ObjectCreated:*")
                .filterSuffix(".log")
                .build())
            .build());

    }
}
resources:
  topicTopic:
    type: aws:sns:Topic
    name: topic
    properties:
      name: s3-event-notification-topic
      policy: ${topic.json}
  bucket:
    type: aws:s3:Bucket
    properties:
      bucket: your-bucket-name
  bucketNotification:
    type: aws:s3:BucketNotification
    name: bucket_notification
    properties:
      bucket: ${bucket.id}
      topics:
        - topicArn: ${topicTopic.arn}
          events:
            - s3:ObjectCreated:*
          filterSuffix: .log
variables:
  topic:
    fn::invoke:
      function: aws:iam:getPolicyDocument
      arguments:
        statements:
          - effect: Allow
            principals:
              - type: Service
                identifiers:
                  - s3.amazonaws.com
            actions:
              - SNS:Publish
            resources:
              - arn:aws:sns:*:*:s3-event-notification-topic
            conditions:
              - test: ArnLike
                variable: aws:SourceArn
                values:
                  - ${bucket.arn}

The topics array specifies the SNS topic ARN and event types. S3 publishes a message to the topic whenever a matching event occurs. The SNS topic policy must allow s3.amazonaws.com to publish; the example shows the required policy document with a condition restricting the source to the specific bucket ARN.

Queue events for asynchronous processing

SQS queues decouple S3 events from processing, allowing consumers to poll at their own pace and handle backpressure gracefully.

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 array configures SQS delivery. S3 sends messages to the queue for each matching event. Like SNS, the SQS queue policy must grant s3.amazonaws.com permission to send messages, with a condition limiting the source to the bucket ARN.

Route events through EventBridge for complex workflows

EventBridge provides advanced routing and filtering capabilities beyond basic S3 notifications. Teams use it to build event-driven architectures with multiple targets and transformation rules.

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 enables EventBridge delivery for all bucket events. Unlike Lambda, SNS, or SQS configurations, EventBridge doesn’t require filter configuration in the notification resource; you define routing rules in EventBridge itself.

Beyond these examples

These snippets focus on specific notification configuration features: Lambda, SNS, and SQS notification targets, prefix and suffix filtering, and EventBridge integration. They’re intentionally minimal rather than full event-driven applications.

The examples reference pre-existing infrastructure such as S3 buckets, Lambda functions with execution roles, and SNS topics and SQS queues with appropriate policies. They focus on configuring the notification rather than provisioning everything around it.

To keep things focused, common notification patterns are omitted, including:

  • Event type filtering beyond ObjectCreated (ObjectRemoved, ObjectRestore)
  • Multiple event types per notification configuration
  • Complex filter combinations (prefix AND suffix patterns)
  • Notification IDs for tracking specific configurations

These omissions are intentional: the goal is to illustrate how each notification target 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 FREE

Frequently Asked Questions

Common Issues & Limitations
Why am I seeing a perpetual difference in my S3 bucket notification configuration?
S3 buckets only support a single notification configuration resource. Declaring multiple BucketNotification resources for the same bucket causes perpetual configuration differences. Use a single resource with arrays (lambdaFunctions, queues, topics) to configure multiple triggers.
What happens to existing event notifications when I create this resource?
This resource overwrites any existing event notifications configured for the S3 bucket. Ensure all desired notifications are defined within this single resource.
Can I change the bucket after creating the notification configuration?
No, the bucket property is immutable and cannot be changed after creation. You must destroy and recreate the resource to change buckets.
Can I use this resource with S3 directory buckets?
No, this resource cannot be used with S3 directory buckets. It only works with standard S3 buckets.
IAM Permissions & Setup
What IAM permissions are required for Lambda notifications?
Create an aws.lambda.Permission resource with action set to lambda:InvokeFunction, principal set to s3.amazonaws.com, and sourceArn set to the bucket ARN. Use dependsOn to ensure the Permission is created before the BucketNotification.
What IAM permissions are required for SNS notifications?
The SNS topic needs a policy allowing the s3.amazonaws.com principal to perform the SNS:Publish action, with a condition checking that aws:SourceArn matches the bucket ARN.
What IAM permissions are required for SQS notifications?
The SQS queue needs a policy allowing the sqs:SendMessage action, with a condition checking that aws:SourceArn equals the bucket ARN.
Multiple Notifications & Filtering
How do I configure multiple Lambda functions to trigger from the same bucket?
Use an array in the lambdaFunctions property with different filterPrefix values for each function. Ensure all required aws.lambda.Permission resources are created and referenced in dependsOn.
How do I configure multiple SQS queues with different filters?
Use an array in the queues property, with each entry having a unique id and different filterPrefix values to route events based on object key prefixes.
How can I filter which S3 events trigger notifications?
Use filterPrefix to filter by object key prefix (e.g., images/, AWSLogs/) and filterSuffix to filter by suffix (e.g., .log, .jpg). Both can be combined in the same notification configuration.

Using a different cloud?

Explore storage guides for other cloud providers: