S3

Process S3 object events (created, removed, restored) in a Lambda triggered by an S3 event notification, with or without SNS/SQS in the middle.

AWS documentation

What AWS sends

event.Records is an array of S3 event records. Each record has eventName (ObjectCreated:Put, ObjectRemoved:Delete, etc.), eventTime, s3.bucket.name, s3.object.key (URL-encoded), and s3.object.size.

When S3 fans out via SNS or SQS, the original S3 record is nested inside the SNS/SQS envelope as a JSON string. @middy/event-normalizer walks the envelope shape S3 -> SNS -> SQS -> Lambda and unwraps to the original S3 record.

Direct S3 trigger

import middy from '@middy/core'

const lambdaHandler = async (event, context, { signal }) => {
  for (const record of event.Records) {
    const bucket = record.s3.bucket.name
    const key = decodeURIComponent(record.s3.object.key.replace(/+/g, ' '))
    // ... process
  }
}

export const handler = middy().handler(lambdaHandler)
import middy from '@middy/core'
import eventNormalizer from '@middy/event-normalizer'

export const handler = middy()
  .use(eventNormalizer()) // walks S3 -> SNS -> SQS -> Lambda envelopes
  .handler(async (event, context, { signal }) => {
    for (const record of event.Records) {
      const bucket = record.s3.bucket.name
      const key = decodeURIComponent(record.s3.object.key.replace(/+/g, ' '))
      // ...
    }
  })

With S3 Object Lambda (transform on read)

For S3 Object Lambda Access Points, see the s3-object event and @middy/s3-object-response.

Common gotchas

  • key is URL-encoded. Always decodeURIComponent(record.s3.object.key.replace(/\+/g, ' ')). Spaces in keys arrive as +.
  • Batch size is implicit. S3 typically delivers one record per invocation, but the schema is an array. Always iterate event.Records.
  • No automatic retry on direct triggers. A failed Lambda from a direct S3 notification will not retry by default; fan out via SNS or SQS for retry semantics.
  • Loops. A Lambda triggered by s3:ObjectCreated:* that writes back to the same bucket will infinite-loop. Use a prefix filter or write to a different bucket.

Last updated: