Form Data and File Uploads

Not all requests are JSON. HTML forms send application/x-www-form-urlencoded or multipart/form-data (when files are involved). FastAPI handles both with Form and File/UploadFile.

Receiving Form Fields

from fastapi import FastAPI, Form

app = FastAPI()

@app.post("/login/")
def login(username: str = Form(), password: str = Form()):
    return {"username": username}

Requires pip install python-multipart. Unlike query/body params, a parameter declared with Form() is read from the form body, and the docs show a form-style request body.

Single and Multiple File Uploads

from fastapi import FastAPI, UploadFile, File
from typing import List

app = FastAPI()

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    contents = await file.read()
    return {
        "filename": file.filename,
        "content_type": file.content_type,
        "size": len(contents),
    }

@app.post("/upload-multiple/")
async def upload_multiple(files: List[UploadFile]):
    return {"names": [f.filename for f in files]}

UploadFile vs bytes

Saving an Upload Safely

import shutil, uuid
from pathlib import Path

UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)

@app.post("/save-file/")
async def save_file(file: UploadFile):
    safe_name = f"{uuid.uuid4()}{Path(file.filename).suffix}"  # never trust client filename
    dest = UPLOAD_DIR / safe_name
    with dest.open("wb") as out:
        shutil.copyfileobj(file.file, out)  # sync streaming copy
    return {"saved_as": safe_name}

Security notes: never use the client-supplied filename directly (path traversal), validate content_type and size limits, and consider scanning uploads.

Mixing File + Form Fields

@app.post("/reports/")
async def upload_report(
    title: str = Form(),
    period: str = Form(),
    attachment: UploadFile | None = None,
):
    return {"title": title, "has_attachment": attachment is not None}

Quick Questions and Answers

Q: Why does the endpoint stop working with "Form data requires python-multipart"?

A: Form and file parsing are not part of FastAPI's core; install the parser with pip install python-multipart. It is included when you install fastapi[standard].

Q: How do I limit upload size?

A: Read in chunks and abort when you exceed the limit: check file.size if available, or loop await file.read(1024 * 1024) counting bytes; alternatively enforce the limit at the proxy level (Nginx client_max_body_size).

Q: Can UploadFile be used with a Pydantic model?

A: Not directly inside a JSON model — files are multipart, not JSON. Declare the UploadFile as a separate parameter next to your Form() fields (or a BaseModel with model_config = ConfigDict(arbitrary_types_allowed=True) as a dependency).

Conclusion

Form() reads urlencoded fields and UploadFile streams files with metadata. Always prefer UploadFile over raw bytes, sanitize filenames, and enforce size limits — uploads are the classic attack surface of an API.