5.9 Forms
Yes, you can use Form() instead of Field() if you want FastAPI to expect the values as form-data instead of JSON in the request body.
The difference is:
Field(...)→ For JSON body parameters in Pydantic models.Form(...)→ For form submissions (application/x-www-form-urlencodedormultipart/form-data).
Here’s how it would look with Form and descriptions:
from fastapi import Formfrom pydantic import BaseModel, EmailStr
class UserBase(BaseModel): email: EmailStr full_name: str
@classmethod def as_form( cls, email: EmailStr = Form(..., description="The user's email address (must be valid).", examples=["alice@example.com", "chathurdara@gmail.com"]), full_name: str = Form(..., description="The user's full name.", examples=["Alice Wonderland", "Nadith Pathirage"]) ): return cls(email=email, full_name=full_name)Then in your endpoint:
from fastapi import FastAPI, Depends
app = FastAPI()
@app.post("/users")def create_user(user: UserBase = Depends(UserBase.as_form)): return userKey points:
- You can pass
descriptionandexamplestoForm()just likeField(). Form()is not a Pydantic feature — it’s a FastAPI dependency injection feature.- If you switch to
Form(), Swagger will show the parameters as form-data instead of a JSON body.
If you want, I can show you a cleaner way without the as_form boilerplate, so you don’t have to manually define that method. That’s a common trick in FastAPI form handling.
=========================
Alright — the cleaner way is to skip manually writing an as_form method and instead use Form() directly in your endpoint function parameters.
Example:
from fastapi import FastAPI, Formfrom pydantic import EmailStr
app = FastAPI()
@app.post("/users")def create_user( email: EmailStr = Form(..., description="The user's email address (must be valid).", examples=["alice@example.com", "chathurdara@gmail.com"]), full_name: str = Form(..., description="The user's full name.", examples=["Alice Wonderland", "Nadith Pathirage"])): return {"email": email, "full_name": full_name}What happens here:
Form(...)tells FastAPI to read that value from form-data.descriptionandexamplesstill work in Swagger docs.- You avoid creating a separate Pydantic model just for forms.
💡 When to still use the as_form trick
- If you want the same Pydantic model to work for both JSON and form-data without rewriting all fields in the route.
- Keeps validation in one place.
If you want, I can show you a decorator that automatically adds .as_form() to all your Pydantic models so you never write boilerplate again. That’s what I usually use for larger APIs.
=========================
Gotcha — here’s the decorator version so your Pydantic model can be used with both JSON and form-data without writing the as_form method every time.
from fastapi import Formfrom pydantic import BaseModelfrom typing import Type
def as_form(cls: Type[BaseModel]): """ Adds an .as_form class method to Pydantic models so they can be used with Form(). """ new_params = [] for field_name, model_field in cls.__fields__.items(): default = Form( ... if model_field.required else model_field.default, description=model_field.field_info.description, examples=model_field.field_info.extra.get("examples") ) new_params.append((field_name, default))
# Create the method dynamically def _as_form(**data): return cls(**data)
# Add default values to the signature from inspect import signature, Parameter sig = signature(_as_form) params = [ Parameter( name=name, kind=Parameter.POSITIONAL_OR_KEYWORD, default=default ) for name, default in new_params ] _as_form.__signature__ = sig.replace(parameters=params) # type: ignore
setattr(cls, "as_form", classmethod(_as_form)) return clsExample usage
Section titled “Example usage”from pydantic import BaseModel, EmailStr, Fieldfrom fastapi import FastAPI, Depends
app = FastAPI()
@as_formclass UserBase(BaseModel): email: EmailStr = Field( ..., description="The user's email address (must be valid).", examples=["alice@example.com", "chathurdara@gmail.com"] ) full_name: str = Field( ..., description="The user's full name.", examples=["Alice Wonderland", "Nadith Pathirage"] )
@app.post("/users")def create_user(user: UserBase = Depends(UserBase.as_form)): return userWhat this does
Section titled “What this does”- You still write your model once using
Field()for descriptions & examples. @as_formautomatically adds.as_form()so FastAPI can use it withForm().- Swagger will show form-data fields with your descriptions & examples.
If you want, I can trim this decorator down so it’s just 10 lines instead of this verbose version — easier to maintain in your codebase. Would you like me to do that?
=============================
The whole reason people use an as_form method in FastAPI is because of how FastAPI treats request bodies.
Here’s the problem in short:
- Pydantic models (
BaseModel) → By default, FastAPI expects them as JSON in the body. - Form data (
Form()) → FastAPI expects each field to be declared as a function parameter withForm(...).
If you try to just replace Field() with Form() inside your Pydantic model, it won’t work — Form() is not part of Pydantic, it’s a FastAPI dependency marker.
Why as_form fixes it
Section titled “Why as_form fixes it”as_form is simply a helper to make your model usable for form-data without rewriting all fields in your route function.
It does two things:
- Defines a class method (e.g.,
UserBase.as_form) that declares each model field as a FastAPIForm(...)parameter. - Lets you keep all your validation, descriptions, examples inside the Pydantic model instead of duplicating them in the route.
Without as_form
Section titled “Without as_form”@app.post("/users")def create_user( email: EmailStr = Form(..., description="The user's email address"), full_name: str = Form(..., description="The user's full name")): return {"email": email, "full_name": full_name}❌ You have to repeat every field & validation here.
With as_form
Section titled “With as_form”@app.post("/users")def create_user(user: UserBase = Depends(UserBase.as_form)): return user✅ You define fields once in the model and reuse them for form-data.
Basically:
as_form= “Make this model work withForm()without rewriting everything.”- It’s just syntactic sugar — FastAPI doesn’t require it, but it saves a lot of code in bigger APIs.
If you want, I can show you exactly how FastAPI internally reads form-data with models, so as_form will make even more sense. That would explain why you can’t just Field(..., Form) inside a model.
==========================================
Right — that’s because once you switch to Form(...) (or File(...)) in FastAPI, you’re no longer dealing with a JSON request body, so Swagger can’t render the nice JSON schema like it does for application/json.
FastAPI + OpenAPI work like this:
- Model in JSON body → OpenAPI shows a full JSON schema in Swagger.
- Form parameters → OpenAPI treats each field as an individual parameter in
application/x-www-form-urlencodedormultipart/form-data. Swagger shows them as separate inputs, not as one JSON object.
That’s why your JobBase.as_form example only shows a list of fields in Swagger, not a nested JSON example.
Why it happens
Section titled “Why it happens”OpenAPI does not support showing a nested JSON schema for application/x-www-form-urlencoded bodies the same way it does for application/json. It sees them as flat parameters.
So in Swagger:
Body(..., example=...)→ JSON schema with example.Form(...)→ Table of form fields with types & descriptions.
What you can do
Section titled “What you can do”If you really want Swagger to still show the JSON schema but also accept form-data, you have three main options:
- Accept JSON in Swagger, but parse form-data in code (Not great if you actually expect form uploads.)
- Use
Body()for JSON schema and separate file uploads withForm()/File(). - Write a custom OpenAPI schema override to fake the nested form-data object in Swagger (works visually but is misleading because the request type is still form-data).
If you tell me which one you want —
- keep JSON schema look in Swagger,
- or keep real form-data but just improve how Swagger displays it — I can show you the exact code to do that.
Which way do you want to go?
https://fastapi.tiangolo.com/tutorial/schema-extra-example/#openapi-examples-in-the-docs-ui