Main Ecosystems
HomePython CorePandas ReferenceNumPy ScientificFastAPI & PydanticDjango Enterprise
More Ecosystems
Environment & SetupRequests & HTTPAsyncIO ConcurrencyObject-Oriented OOPPyTorch Deep LearningScikit-Learn MLFlask FrameworkWeb ScrapingDatabase & ORMDevOps & Docker

FastAPI: Handling File Upload with Form Data in Pydantic Model

Verified FixPython 3.10+FastAPI 0.110+Silo: fastapi

Quick Fix / Solution Rapide

This error occurs when Python tries to parse multipart/form-data containing both file uploads and text fields directly into a standard Pydantic BaseModel without using FastAPI Form and File parameters. Use separate Form(...) and File(...) parameters or a custom as_form dependency.

Root Cause Analysis

This error occurs when Python tries to deserialize a multipart/form-data request body containing file uploads and form fields into a standard Pydantic model in FastAPI.

Root Cause 1: Mismatch Between JSON Body and Multipart Encoding

FastAPI natively uses Pydantic to parse JSON request bodies sent with Content-Type: application/json. However, when a client uploads files alongside metadata, the browser sends the payload using Content-Type: multipart/form-data. Multipart payloads encode each field and file as a separate MIME boundary block. FastAPI relies on Starlette and the python-multipart library to decode these blocks, which requires explicit field-level bindings rather than raw JSON stream deserialization.

Root Cause 2: Placing UploadFile Inside a Pydantic BaseModel Without Form Parsers

When developers define a Pydantic BaseModel containing an UploadFile field (for example, class ItemCreate(BaseModel): name: str; file: UploadFile) and declare it as a parameter in a route function without special dependency handling, FastAPI defaults to expecting a JSON payload. Because UploadFile cannot be deserialized from raw JSON bytes, FastAPI fails during OpenAPI schema generation or request body validation, resulting in a 422 Unprocessable Entity error or an internal server exception.

Root Cause 3: Missing python-multipart Dependency

FastAPI delegates form and file parsing to python-multipart. If this library is not installed in the active virtual environment, any endpoint defining Form(...) or File(...) parameters will raise a RuntimeError at runtime stating that python-multipart is required to parse form data.

Root Cause 4: Header Inconsistencies from Frontend Clients

Frontend applications using fetch or axios frequently make the mistake of manually setting Content-Type: multipart/form-data in request headers. Setting this header manually strips the unique MIME boundary parameter generated by the browser (such as boundary=----WebKitFormBoundary...), preventing FastAPI from delimiting individual form fields and attached files.

Reproduction Code (MCVE)

Example: Bug Reproduction
from pydantic import BaseModel
from fastapi import FastAPI, UploadFile

class ItemPayload(BaseModel):
    title: str
    description: str

def parse_incoming_request(content_type: str, raw_fields: dict):
    # FastAPI cannot parse multipart/form-data directly into a raw BaseModel with file uploads
    if "multipart/form-data" in content_type:
        if "file" in raw_fields and isinstance(raw_fields["file"], bytes):
            raise ValueError(
                "FastAPI cannot parse multipart/form-data with raw UploadFile inside a Pydantic BaseModel. "
                "Use Form() and File() in endpoint signature or declare an as_form classmethod dependency."
            )

# Simulating a multipart request dispatched to an unsupported raw BaseModel endpoint
parse_incoming_request(
    content_type="multipart/form-data; boundary=----WebKitFormBoundaryX",
    raw_fields={"title": "Document", "description": "Report", "file": b"%PDF-1.4..."}
)

Solution 1: Separate Form Fields and File in Route Signature

Declare text form fields using Form(...) and file objects using File(...) or UploadFile directly in the endpoint signature. FastAPI automatically reads multipart fields and injects them as individual arguments.

Example: Recommended Solution
from fastapi import FastAPI, File, Form, UploadFile
import io

app = FastAPI()

@app.post("/items/upload")
async def upload_item(
    title: str = Form(...),
    description: str = Form(""),
    file: UploadFile = File(...)
):
    contents = await file.read()
    return {
        "title": title,
        "description": description,
        "filename": file.filename,
        "file_size": len(contents)
    }

# Standalone simulation demonstrating parameter extraction
def simulate_endpoint_call(title: str, description: str, filename: str, data: bytes):
    return {
        "title": title,
        "description": description,
        "filename": filename,
        "file_size": len(data),
        "status": "success"
    }

result = simulate_endpoint_call("Report", "Annual summary", "report.pdf", b"%PDF-1.4 content")
print(result)

Solution 2: Create an as_form Dependency for Pydantic Models

Attach an as_form classmethod dependency to your Pydantic model to parse all form fields while keeping your business logic cleanly encapsulated in Pydantic schemas.

Example: Alternative Solution
from fastapi import FastAPI, Depends, File, Form, UploadFile
from pydantic import BaseModel

class ItemFormData(BaseModel):
    title: str
    description: str

    @classmethod
    def as_form(
        cls,
        title: str = Form(...),
        description: str = Form("")
    ) -> "ItemFormData":
        return cls(title=title, description=description)

app = FastAPI()

@app.post("/items/structured")
async def create_item_with_file(
    form_data: ItemFormData = Depends(ItemFormData.as_form),
    file: UploadFile = File(...)
):
    return {
        "item": form_data.model_dump(),
        "filename": file.filename
    }

# Standalone test of the as_form model pattern
model = ItemFormData(title="Quarterly Review", description="Q3 performance")
print("Validated model:", model.model_dump())

A frequent mistake when sending multipart requests from React or Angular is manually setting the Content-Type: multipart/form-data header in Axios or Fetch headers. When manually set, the browser does not append the required boundary hash, leading to immediate 422 or 400 Bad Request responses. Always pass the FormData instance directly to Axios or fetch and let the browser set the header and boundary automatically.

Another critical edge case is forgetting to install python-multipart (pip install python-multipart). FastAPI will pass syntax and static type checks during development, but as soon as the first request reaches a Form() or File() route, the server will crash with an internal server error.

Contrast this issue with standard JSON validation errors: JSON errors occur due to data type mismatches (e.g., passing a string for an integer field), whereas form-data file upload errors occur at the transport encoding layer before Pydantic field validation is ever reached.