Introduction
FastAPI is a modern web framework for building high-performance APIs in Python. It’s built for speed, ease of use, and developer productivity, making it an ideal choice for developing efficient and reliable backend systems. FastAPI is not only fast but also supports asynchronous programming and automatic interactive API documentation via Swagger UI.
Handling forms and file uploads is a common requirement in many APIs, especially in applications like resume builders, image galleries, and document submission platforms. These features allow users to send structured data (e.g., names, descriptions) alongside files (like PDFs or images), which the server must validate, store, and possibly process.
In this guide, we’ll walk through how to handle both form data and file uploads in FastAPI. You’ll see practical examples, learn best practices, and understand how to structure your endpoints to accept and process form data requests correctly.
Handling Regular Forms (Form Data)
Understanding Request Payloads: JSON vs. Form Data
When building APIs, it’s important to understand the difference between sending data as a JSON payload versus using form data (application/x-www-form-urlencoded or multipart/form-data)
JSON Payload
Sent with the header:
Content-Type: application/json.
Commonly used for structured data like nested objects or arrays.
In FastAPI, you receive this using Pydantic models directly.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
email: str
@app.post("/json-example")
async def receive_json(user: User):
return {"message": f"Hello {user.name}"}
Form Data
Form data refers to the information sent to the server when a user submits an HTML form. In FastAPI, you receive this data using the Form()
dependency. The data is sent with the header: Content-Type: application/x-www-form-urlencoded
(or multipart/form-data
for file uploads). The header tells the server how the data is formatted.
application/x-www-form-urlencoded
is the default for HTML forms without file uploads as it send the form as key-value pairs.multipart/form-data
is used when the form includes files like PDFs, images or documents. The form data is split into parts - each part has its own headers (e.g., filename, content type)
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/form-example")
async def receive_form(name: str = Form(...), email: str = Form(...)):
return {"message": f"Hello {name}"}
When Should You Use Form()
in FastAPI?
By default, FastAPI expects request data to come in as JSON, but when submitting data using an HTML form (e.g., using the <form>
tag) the content isn’t JSON, and FastAPI won’t parse the data correctly unless you use the Form()
dependency for text fields.
Form()
is required when:
The client (usually a web form) is sending data using
application/x-www-form-urlencoded.
You need to mix text fields with file uploads (i.e.,
multipart/form-data)
, since FastAPI requires form fields to be explicitly marked withForm()
when used withUploadFile.
Example mixing file and form:
from fastapi import FastAPI, Form, File, UploadFile
app = FastAPI()
@app.post("/upload-resume")
async def upload_resume(
name: str = Form(...),
email: str = Form(...),
file: UploadFile = File(...)
):
return {"filename": file.filename, "submitted_by": name}
Handling File Uploads
File uploads are a common need in modern web applications, whether users are submitting resumes, profile pictures, scanned documents, or media files. FastAPI makes handling file uploads simple and efficient.
How to Receive Files in FastAPI
To receive uploaded files, FastAPI provides two built-in tools:
File()
– This is used to declare a file input.UploadFile
– This is a class that gives you access to the uploaded file without loading it entirely into memory.
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
contents = await file.read()
return {
"filename": file.filename,
"content_type": file.content_type,
"size": len(contents)
}
Combining Form Fields and File Uploads
from fastapi import FastAPI, Form, UploadFile, File
@app.post("/submit-resume/")
async def submit_resume(
full_name: str = Form(...),
email: str = Form(...),
resume: UploadFile = File(...)
):
return {
"name": full_name,
"email": email,
"filename": resume.filename
}
Common Errors and Fixes
When working with form data and file uploads in FastAPI, a few common mistakes can cause your API to break. Here’s how to identify and fix them:
Missing Form()
or File()
Symptom: FastAPI returns a 422 Unprocessable Entity
error.
Cause: FastAPI expects parameters like form fields or files to be explicitly declared using Form()
and File().
If you leave them as plain function arguments, FastAPI assumes they should come from JSON or the query string.
Fix:
from fastapi import Form, File, UploadFile
@app.post("/submit")
async def submit_form(
name: str = Form(...),
resume: UploadFile = File(...)
):
Wrong Content-Type (e.g., sending JSON instead of Form Data)
Symptom: FastAPI doesn’t receive your data or shows validation errors.
Cause: If your frontend sends data as application/json,
but the backend expects multipart/form-data
(for files) or application/x-www-form-urlencoded
(for regular forms), it will fail to parse the request.
Fix: Make sure your frontend sends the right content type:
For forms: Use
application/x-www-form-urlencoded
ormultipart/form-data
For file uploads: Always use
multipart/form-data
Example using FormData
in JavaScript:
const form = new FormData();
form.append("name", "Jane Doe");
form.append("resume", file); // file is a File object
fetch("/submit", {
method: "POST",
body: form,
});
Pro-Tip:
Always match how your frontend sends data with how FastAPI expects to receive it. If you’re using Form()
or File()
in the backend, use FormData
on the frontend.
Real-World Application: Resume Builder
To bring these concepts to life, I built a Resume Builder using FastAPI on the backend and React on the frontend. This project combines form handling and file uploads in a practical way.
In the form, users fill out key sections like:
Personal information (name, email, phone)
Skills and professional summary
Education and work experience (with dynamic fields)
Volunteer experience and certifications
In addition to this structured data, users can upload their professional image.
To handle both parts, structured data and the file, I used a FormData
object on the frontend. Here's how it worked:
const form = new FormData();
form.append("resume", JSON.stringify(formData)); // all form fields
form.append("file", fileInput); //image file
Note: Because FormData
cannot send nested objects directly, we serialize the entire form data as a JSON string on the frontend. On the backend, we deserialize it using json.loads().
This allowed me to send the complete resume data and the attached file in one request using multipart/form-data.
Processing and Storing the File
On the FastAPI backend, I used Form()
and UploadFile
to receive both parts of the request:
from fastapi import FastAPI, File, Form, UploadFile
@app.post("/resume")
async def create_resume(
resume: str = Form(...), # JSON stringified form data
file: UploadFile = File(...)
):
resume_data = json.loads(resume)
# Save resume info to the database
resume_id = await save_resume_data(resume_data)
# Optionally save the file
if file:
file_location = f"uploads/{file.filename}"
with open(file_location, "wb") as f:
f.write(await file.read())
return {"id": resume_id, "message": "Resume saved successfully"}
Resume Preview Before Download
Another great user experience I added was previewing the resume before submission or download. For images, I used FileReader
in React to show a preview. For PDFs, I displayed the file name and allowed users to confirm before submission.
Conclusion
The resume builder demonstrates how to handle mixed data types in APIs: validating complex nested form inputs alongside file uploads, a common requirement in many real-world applications like job portals, education platforms, and onboarding systems.
References
Github Repo: Resume Builder
FastAPI: Form Data
FastAPI: Files
FastAPI: Request Form and Files
MDN Web Docs: Files