Overview
Every FastAPI request body, response body, and parameter set passes through Pydantic. Model design decides whether internal state leaks to clients, whether clients can set server-owned fields, and whether validation runs redundantly. FastAPI 0.142 requires Pydantic 2.9 or later, and this page assumes the v2 API. For the framework baseline see fastapi; for typing conventions see python-typing.
Use separate models for input and output
The model that accepts input is not the model that returns output.
from datetime import datetime
from pydantic import BaseModel, ConfigDict, EmailStr
class UserCreate(BaseModel):
email: EmailStr
password: str
class UserRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
email: EmailStr
created_at: datetimeInput models accept exactly what the caller may set; id, created_at, and role flags belong on neither input. Output models expose exactly what the caller may see; password_hash and internal status appear on no model. from_attributes=True lets an output model be built from an ORM instance. EmailStr needs the email-validator package, which fastapi[standard] installs.
Validate once, at the HTTP boundary
Pydantic runs when FastAPI parses the body. Do not revalidate the same data in the service, repository, and database layers; pass typed model instances or dataclasses between layers.
@router.post("/users", status_code=201)
async def create_user(
payload: UserCreate, db: Annotated[AsyncSession, Depends(get_db)]
) -> UserRead:
user = await user_service.create(db, payload) # payload is already validated
return UserRead.model_validate(user)The return annotation doubles as the response model. Set response_model=UserRead explicitly when the function returns an ORM object or dict and you annotate something else; see fastapi-openapi.
Prefer Field constraints; use validators for rules constraints cannot express
Declarative constraints appear in the OpenAPI schema and need no code. Validators do not.
from pydantic import BaseModel, Field, field_validator, model_validator
class OrderCreate(BaseModel):
quantity: int = Field(gt=0)
unit_price: float = Field(gt=0)
coupon: str | None = None
@field_validator("coupon")
@classmethod
def coupon_is_uppercase(cls, v: str | None) -> str | None:
if v is not None and v != v.upper():
raise ValueError("coupon must be uppercase")
return v
@model_validator(mode="after")
def total_under_limit(self):
if self.quantity * self.unit_price > 10_000:
raise ValueError("order total exceeds limit")
return self@field_validator runs after coercion to the annotated type, so never check isinstance on an annotated field. Use @model_validator(mode="after") for rules that span fields.
Name aliases for the wire format, attributes for Python
Use an alias generator for camelCase clients and keep snake_case attributes.
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
class InvoiceRead(BaseModel):
model_config = ConfigDict(
alias_generator=to_camel,
validate_by_name=True,
validate_by_alias=True,
)
invoice_id: str
line_item_count: intFrom Pydantic 2.11, set validate_by_name=True with validate_by_alias=True instead of populate_by_name, which will be deprecated in Pydantic 3. FastAPI serializes responses by alias by default. Pass by_alias=True when you call model_dump() yourself.
Compose nested models
Model nested structures as classes, not dict or Any, so the OpenAPI schema renders them and clients are typed. Split a model that grows past about ten fields into sub-models that each capture one concept.
class Address(BaseModel):
street: str
city: str
postal_code: str
class CustomerCreate(BaseModel):
name: str
email: EmailStr
billing_address: Address
shipping_address: Address | None = NoneParse raw JSON with model_validate_json
For queue messages, files, and webhook bodies that FastAPI does not parse, call Model.model_validate_json(raw_bytes). It parses and validates in one pass, which is faster than model_validate(json.loads(raw)). Model.model_json_schema() exports the schema for contract tests without starting the app. For webhook handlers, verify the signature on the raw bytes first; see webhooks.