Skip to content

Beyond JSON: Form Data and File Uploads

While JSON is the standard for modern APIs, sometimes you need to interact with classic HTML forms or handle file uploads. FastAPI handles these non-JSON requests gracefully, but they require a specific approach.

To handle uploaded files and form data, FastAPI relies on an external library called python-multipart. Before proceeding, you must install it:

Terminal window
pip install python-multipart

When receiving data from an HTML <form>, the media type is usually application/x-www-form-urlencoded. This is different from JSON. To tell FastAPI to read from form data instead of JSON, use the Form class.

from typing import Annotated
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]):
return {"username": username}

This works exactly like Body, Query, or Path. It tells FastAPI to look for username and password fields in the form data.

FastAPI provides two ways to define file parameters: bytes (using File) and UploadFile.

If you declare the type as bytes, FastAPI will read the entire file into memory.

from fastapi import File
@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
return {"file_size": len(file)}

This is simple for small files, but for large files (like videos), it can consume all your server’s memory and crash the application.

Using UploadFile is safer and more feature-rich. It uses Python’s SpooledTemporaryFile. This means it stays in memory up to a certain size limit, and then automatically spills over to disk.

from fastapi import UploadFile
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
return {"filename": file.filename, "content_type": file.content_type}

Comparison of the two approaches:

FeaturebytesUploadFile
StorageStored entirely in RAMSpooled (RAM + Disk)
PerformanceFast for tiny filesEfficient for large files
MetadataNone (just raw data)Has filename, content_type
InterfacePython bytes objectFile-like object (read/write/close)

You can handle multiple files at once by declaring a list of UploadFile.

from typing import List
@app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile]):
return {"filenames": [file.filename for file in files]}

This endpoint will accept an HTML form sending multiple files (e.g., <input type="file" multiple>) and process them efficiently.