Request Form Models
Part of: FastAPI Basics
Declare form fields as Pydantic models instead of individual Form() parameters for cleaner form handling.
What You'll Learn
- Group form fields into Pydantic models
- Understand the difference between Form models and JSON Body models
- Apply validation to form data with model constraints
- Handle complex forms with many fields cleanly
Theory and Concepts
Request Form Models
What You'll Learn
HTML forms send data as application/x-www-form-urlencoded or multipart/form-data, not JSON. When a form has many fields, declaring each one with Form() individually leads to bloated function signatures. FastAPI 0.115+ lets you group form fields into a Pydantic model.
By the end of this lesson, you'll understand how to:
- Group form fields into Pydantic models
- Understand the difference between Form models and JSON Body models
- Apply validation to form data with model constraints
- Handle complex forms with many fields cleanly
Form Data vs JSON
When a client sends data, the format matters:
| Format | Content-Type | Used By |
|--------|-------------|---------|
| JSON | application/json | JavaScript fetch, API clients |
| Form | application/x-www-form-urlencoded | HTML <form> elements |
| Multipart | multipart/form-data | File uploads with forms |
FastAPI uses the annotation to decide how to parse the incoming data:
- Body() or no annotation on a Pydantic model -> expects JSON
- Form() -> expects form-encoded data
Individual Form Parameters
Without form models, you declare each form field separately:
[Code Example]
For a registration form with four fields this is manageable, but real-world forms can have ten or more fields -- billing address, shipping address, payment details, preferences, and so on.
Form Parameter Models
With FastAPI 0.115+, you can group form fields into a Pydantic model:
[Code Example]
The Form() annotation is what tells FastAPI to read the model fields from form-encoded data instead of a JSON body.
Key Concepts
The Form() Annotation Is Critical
Without the Form() annotation, FastAPI treats a Pydantic model parameter as a JSON body:
[Code Example]
An HTML form submitting to the first endpoint would fail because the data arrives as application/x-www-form-urlencoded but FastAPI expects application/json.
Validation
Because the model is a standard Pydantic model, all validation features work:
[Code Example]
If a user submits a username shorter than 3 characters or a password shorter than 8, FastAPI returns a 422 validation error with details about which field failed.
Required vs Optional Fields
Fields without default values are required. If the form submission omits them, FastAPI returns a 422 error:
[Code Example]
Forbidding Extra Fields
You can reject form submissions that include unexpected fields:
[Code Example]
A submission with an extra field like role=admin would be rejected, preventing clients from injecting unexpected data.
Accessing Individual Fields
Once FastAPI parses the form data into the model, you access fields as normal attributes:
[Code Example]
You can also convert the entire model to a dictionary with form_data.model_dump().
Comparison with Body Models
| Feature | Body Model | Form Model |
|---------|-----------|------------|
| Annotation | None or Body() | Form() |
| Content-Type | application/json | application/x-www-form-urlencoded |
| Sent by | API clients, fetch | HTML forms |
| Nested objects | Supported | Not supported |
| File uploads | Not supported | Supported (with multipart/form-data) |
Form data is flat -- it does not support nested objects the way JSON does. If you need to receive deeply nested structures, use a JSON body instead.
Real-World Example
A complete checkout form model:
[Code Example]
Without form models, this endpoint would need 12 individual Form() parameters.
Best Practices
1. Always use the Form() annotation when receiving form data -- omitting it causes FastAPI to expect JSON
2. Add validation with Field() to catch bad data early (min lengths, patterns, ranges)
3. Use extra="forbid" to prevent clients from injecting unexpected form fields
4. Keep sensitive fields out of responses -- never return passwords or credit card numbers
5. Reuse form models across endpoints that handle the same form structure
Common Mistakes
Forgetting Form() annotation
[Code Example]
Trying to nest models in forms
[Code Example]
What's Next?
You now know how to group form fields into Pydantic models. This completes the parameter model series covering query parameters, cookies, headers, and form data. Each uses the same pattern: define a Pydantic model and annotate with the appropriate source (Query(), Cookie(), Header(), or Form()).
Helpful Hint
Create a Pydantic model with your form fields and annotate the parameter with Form().
