FastAPI Form Data and File Uploads
Not all data arrives as JSON. HTML forms and file upload inputs send data in a different format called multipart or URL-encoded form data. FastAPI handles both with dedicated tools from the fastapi package.
JSON vs Form Data — The Key Difference
JSON request (typical API call):
Content-Type: application/json
Body: {"username": "priya", "password": "secret"}
Form request (HTML form submit):
Content-Type: application/x-www-form-urlencoded
Body: username=priya&password=secret
Browsers submit HTML forms as form data, not JSON. If you build a login page or registration form with a traditional HTML frontend, your FastAPI backend must accept form data.
Install the Required Package
pip install python-multipart
This library parses multipart and URL-encoded form bodies. FastAPI requires it for both form fields and file uploads.
Receiving Form Fields
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login")
def login(username: str = Form(), password: str = Form()):
return {"username": username, "logged_in": True}
HTML form that sends to this route: <form method="POST" action="/login"> <input name="username" /> <input name="password" type="password" /> <button type="submit">Login</button> </form>
Each Form() argument tells FastAPI to read that field from the form body instead of from JSON.
Receiving a Single File Upload
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/upload")
async def upload_file(file: UploadFile):
content = await file.read()
return {
"filename": file.filename,
"size_bytes": len(content),
"content_type": file.content_type
}
UploadFile gives you: file.filename → "resume.pdf" file.content_type → "application/pdf" file.read() → actual bytes (use await) file.size → size in bytes
UploadFile vs File(bytes)
File(bytes): Reads entire file into memory at once. Simple but risky for large files. UploadFile: Streams the file. Safe for large files. Gives you metadata (name, type). Recommended in almost all cases.
Uploading Multiple Files
from typing import List
@app.post("/upload-many")
async def upload_many(files: List[UploadFile]):
results = []
for file in files:
content = await file.read()
results.append({"filename": file.filename, "size": len(content)})
return results
Combining Form Fields and a File in One Request
You cannot mix JSON body with form data in one request — but you can mix form fields with file uploads:
@app.post("/profile")
async def create_profile(
name: str = Form(),
bio: str = Form(),
avatar: UploadFile = File()
):
image_data = await avatar.read()
return {
"name": name,
"bio": bio,
"avatar_filename": avatar.filename,
"avatar_size": len(image_data)
}
Request format: multipart/form-data Parts: name = "Anjali" ← text field bio = "Developer" ← text field avatar = [file bytes] ← file field
Saving an Uploaded File to Disk
import shutil
from pathlib import Path
@app.post("/save-file")
async def save_file(file: UploadFile):
save_path = Path("uploads") / file.filename
save_path.parent.mkdir(parents=True, exist_ok=True)
with open(save_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
return {"saved_as": str(save_path)}
Key Points
- Install
python-multipartto use form data and file uploads. - Use
Form()to read individual form fields. - Use
UploadFileinstead of raw bytes for file uploads — it streams content safely. - Combine
Form()andFile()in one endpoint to accept both text fields and files. - Use
List[UploadFile]to accept multiple files in a single request.
