How to Upload to S3 from a Lambda (boto3)
A Lambda that saves an upload to S3 is one put_object call - the catch is decoding the request body (API Gateway delivers it base64-encoded) and giving the function s3:PutObject. Here's the handler.
The flow
A form posts a file to API Gateway, which invokes a Lambda. The Lambda's one job: write the bytes to S3 under the given key. The two things to get right are decoding the body and the IAM permission.
The handler
import base64
import boto3
BUCKET = "user-uploads"
s3 = boto3.client("s3")
def handler(event, context=None):
key = event["key"]
body = base64.b64decode(event["body_b64"]) # API Gateway sends binary base64-encoded
s3.put_object(Bucket=BUCKET, Key=key, Body=body, ContentType="image/png")
return {"statusCode": 200, "key": key}
put_object writes the object in one call - no temp files, no multipart needed for normal
image sizes.
The gotchas
- Base64 body - API Gateway delivers binary uploads base64-encoded (
isBase64Encoded: true). Decode before writing, or you'll store the encoded text, not the image. - IAM permission - the Lambda's execution role needs
s3:PutObjectonarn:aws:s3:::user-uploads/*. Without it you getAccessDeniedat runtime. - ContentType - set it so the object serves with the right MIME type (browsers won't
render an image stored as
application/octet-stream). - Unique keys - generate the key (e.g.
uploads/<uuid>.png) so concurrent uploads don't overwrite each other.
Reuse the client
Create the boto3.client("s3") at module scope (outside handler), not inside it - Lambda
reuses the warm container across invocations, so the client is created once and connection
setup is amortized.
For large files
For big uploads, skip the Lambda body entirely: hand the client a presigned PUT URL and let it upload straight to S3. The Lambda only mints the URL - it never touches the bytes, which dodges Lambda's payload size limits.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Decoding a base64 body and put_object to S3
- Granting the Lambda role s3:PutObject
- Reusing the boto3 client across warm invocations
FAQ
How do I upload a file to S3 from a Lambda?
Call s3.put_object(Bucket=..., Key=..., Body=bytes, ContentType=...) with boto3. Decode the incoming body first (API Gateway sends binary uploads base64-encoded) and give the function's role s3:PutObject.
Why is my Lambda S3 upload AccessDenied?
The Lambda execution role is missing s3:PutObject on the bucket (arn:aws:s3:::your-bucket/*). Add that permission to the role's policy.
How do I upload large files to S3 via Lambda?
Don't pass big files through the Lambda - have it return a presigned PUT URL and let the client upload directly to S3. That avoids Lambda's request/response payload limits.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →