Form Data and File Uploads
In this tutorial, you will build a robust endpoint for processing user submissions that include both text data and file uploads. You will learn how to use the Form and File classes to handle multipart/form-data and how to use UploadFile for efficient file processing.
Prerequisites
To handle form data and file uploads, you must install the python-multipart package. FastAPI relies on this library to parse the incoming request body.
pip install python-multipart
[!WARNING] Ensure you install
python-multipartand not the package namedmultipart. Installing the wrong package will result in aRuntimeErrorwhen your application tries to parse form data.
Step 1: Defining Form Fields
Standard HTML forms send data as application/x-www-form-urlencoded. To receive these fields in FastAPI, use the Form class from fastapi.params.
Create a simple login endpoint that receives a username and password:
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}
By using Annotated[str, Form()], you tell FastAPI that these parameters should be extracted from the form body rather than from query parameters or a JSON body.
Step 2: Handling File Uploads
When you need to receive files, the request uses multipart/form-data. FastAPI provides the File class to define these parameters. You can receive files in two ways: as bytes or as an UploadFile object.
Using bytes
If you declare the type as bytes, FastAPI will read the entire file into memory. This is suitable for small files.
from typing import Annotated
from fastapi import FastAPI, File
app = FastAPI()
@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
return {"file_size": len(file)}
Using UploadFile
For larger files, use the UploadFile class from fastapi.datastructures. UploadFile has several advantages:
- It uses a "spooled" file: files up to a certain size are kept in memory, but beyond that, they are stored on disk.
- It provides metadata like
filename,size, andcontent_type. - It has an
asyncinterface for reading and writing.
from typing import Annotated
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
return {
"filename": file.filename,
"content_type": file.content_type,
"size": file.size
}
The UploadFile object includes methods like await file.read(), await file.seek(0), and await file.close().
Step 3: Combining Forms and Files
You can define multiple File and Form parameters in the same path operation. FastAPI will correctly parse the multipart/form-data request and populate each parameter.
from typing import Annotated
from fastapi import FastAPI, File, Form, UploadFile
app = FastAPI()
@app.post("/profile/")
async def update_profile(
file: Annotated[bytes, File()],
fileb: Annotated[UploadFile, File()],
notes: Annotated[str, Form()],
):
return {
"file_size": len(file),
"notes": notes,
"fileb_content_type": fileb.content_type,
}
In this example, file is read into memory as bytes, while fileb is handled as an UploadFile.
Step 4: Uploading Multiple Files
To receive multiple files in a single field, use a list of bytes or UploadFile.
from typing import Annotated
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/upload-multiple/")
async def create_upload_files(
files: Annotated[list[UploadFile], File(description="Multiple files as UploadFile")]
):
return {"filenames": [file.filename for file in files]}
When you define a list of files, the client must send multiple parts with the same field name (in this case, files).
Step 5: Grouping Form Fields with Pydantic
As your forms grow, you might want to group fields into a single object. You can use a Pydantic model with Form().
from typing import Annotated
from fastapi import FastAPI, Form
from pydantic import BaseModel
app = FastAPI()
class UserForm(BaseModel):
username: str
lastname: str
age: int | None = None
@app.post("/user/")
async def post_form(user: Annotated[UserForm, Form()]):
return user
FastAPI will extract the fields defined in UserForm (username, lastname, age) from the form data and validate them using the Pydantic model. This keeps your function signatures clean and leverages Pydantic's powerful validation.
Complete Result
By combining these techniques, you can build complex endpoints that handle diverse data types. Here is a complete example of a user registration endpoint:
from typing import Annotated
from fastapi import FastAPI, File, Form, UploadFile
from pydantic import BaseModel
app = FastAPI()
class RegistrationData(BaseModel):
email: str
marketing_consent: bool = False
@app.post("/register/")
async def register_user(
user_data: Annotated[RegistrationData, Form()],
profile_pic: UploadFile,
documents: Annotated[list[UploadFile], File()]
):
# Process the UploadFile
content = await profile_pic.read()
return {
"email": user_data.email,
"profile_pic_name": profile_pic.filename,
"document_count": len(documents)
}
This endpoint handles a Pydantic model (parsed from form fields), a single file upload, and a list of supporting documents, all within a single multipart/form-data request.