A client sends an extra field is_admin: true in the registration body, and Pydantic silently drops it by default — the account is still created successfully, and you never know strange data slipped in. The fix is to declare model_config = ConfigDict(extra="forbid", str_strip_whitespace=True, frozen=True) on the model: unknown fields get a 422, strings have their extra whitespace trimmed, and the model is locked after creation. Use this for every request model that takes data from clients, especially registration forms, order creation, and profile updates.
The Problem
Pydantic v2 defaults to extra="ignore": every undeclared field is dropped in silence. A client that mistypes a field name — usernmae instead of username, for example — gets no error, the data flows into the database missing a field, and you only find out when users complain. Worse, a strange field like is_admin sent with a registration body is only dropped at the validation layer — if the code behind it reads request.json() directly, the field is still there.
Two other annoyances are often overlooked. Strings from forms usually carry extra whitespace at both ends, like " Linh ", and get saved to the database as-is — later, searching or comparing emails will be off. And Pydantic models let you reassign attributes after creation by default, so an object that has already been validated can still be modified before saving, making the final data different from the data you checked.
from pydantic import BaseModelclass UserCreate(BaseModel): name: str email: struser = UserCreate(name=" Linh ", email="linh@example.com", is_admin=True)user.model_dump()# {'name': ' Linh ', 'email': 'linh@example.com'} — is_admin biến mất, không một cảnh báo
The Solution
Declare model_config with ConfigDict right inside the model. The three options below handle all three problems at once, and FastAPI turns a ValidationError into a 422 automatically, so you don’t need extra error-handling code:
from pydantic import BaseModel, ConfigDictclass UserCreate(BaseModel): model_config = ConfigDict( extra="forbid", str_strip_whitespace=True, frozen=True, ) name: str email: str
extra="forbid" rejects every undeclared field immediately with an extra_forbidden error — a client that mistypes a field name sees a 422 and fixes it right away. str_strip_whitespace=True trims whitespace at both ends of every str field, so " Linh " becomes "Linh" before it touches the database. frozen=True locks the model after creation: reassigning an attribute raises a frozen_instance error, and the model can also be used as a dict key or a set element because it is hashable.
These three behaviors were verified on Pydantic 2.14.3: unknown fields are blocked, strings are trimmed, and reassigning an attribute raises an error.
Apply It to Your Project
Don’t repeat ConfigDict in every model — create one shared base model and have every request model inherit from it, because config is inherited by child models. Only turn on frozen=True for models that really need locking, but turn on extra="forbid" and str_strip_whitespace=True for all request models. Note: nested models use their own config, so they also need to inherit the base model if you want them to block unknown fields and trim whitespace.
class StrictModel(BaseModel): model_config = ConfigDict( extra="forbid", str_strip_whitespace=True, )class UserCreate(StrictModel): name: str email: str
Related Posts
- FastAPI: validate dữ liệu đầu vào chuẩn chỉnh bằng Pydantic Field và validator tùy chỉnh
- FastAPI: Derive Fields and Cross-Validate with computed_field and model_validator in Pydantic v2
- FastAPI: Bundle a Dozen Query Params into One Pydantic Model — Centralized Validation in 5 Minutes
FAQ
How is extra=”forbid” different from extra=”ignore” and extra=”allow”?
extra="ignore" is the default: unknown fields are dropped silently. extra="allow" keeps unknown fields in the model. extra="forbid" rejects them outright and returns a validation error — use it for every model that takes data from a client.
Does str_strip_whitespace apply to nested models?
No. Each model uses its own config, so a nested model doesn’t automatically inherit whitespace trimming. Having nested models inherit the same base model is the tidiest way.
Does frozen=True make a model hashable?
Yes. A frozen model can be used as a dict key or a set element, which is handy when you need to cache or deduplicate by object.
Should I turn on extra=”forbid” for every model?
No. Only for request models that take data from clients. For models reading data from webhooks or external APIs — where new fields can appear at any time — extra="ignore" is safer.
💬 Have you tried this in your own project? If you hit an error or know a shorter way, leave a comment below — I read them all and will add the good ideas to the post.
References
- Pydantic documentation — Configuration: https://docs.pydantic.dev/latest/concepts/config/
- FastAPI documentation — Request Body: https://fastapi.tiangolo.com/tutorial/body/
