Pydantic: How to return user friendly validation error messages?

Pydantic: How to return user friendly validation error messages?
python
Ethan Jackson

Is there any way to change the validation messages from pydantic? The problem is this: I want to return these validation messages to my frontend but not all of the users prefer the language english and the validation messages are not user friendly. So my question is: Is there any way to change the pydantic validation messages?

If I continue writing like this, I'll end up creating boilerplate code, which I obviously want to avoid. And I don't see the point of using Pydantic if I'm going to perform all the validations myself just to change the message.

class RegisterModel(BaseModel): email: EmailStr password: str @field_validator("password", mode="before") def validate_password(cls, value): if not isinstance(value): raise ValueError("Password must be a string.") if len(value) < 2: raise ValueError("Password must have at least 8 characters.") if len(value) > 32: raise ValueError("Password must not have more than 32 characters.") if not re.search(r"[A-Z]", value): raise ValueError("Password must contain at least one uppercase letter.") if not re.search(r"[0-9]", value): raise ValueError("Password must contain at least one digit.") return value

Answer

A good approach for internationalization (i18n) and for maintaining a clean codebase in Pydantic v2 is:

Structured error codes like "password.too_short" or "email.invalid"

  • define messages once per locale (in a .json, .po, or dict)

  • avoid repeating logic or writing validation from scratch

  • Frontend or a different common backend service can localize messages per user language

  1. Define validators using Pydantic custom error codes

    from pydantic import BaseModel, EmailStr, field_validator from pydantic_core import PydanticCustomError class RegisterModel(BaseModel): email: EmailStr password: str @field_validator("password", mode="before") def validate_password(cls, value): if not isinstance(value, str): raise PydanticCustomError("password.not_string", "Invalid type") if len(value) < 8: raise PydanticCustomError("password.too_short", "Too short") if not any(c.isupper() for c in value): raise PydanticCustomError("password.no_uppercase", "No uppercase letter") if not any(c.isdigit() for c in value): raise PydanticCustomError("password.no_digit", "No digit")
  2. From backend, return the code { "field": "password", "code": "password.too_short" }
  3. Frontend or other backend common service maps codes to localized translated message to show user in their language

    { "password.too_short": "Password must have at least 8 characters.", "password.no_digit": "Password must include at least one number." } // fr.json { "password.too_short": "Le mot de passe doit contenir au moins 8 caractères.", "password.no_digit": "Le mot de passe doit contenir au moins un chiffre." }

In the back end : Small Optional : Create a helper to avoid repeating error raises

raise_error(code, message): raise PydanticCustomError(code, message)

Just a utility to avoid repeating this everywhere:

raise PydanticCustomError("password.too_short", "Password must be at least 8 characters.")

Just write:

raise_error("password.too_short", "Password must be at least 8 characters.")

Later extend it :

def raise_error(code: str, message: str, ctx: dict | None = None): raise PydanticCustomError(code, message, ctx or {}) # Reusable message templates with ctx: raise_error("length.too_short", "Must be at least {min} characters.", {"min": 8})

Related Articles