S3 Object Lambda Access Points: Transform Objects on Read, Not on Write
S3 Object Lambda Access Points run a Lambda function on every GET, so one canonical object can serve many derived views. Here's the request path, a worked downscaling example, and when precomputing wins.
19 Jun 2026, 10:01 UTC

One canonical object, several consumers, and each consumer wants something different. Mobile clients need a downscaled JPEG. A partner API needs account numbers stripped out. An analytics job wants CSV instead of the JSON your service writes. The default answer is to generate a derivative at write time — and then you own N copies, N backfills, and a migration every time a consumer changes its mind.
S3 Object Lambda Access Points move that work to the read path. The stored object never changes; a Lambda function rewrites the bytes on the way out. This post covers how the request path actually works, a worked downscaling example, and the cases where you should not use it.
What happens on a GET through an Object Lambda access point
An Object Lambda Access Point is a named endpoint with its own ARN. Clients use that ARN where they would normally put a bucket name. Three pieces are involved:
- Supporting access point — a normal S3 access point on the real bucket. It defines which objects the transformation can read.
- Lambda function — invoked by S3 for each request, receiving a context object that includes a presigned URL for the original object and a route/token pair for writing the response.
- Transformation configuration — the setting on the access point that names the function and the supporting access point.
The function either returns the transformed bytes directly or streams them back with the WriteGetObjectResponse API. The latter is the usual choice because it lets you set content type and other response headers. Only GET and HEAD flow through this path; writes go to the bucket as normal.
Worked example: downscale images for mobile clients
Assume a bucket catalog-originals in us-east-1, account 111122223333, holding full-resolution product photos. You want a second endpoint that always returns a smaller JPEG.
1. Create a supporting access point on catalog-originals (console or aws s3control create-access-point). Note its ARN.
2. Give the function role two permissions. It needs to read the original and to write the transformed response:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:us-east-1:111122223333:accesspoint/catalog-supporting/object/*"
},
{
"Effect": "Allow",
"Action": "s3-object-lambda:WriteGetObjectResponse",
"Resource": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/mobile-images"
}
]
}
3. Write the handler. The transform itself is elided — the point is the shape of the call. This is a sketch, not a tested artifact:
import urllib.request
import boto3
s3 = boto3.client("s3")
def handler(event, context):
ctx = event["getObjectContext"]
original = urllib.request.urlopen(ctx["inputS3Url"]).read()
derived = resize_jpeg(original, max_width=800) # your code
s3.write_get_object_response(
RequestRoute=ctx["outputRoute"],
RequestToken=ctx["outputToken"],
Body=derived,
ContentType="image/jpeg",
)
return {"statusCode": 200}
4. Create the Object Lambda access point pointing at that function and the supporting access point.
5. Test through the access point ARN, using credentials that hold s3-object-lambda:GetObject on it:
aws s3api get-object \
--bucket arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/mobile-images \
--key catalog/hero.jpg \
./hero-mobile.jpg
Compare hero-mobile.jpg against the original. If the file is identical, the function was not invoked or the response was not written back.
Trade-offs and limits
Every read now costs a Lambda invocation. That is the whole trade: you stop paying in storage and backfill complexity, and start paying in per-request latency and compute.
- Latency. Cold starts apply. A p99 that was dominated by S3 now includes your function's startup and execution.
- Cost. Lambda duration and requests, plus the extra S3 GET the function performs on the original, on top of the client's GET.
- Payload size. The synchronous invocation payload limit is 6 MB. Larger responses require streaming via
WriteGetObjectResponse; confirm the current response-size limits in the AWS documentation before designing around them. - Read-only. Writes and listing operations are not transformed by this path.
- Failure surface. An exception in your function surfaces to the client as a failed S3 GET. A bug in transformation logic looks like a broken bucket.
There is no caching in the Object Lambda path itself. If your reads are high-volume and the output is cacheable, either precompute derivatives or place a CDN in front — CloudFront can be configured with an Object Lambda access point as an origin, though you should verify current origin support in the docs for your region.
Choosing between the three approaches
| Approach | Best when | Main cost |
|---|---|---|
| Object Lambda on read | One canonical copy, moderate read rate, cheap stateless transform | Per-request latency and compute |
| Precomputed derivatives | Reads are heavy and cacheable, or the transform is expensive | Storage duplication and backfill jobs |
| Edge function (CloudFront) | Transform is trivial and must run close to users | Runtime and package constraints at the edge |
How to verify it is working
- GET the same key through the access point ARN and directly from the bucket. The bytes should differ in the expected way.
- Check the Lambda function's CloudWatch Logs and its
InvocationsandErrorsmetrics — each Object Lambda request should produce one invocation. - Run IAM Policy Simulator against the function role for
s3:GetObjectands3-object-lambda:WriteGetObjectResponseto confirm the permissions are no broader than needed. - Force the function to raise an exception and confirm the client sees an error rather than a stale or empty object. That tells you how the failure will present in production.
Availability note (flagged for review): confirm the current service status and regional availability of S3 Object Lambda Access Points in the AWS documentation before committing to this design. Treat the pattern above as a design sketch to be re-validated against current docs rather than a settled platform choice.
The decision rule
Use Object Lambda when you genuinely need one canonical object, the transformation is cheap, stateless, and idempotent, and your read rate is low enough that per-request compute does not dominate the bill. Precompute instead when reads are heavy, cacheable, or the transform is expensive. The test is simple: if you would not be comfortable running your transformation function on every single read, it does not belong on the read path.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.