Cheatsheet for common domain types in Pydantic.
Branded IDs (type-safe)
from typing import Annotated, NewType
from pydantic import Field
UserId = Annotated[int, Field(description="User ID", ge=1)]
PostId = Annotated[int, Field(description="Post ID", ge=1)]
class Post(BaseModel):
id: PostId
author: UserId
Or use NewType (less ergonomic):
UserId = NewType("UserId", int)
Branding doesn’t enforce at runtime in stdlib, but Pydantic + IDE distinguishes them.
Money / Decimal
from decimal import Decimal
from pydantic import Field
class Order(BaseModel):
amount: Decimal = Field(..., gt=0, decimal_places=2, max_digits=12)
currency: str = Field(..., min_length=3, max_length=3, pattern=r"^[A-Z]{3}$")
Never use float for money.
Or store as integer cents:
amount_cents: int = Field(..., ge=0)
from pydantic import EmailStr
class User(BaseModel):
email: EmailStr
Requires email-validator package.
Phone
Pydantic doesn’t ship phone validation; use phonenumbers:
import phonenumbers
from typing import Annotated
from pydantic import AfterValidator
def validate_phone(v: str) -> str:
parsed = phonenumbers.parse(v, "US")
if not phonenumbers.is_valid_number(parsed):
raise ValueError("invalid phone")
return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
Phone = Annotated[str, AfterValidator(validate_phone)]
class User(BaseModel):
phone: Phone
URL
from pydantic import HttpUrl, AnyUrl, PostgresDsn, RedisDsn
class Config(BaseModel):
api: HttpUrl
db: PostgresDsn
cache: RedisDsn
HttpUrl enforces http(s); AnyUrl allows any.
Datetime variants
from datetime import datetime
from pydantic import AwareDatetime, NaiveDatetime, PastDatetime, FutureDatetime
class Event(BaseModel):
when: AwareDatetime # must have tzinfo
when_naive: NaiveDatetime # must NOT have tzinfo
born: PastDatetime
expires: FutureDatetime
Date / Time
from datetime import date, time, timedelta
from pydantic import PastDate, FutureDate
class M(BaseModel):
dob: PastDate
starts: FutureDate
duration: timedelta
open_at: time
UUID
from uuid import UUID, uuid4
class Doc(BaseModel):
id: UUID = Field(default_factory=uuid4)
For UUID v7 (sortable): use a library or generate manually.
IP addresses
from pydantic import IPvAnyAddress, IPvAnyInterface, IPvAnyNetwork
from ipaddress import IPv4Address, IPv6Address
class Visitor(BaseModel):
ip: IPvAnyAddress
network: IPvAnyNetwork | None = None
File / Path
from pydantic import FilePath, DirectoryPath, NewPath
class Settings(BaseModel):
config: FilePath # must exist + be a file
cache_dir: DirectoryPath # must exist + be a dir
output: NewPath # must NOT exist
Color
from pydantic_extra_types.color import Color
class Theme(BaseModel):
primary: Color # accepts "#ff0", "rgb(255,...)", named, etc.
pydantic-extra-types package; many domain types.
Country / Language codes
from pydantic_extra_types.country import CountryAlpha2, CountryAlpha3
from pydantic_extra_types.language_code import LanguageAlpha2
class User(BaseModel):
country: CountryAlpha2 # "US"
language: LanguageAlpha2 # "en"
Money (extra-types)
from pydantic_extra_types.payment import PaymentCardNumber
class Card(BaseModel):
number: PaymentCardNumber # validates Luhn
ISBN
from pydantic_extra_types.isbn import ISBN
class Book(BaseModel):
isbn: ISBN
Coordinate
from pydantic_extra_types.coordinate import Latitude, Longitude
class Place(BaseModel):
lat: Latitude # -90 to 90
lng: Longitude # -180 to 180
TimeZone
import pytz
from typing import Annotated
from pydantic import AfterValidator
def validate_tz(v: str) -> str:
pytz.timezone(v) # raises if invalid
return v
TimeZone = Annotated[str, AfterValidator(validate_tz)]
class User(BaseModel):
tz: TimeZone = "UTC"
Constrained collections
from typing import Annotated
from pydantic import Field
Tags = Annotated[list[str], Field(min_length=1, max_length=10)]
class Post(BaseModel):
tags: Tags
Common mistakes
- Storing money as float — rounding errors.
- Generic
strfor phone / URL / email — accepts garbage. - Naive datetime in timezone-sensitive APIs — bugs at DST.
- Branded IDs without TypedDict / NewType — not actually distinct at runtime.
Read this next
If you want my domain-type library (Phone, Money, etc.), it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .