FastAPI Form Data Handling: Receiving multipart/form-data

In web development, we often need to handle user-submitted form data, especially when uploading files (such as images or documents), where the multipart/form-data format is typically used. This format can transfer both text data and binary file data simultaneously, making it ideal for scenarios involving mixed forms and files. The FastAPI framework provides a simple and intuitive way to receive such data, so let’s learn how to handle it step by step.

I. What is multipart/form-data?

multipart/form-data is an HTTP request format used to include multiple types of data in a form. For example, when a user uploads an image, they might also submit text information like username and age. This format splits data into multiple “parts,” where each part can be either text or binary file data.

II. Prerequisites for FastAPI to handle multipart/form-data

To receive multipart/form-data in FastAPI, import two key tools:
- Form: For receiving text data from forms.
- File (or UploadFile): For receiving file data.

While both File and UploadFile handle file uploads, UploadFile is more powerful (e.g., it can access file metadata and save files to disk), which we’ll explore in subsequent examples.

III. Handling Text Form Data

If you only need to receive text-form data (e.g., name, age), use the Form parameter directly. The usage of Form is similar to Query, but it’s specifically designed for text fields in multipart/form-data format.

Example Code:

from fastapi import FastAPI, Form

app = FastAPI()

@app.post("/submit-info")
def submit_info(
    name: str = Form(...) ,  # Required parameter, ... indicates it must be provided
    age: int = Form(...)     # Required integer parameter
):
    return {
        "message": "Form data received successfully!",
        "name": name,
        "age": age
    }

Key Points:
- Use Form(...) to mark parameters as form fields; ... indicates the parameter is required (FastAPI will throw an error if omitted).
- For optional parameters, set a default value, e.g., age: int = Form(default=18).
- FastAPI automatically parses text fields from multipart/form-data when the request is submitted.

IV. Handling File Uploads

When uploading files (e.g., images, PDFs), use File or UploadFile. File returns the file’s binary content (bytes), suitable for simple scenarios; UploadFile returns a file object, allowing access to metadata like filename, size, and save path.

1. Using File to receive binary files

If you only need the binary content of a file (e.g., directly processing image data), use File: bytes:

from fastapi import FastAPI, File, Form

app = FastAPI()

@app.post("/upload-text-file")
def upload_text_file(
    name: str = Form(...),
    file: bytes = File(None)  # Optional file, default None
):
    return {
        "name": name,
        "file_size": len(file) if file else 0,  # File size in bytes
        "file_content": f"File content (first 10 bytes): {file[:10]}" if file else "No file"
    }

Testing Method:
After starting the FastAPI service, visit http://localhost:8000/docs (Swagger UI). Locate the /upload-text-file endpoint, fill in name, click “Choose File” to upload, then submit to view the result.

2. Using UploadFile to get file metadata

UploadFile is more powerful, supporting file metadata (e.g., filename, MIME type, size) and disk storage:

from fastapi import FastAPI, UploadFile, File, Form

app = FastAPI()

@app.post("/upload-meta-file")
def upload_meta_file(
    name: str = Form(...),
    file: UploadFile = File(...)  # Required file parameter
):
    # Get file metadata
    file_info = {
        "filename": file.filename,
        "content_type": file.content_type,  # MIME type (e.g., image/png)
        "size": file.size,                  # File size in bytes
        "file_type": file.content_type.split("/")[0]  # Extract type (e.g., image)
    }

    # Save file to disk
    try:
        contents = await file.read()  # Read binary content
        with open(f"uploaded_{file.filename}", "wb") as f:
            f.write(contents)
        return {
            "message": "File uploaded successfully!",
            "file_info": file_info,
            "saved_path": f"uploaded_{file.filename}"
        }
    except Exception as e:
        return {"error": f"Failed to save file: {str(e)}"}
    finally:
        await file.close()  # Close the file to prevent resource leaks

Key Points:
- The file parameter with UploadFile is required (marked with ...).
- Access metadata via file.filename, file.content_type, and file.size.
- Use await file.read() to read binary content and with open(...) to save to disk.
- Always call await file.close() to avoid memory leaks.

V. Complete Example: Mixing Text and File

To handle both text and file data, include both Form and File (or UploadFile) parameters in the route function:

from fastapi import FastAPI, File, Form, UploadFile

app = FastAPI()

@app.post("/user-info-upload")
def user_info_upload(
    username: str = Form(...),       # Text parameter: username
    age: int = Form(default=18),     # Optional text parameter: age (default 18)
    avatar: UploadFile = File(...)   # Optional file parameter: avatar
):
    # Process text data
    user_data = {"username": username, "age": age}

    # Process file data
    file_data = {}
    if avatar:
        file_data = {
            "filename": avatar.filename,
            "content_type": avatar.content_type,
            "size": avatar.size
        }
        # Save file to disk
        with open(f"avatar_{avatar.filename}", "wb") as f:
            f.write(await avatar.read())  # Asynchronous read (use file.read() for sync)

    return {
        "user_info": user_data,
        "file_info": file_data if file_data else "No file uploaded"
    }

Note:
The read() method of UploadFile is asynchronous (async). If using in a synchronous function, use await avatar.read(). For compatibility, you can also use avatar.file.read() (the underlying file object supports synchronous reading).

VI. Testing the API

FastAPI includes built-in Swagger UI (access http://localhost:8000/docs):
1. Navigate to the /user-info-upload endpoint.
2. Fill in username (required) and age (optional, default 18).
3. Click “Choose File” to upload an image or document.
4. Click “Execute” to send the request and view the response.

VII. Summary

  • multipart/form-data: Used for mixed text and file form submissions.
  • FastAPI: Uses Form for text and File/UploadFile for files.
  • File vs. UploadFile: File is suitable for simple binary content; UploadFile handles metadata and disk storage.
  • Swagger UI: FastAPI’s built-in Swagger UI simplifies testing without additional tools.

With these examples, you can handle most scenarios involving forms and file uploads. For advanced use cases (e.g., chunked uploads or large file streaming), explore UploadFile features like save_to and file object operations.

Xiaoye