Request Validation with Pydantic v2 in FastAPI

A
Author
16 Sep, 2026 1 min read 1.46K views

Pydantic models are the heart of FastAPI. Every field type and constraint becomes runtime validation and documentation.

Field constraints

from typing import Annotated
from pydantic import BaseModel, EmailStr, Field

class SignupRequest(BaseModel):
    email: EmailStr
    password: Annotated[str, Field(min_length=8, max_length=128)]
    name: Annotated[str, Field(min_length=1, max_length=80)]
    age: Annotated[int, Field(ge=13, le=120)] | None = None
    tags: list[str] = Field(default_factory=list, max_length=10)

Custom validators

from datetime import date
from pydantic import field_validator, model_validator

class BookingRequest(BaseModel):
    start: date
    end: date
    coupon: str | None = None

    @field_validator("coupon")
    @classmethod
    def normalise_coupon(cls, v: str | None) -> str | None:
        return v.strip().upper() if v else v

    @model_validator(mode="after")
    def check_dates(self):
        if self.end <= self.start:
            raise ValueError("end must be after start")
        return self

Separate input and output models

class UserCreate(BaseModel):
    email: EmailStr
    password: str

class UserOut(BaseModel):
    id: int
    email: EmailStr
    model_config = {"from_attributes": True}   # read from ORM objects

@app.post("/users", response_model=UserOut)
async def create_user(data: UserCreate, db: DbSession):
    user = await users.create(db, data)
    return user   # password hash is filtered out by UserOut

response_model (or the return type) guarantees you never leak fields like password_hash.

Validation errors

Invalid requests get an automatic 422 response listing each problem with its location (["body", "email"]), which front-ends can map to form fields.

Pydantic v2 changes to remember

  • .dict() became .model_dump(), .json() became .model_dump_json().
  • @validator became @field_validator; @root_validator became @model_validator.
  • class Config: orm_mode = True became model_config = {"from_attributes": True}.
Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.