Field and model validators

Pydantic · Practical · 14 min

Rules that span fields or need code, like normalizing input or checking that end > start, live in validators.

When constraints are not enough, write a validator.

@field_validator("name") on a classmethod validates one or more fields. The mode decides when it runs:

  • mode="after" (the default) runs after Pydantic's own validation, so you receive a value of the right type. Most validators should be after validators.
  • mode="before" runs on the raw input, before type conversion. Use it to reshape input, for example splitting "a,b" into a list.
  • mode="wrap" receives a handler you call to run the inner validation, so you can act before and after or catch its errors.
  • mode="plain" replaces Pydantic's validation for that field entirely.

The validator must return the value to keep; forgetting the return sets the field to None. Raise ValueError to reject; the message lands in the error list.

@model_validator(mode="after") is an instance method that sees the fully built model, the right place for cross-field rules such as end >= start; it must return self. A mode="before" model validator is a classmethod receiving the raw data, often a dict but possibly anything.

info: ValidationInfo as a second parameter gives access to info.data, the fields validated so far (in definition order), and the validation context passed via model_validate(data, context=...).

Validators do not run on defaults unless you set validate_default=True on the field, and they do not run on attribute assignment unless validate_assignment=True is configured.

Example

from datetime import date
from pydantic import BaseModel, ValidationError, field_validator, model_validator

class Booking(BaseModel):
    tags: list[str] = []
    start: date
    end: date

    @field_validator("tags", mode="before")
    @classmethod
    def split_csv(cls, v):
        return v.split(",") if isinstance(v, str) else v

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

b = Booking(tags="vip,late", start="2025-01-01", end="2025-01-03")
print(b.tags)                              # ['vip', 'late']
try:
    Booking(start="2025-01-05", end="2025-01-01")
except ValidationError as e:
    print(e.errors()[0]["msg"])            # Value error, end must not be before start

Remember

  • after validators get typed values; before validators get raw input
  • Always return the value (field) or self (model after-validator)
  • Use a model validator for rules that involve several fields

Watch out for

  • Forgetting the return statement, which silently sets the field to None.
  • Expecting validators to run on defaults or on assignment without enabling it.

Check yourself

  1. You need to accept "a,b,c" for a list[str] field. Which validator mode fits?
  2. Where should a check that end_date is after start_date go?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.pydantic.dev/latest/concepts/validators/