Migrate @validator to @field_validator in Pydantic v2
Key takeaways:
- Rename
@validatorto@field_validatorand add@classmethodbeneath it. pre=Truebecomesmode="before"; the default ismode="after".- Reading other fields moves from the
valuesdict to amodel_validator. - Multiple field names still pass positionally to
field_validator. - Verify with the contract suite that behavior is unchanged.
This is a focused slice of the Pydantic V2 Migration Guide, and it builds on Custom Validators and Field Constraints.
The Problem This Solves
The validator decorator is one of the most-used Pydantic features, so migrating it correctly is most of the work in a v1-to-v2 move. The renames are simple, but two semantic changes — the classmethod requirement and the loss of the values dict — trip people up.
Prerequisites
- Pydantic v2 installed.
- A contract test suite pinned before changes (see the non-breaking migration guide).
Step-by-Step Implementation
1. Single-field validator
# Pydantic v1
from pydantic import BaseModel, validator
class V1(BaseModel):
name: str
@validator("name")
def strip(cls, v):
return v.strip()
# Pydantic v2
from pydantic import BaseModel, field_validator
class V2(BaseModel):
name: str
@field_validator("name")
@classmethod # Required in v2.
def strip(cls, v: str) -> str:
return v.strip()
2. pre=True → mode="before"
@field_validator("amount", mode="before") # was @validator("amount", pre=True)
@classmethod
def coerce(cls, v: object) -> object:
return int(v) if isinstance(v, str) else v
3. Cross-field logic → model_validator
from pydantic import model_validator
class DateRange(BaseModel):
start: int
end: int
@model_validator(mode="after") # Replaces a v1 validator reading `values`.
def check_order(self) -> "DateRange":
if self.end <= self.start:
raise ValueError("end must be after start")
return self
Edge Cases and Gotchas
- Missing
@classmethod. The most common error; it raises at import. always=True. Default validation changed; usevalidate_default=Truewhere you relied on it.- Reusable validators. Consider moving recurring rules to
Annotatedtypes, per Creating Reusable Custom Validators.
Verification
import pytest
from pydantic import ValidationError
def test_behaviour_unchanged():
assert V2(name=" ada ").name == "ada"
with pytest.raises(ValidationError):
DateRange(start=5, end=1)
Related Reading
- Up to the topic: Pydantic V2 Migration Guide.
- Related guides: model_config vs class Config and Custom Validators and Field Constraints.