DEV Community

Cover image for Handling File Uploads in AWS Lambda with Powertools OpenAPI (From Limitation to Production Feature)

Handling File Uploads in AWS Lambda with Powertools OpenAPI (From Limitation to Production Feature)

Introduction

Handling file uploads in serverless APIs sounds simple until you actually try to do it.

If you're building APIs with AWS Lambda Powertools and OpenAPI validation, you quickly run into a limitation:

multipart/form-data isn’t natively supported in the same way as JSON or form-encoded requests.

That gap forces teams into workarounds:

  • Manual multipart parsing
  • Base64 hacks
  • Disabling validation entirely

None of which are ideal in production systems.

This article walks through:

  • The real problem
  • How the feature was designed
  • How you can now use it in practice

The Problem: File Uploads Break the Abstraction

Before this feature, Powertools handled:

  • JSON payloads
  • Query parameters
  • Headers
  • Form data (application/x-www-form-urlencoded)

But not:

multipart/form-data (file uploads)

That meant:

@app.post("/upload")
def upload(file: bytes):
    ...
Enter fullscreen mode Exit fullscreen mode

simply didn’t work with OpenAPI validation.

Instead, developers had to:

  • Parse raw request bodies manually
  • Disable validation middleware
  • Or redesign APIs around non-standard formats

At scale, this creates:

  • Inconsistent APIs
  • Security gaps
  • Poor developer experience

The Goal: Make File Uploads First-Class

The aim was simple:

Make file uploads work the same way as Query(), Header(), and Form()

That means:

  • Type-safe
  • Automatically validated
  • Fully reflected in OpenAPI schema
  • Works with Swagger UI

The Solution: File() Parameter Support

You can now define file inputs like this:

from typing import Annotated
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.event_handler.openapi.params import File, UploadFile

app = APIGatewayRestResolver(enable_validation=True)
app.enable_swagger(path="/swagger")

@app.post("/upload")
def upload(file_data: Annotated[UploadFile, File(description="File to upload")]):
    return {
        "filename": file_data.filename,
        "content_type": file_data.content_type,
        "file_size": len(file_data),
    }
Enter fullscreen mode Exit fullscreen mode

Two Ways to Work with Files

1. Raw bytes

file: Annotated[bytes, File()]
Enter fullscreen mode Exit fullscreen mode

You get:

  • File content only

2. Rich file object

file_data: Annotated[UploadFile, File()]
Enter fullscreen mode Exit fullscreen mode

You get:

  • Content
  • Filename
  • Content type

This is usually what you want in real systems.


Combining Files with Form Data

@app.post("/upload-csv")
def upload_csv(
    file_data: Annotated[UploadFile, File(description="CSV file")],
    separator: Annotated[str, Form(description="CSV separator")] = ",",
):
    text = file_data.content.decode("utf-8")
Enter fullscreen mode Exit fullscreen mode

This unlocks:

  • Metadata + file uploads
  • Real-world API patterns

What Changed Under the Hood

Supporting this wasn’t just adding a new parameter type.

It required:

  • Multipart parsing logic
  • Boundary handling (including WebKit quirks)
  • Base64 decoding for Lambda event payloads
  • Differentiating file vs form fields
  • OpenAPI schema generation (format: binary)
  • Validation integration

There’s also a helpful runtime safeguard:

If multipart requests aren’t properly base64 encoded, a warning is emitted

This helps catch common misconfigurations early.


API Gateway Gotcha (Important)

If you're using REST API (v1), you must configure:

Globals:
  Api:
    BinaryMediaTypes:
      - "multipart~1form-data"
Enter fullscreen mode Exit fullscreen mode

Without this:
File uploads won’t work correctly.

For:

  • HTTP API (v2)
  • Lambda Function URLs
  • ALB

It works out of the box.


Before vs After

Before

  • Manual parsing
  • No validation
  • Custom schemas
  • Inconsistent APIs

After

  • Native File() support
  • OpenAPI validation
  • Swagger UI integration
  • Cleaner, safer APIs

Why This Matters

This isn’t just about file uploads.

It’s about removing friction from real-world API design.

When basic capabilities are missing:

  • Engineers build workarounds
  • Systems become inconsistent
  • Reliability suffers

By making file uploads a first-class feature:

  • APIs become more predictable
  • Validation becomes reliable
  • Developer experience improves significantly

Open Source Insight

One interesting part of this work:

The implementation evolved through multiple iterations before reaching the final version.

Large features often:

  • Start broad
  • Get refined for maintainability
  • Land as cleaner, more focused implementations

That process is what makes open source powerful —
it’s not just about shipping code, but improving it collaboratively.


Final Thoughts

If you’re building serverless APIs and dealing with file uploads:

You no longer need workarounds.

This feature brings Powertools closer to frameworks like:

  • FastAPI
  • Django
  • Express

…but in a serverless-native way.


References

Top comments (0)