mrkeyoor.com_
Thu 06 Aug 10:58 UTC
PyPIUtilsupdated 06 Aug 2026

pydantic-extra-types

pydantic-extra-types is the official overflow bin for pydantic field types that the maintainers did not want inside pydantic itself. Its own README describes it as a place for types that probably should not exist in the main library. You get around thirty ready-made annotations you would otherwise hand-roll: PhoneNumber, CountryAlpha2 and friends, PaymentCardNumber with Luhn checking and brand detection, Color, Latitude and Longitude, MacAddress, ISBN, ISIN, IBAN, ULID, UUID6 through UUID8, TimeZoneName, ISO 4217 currency codes, ISO 639 language codes, S3 paths, cron strings, MongoDB ObjectIds, and epoch timestamps. Each type validates on input, serializes to something sensible, and contributes a JSON Schema entry, so FastAPI docs and generated clients pick it up automatically. Almost all of it depends on pydantic v2 only, but the interesting types need an optional third-party package installed alongside.

Verdict

Useful, well-scoped glue that saves you from writing check-digit and ISO-registry validators, and the pydantic org name means it keeps up with pydantic core changes. Install it with the specific extras you need rather than [all], and treat it as a convenience layer over phonenumbers and pycountry rather than as a validation authority.

API stability4/5Still on 2.x since 2023 and types are added rather than changed, so upgrades are usually painless. Points come off because 2.11.2 was yanked for features merged before they were ready, and because individual types occasionally tighten their validation in a minor release, which can start rejecting data your database already contains.
Docs3/5The README is about twenty lines and points you at pyproject.toml to discover the extras. The real reference is the auto-generated API pages inside the pydantic docs site, built from docstrings; most types do carry a runnable example, but there is no overview page listing what exists, so people find types by reading the source tree.
Maintenance4/5Last push 2 August 2026, 27 open issues (29 counting PRs) against 327 stars, and it lives in the pydantic GitHub organisation with CI and coverage gates. Releases are irregular though: 2.11.1 in March 2026 followed a three-month gap, and it is clearly a lower priority for the team than pydantic itself.
Ecosystem4/5Around 16.2 million downloads a week, mostly pulled in transitively by FastAPI-adjacent stacks. Being in the pydantic org means new types land here rather than in competing packages, but the community around it is small and most types have a single contributor behind them.

Use it if

  • You are writing the same regex or check-digit validator for the fourth time: this ships tested implementations of Luhn, ISBN-10 and ISBN-13, ISIN, IBAN, and the ISO country, currency, and language registries
  • You use FastAPI and want the OpenAPI schema and the generated clients to describe a field as a phone number or a country code, not as a bare string
  • You need phone parsing that follows Google libphonenumber rules (region defaults, national vs E164 formatting, region allowlists) but you want it as a pydantic annotation rather than glue code in a validator
  • You are upgrading from pydantic v1 and your models used Color or PaymentCardNumber, which were removed from pydantic v2 core and moved here
Skip it if

Setup reality

pip install pydantic-extra-types gets you pydantic and typing-extensions and only the types with no third-party dependency: Color, Coordinate, MacAddress, ISBN, ISIN, PaymentCardNumber, DomainStr, S3Path, epoch, MongoObjectId, the resolved Path types, the DSN aliases, and UUID6 through UUID8. Everything else needs an extra, and the extra name is not always the type name: phone_numbers wants pydantic-extra-types[phonenumbers], country, currency_code, and language_code want [pycountry], SemanticVersion wants [semver], ULID wants [python-ulid], CronStr wants [cron], and the pendulum date types want [pendulum]. There is a catch-all pip install 'pydantic-extra-types[all]' if you would rather not think about it, at the cost of pulling in pycountry, phonenumbers, pendulum, pymongo, semver, pytz, tzdata, and uuid-utils. Missing extras raise a RuntimeError during import of the submodule, not a clean fallback, so add the extra to your lockfile the moment you add the import. On slim container images TimeZoneName also needs the tzdata package, because the base image often has no system zoneinfo database.

Patterns

Install only the extras the types you import needinstall-the-right-extra

pip install pydantic-extra-types                      # dependency-free types only
pip install "pydantic-extra-types[phonenumbers]"      # PhoneNumber
pip install "pydantic-extra-types[pycountry]"         # Country, Currency, Language
pip install "pydantic-extra-types[python-ulid,semver]"
pip install "pydantic-extra-types[all]"               # everything, including pymongo

The extras are listed only in pyproject.toml, and the names do not always match the module (phone_numbers needs the phonenumbers extra, cron needs cron). Importing a submodule whose extra is missing raises RuntimeError at import time, so a wrong lockfile fails at process start.

Validate and normalise a phone numberphone-number-field

from pydantic import BaseModel
from pydantic_extra_types.phone_numbers import PhoneNumber

class Contact(BaseModel):
    name: str
    phone: PhoneNumber

c = Contact(name='Alice', phone='+1 650-253-0000')
print(c.phone)   # tel:+1-650-253-0000

The default output format is RFC3966, which means a tel: prefix that surprises people expecting E164. The number must be parseable and valid, not merely digit-shaped, because phonenumbers checks it against the real numbering plan for the region.

Change phone formatting and restrict regionsphone-number-e164

from typing import Annotated, Union
import phonenumbers
from pydantic import BaseModel
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator

E164 = Annotated[Union[str, phonenumbers.PhoneNumber],
                 PhoneNumberValidator(number_format='E164', default_region='US')]

class USPhone(PhoneNumber):
    default_region_code = 'US'
    supported_regions = ['US']
    phone_format = 'NATIONAL'

class Model(BaseModel):
    phone: E164

print(Model(phone='650-253-0000').phone)  # +16502530000

Two ways to configure the same thing: subclass PhoneNumber for class-level defaults, or use the PhoneNumberValidator annotation for per-field settings. Setting default_region is what lets a national number without a country prefix parse at all.

Accept and convert ISO country codescountry-codes

from pydantic import BaseModel
from pydantic_extra_types.country import (
    CountryAlpha2, CountryAlpha3, CountryNumericCode, CountryShortName,
)

class Product(BaseModel):
    made_in: CountryAlpha2

p = Product(made_in='ES')
print(p.made_in.alpha3, p.made_in.short_name)

Each variant validates one representation and exposes the others as properties, so CountryAlpha2 can hand you the alpha-3 code and the English name. Values come from pycountry, meaning the accepted list moves when pycountry updates its ISO data snapshot.

Check a card number and read its BIN and brandpayment-card-number

from pydantic import BaseModel
from pydantic_extra_types.payment import PaymentCardNumber, PaymentCardBrand

class Payment(BaseModel):
    card: PaymentCardNumber

p = Payment(card='4000000000000002')
print(p.card.bin, p.card.last4, p.card.brand)
print(p.card.brand is PaymentCardBrand.visa)

This ran Luhn, a 12 to 19 digit length check, and prefix-based brand detection. That is all. Never log or store the full value you just validated, and do not treat a pass as any signal that the card is chargeable.

Parse colours in any common notationcolor-values

from pydantic import BaseModel
from pydantic_extra_types.color import Color

class Theme(BaseModel):
    accent: Color

t = Theme(accent='rgb(38, 200, 122)')
print(t.accent.as_hex())        # '#26c87a'
print(t.accent.as_rgb_tuple())  # (38, 200, 122)
print(t.accent.original())      # 'rgb(38, 200, 122)'

This is the pydantic v1 Color class, moved here when v2 dropped it. It accepts named CSS colours, hex in short and long form, rgb, rgba, hsl, and hsla, and original() keeps the string the user actually typed. Model dumps serialize it back to a string, not to a tuple.

Bound geographic coordinates at the type levellatitude-longitude

from pydantic import BaseModel
from pydantic_extra_types.coordinate import Coordinate, Latitude, Longitude

class Place(BaseModel):
    lat: Latitude
    lon: Longitude
    where: Coordinate

p = Place(lat=22.3, lon=70.8, where=(22.3, 70.8))
# Place(lat=95, ...) -> ValidationError, latitude out of range

Latitude clamps to -90..90 and Longitude to -180..180, which catches the single most common geo bug: passing the pair in the wrong order. Coordinate accepts a two-item tuple or a 'lat,lon' string and gives you a named pair back.

Validate an IANA timezone nametimezone-name

from pydantic import BaseModel
from pydantic_extra_types.timezone_name import TimeZoneName, TimeZoneNameSettings

class Location(BaseModel):
    city: str
    timezone: TimeZoneName

class TZNonStrict(TimeZoneName, metaclass=TimeZoneNameSettings, strict=False):
    pass

print(Location(city='New York', timezone='America/New_York').timezone)
print(TZNonStrict('america/new_york'))  # case-insensitive match

The allowed set comes from zoneinfo.available_timezones(), which is empty on slim images with no system tz database; install tzdata or every value fails. Subclass with strict=False through the metaclass if you need to accept lowercase input.

Validate ISO currency and language codescurrency-and-language

from pydantic import BaseModel
from pydantic_extra_types.currency_code import ISO4217, Currency
from pydantic_extra_types.language_code import LanguageAlpha2, LanguageName

class Invoice(BaseModel):
    total_currency: ISO4217   # every ISO 4217 code, funds included
    display_currency: Currency  # ISO 4217 minus the special non-currency codes
    locale: LanguageAlpha2

Invoice(total_currency='AED', display_currency='USD', locale='de')

ISO4217 and Currency are not the same list: Currency filters out the placeholder codes such as XXX and the fund codes. LanguageAlpha2 is the two-letter set, ISO639_3 the three-letter one, and LanguageName the English name. All four need the pycountry extra.

Accept a unix timestamp and get a datetimeepoch-timestamp

from pydantic import BaseModel
from pydantic_extra_types import epoch

class Event(BaseModel):
    at: epoch.Integer     # whole seconds
    precise: epoch.Number  # float seconds

e = Event(at=1_752_000_000, precise=1_752_000_000.25)
print(e.at)              # datetime, UTC
print(e.model_dump())    # numbers again, not ISO strings

These are seconds since the epoch, not milliseconds. Feed a JavaScript Date.now() value and the result is a date far in the future or a range error. Serialization round-trips back to a number, which is what makes it useful as a wire type.

Require a specific UUID versionuuid-version-constraint

from uuid import UUID
from pydantic import BaseModel
from pydantic_extra_types.uuid_types import UUID6, UUID7, UUID8

class Document(BaseModel):
    id: UUID7

Document(id='01930000-0000-7000-8000-000000000000')
# Document(id=UUID(int=0)) -> ValidationError: expected version 7

These are Annotated[uuid.UUID, ...] constraints, so they only check the version nibble of a value you already have. They generate nothing; use the uuid-utils package or your database if you need to mint a v7.

Validate ISBN, ISIN, and MAC address stringsidentifier-check-digits

from pydantic import BaseModel
from pydantic_extra_types.isbn import ISBN
from pydantic_extra_types.isin import ISIN
from pydantic_extra_types.mac_address import MacAddress

class Record(BaseModel):
    book: ISBN
    security: ISIN
    nic: MacAddress

r = Record(book='8537809667', security='US0378331005', nic='00:00:5e:00:53:01')
print(r.book)  # 9788537809662, normalised to ISBN-13

ISBN accepts ISBN-10 and converts to ISBN-13 on the way in, so the stored value is not always the string that was submitted. None of these three needs an extra, which makes them the cheapest reason to add the package.

Alternatives

PackageRegistryPick it when
python-stdnumPyPIYou mainly need identifier validation (IBAN, ISIN, VAT numbers, national IDs, hundreds of country-specific formats) and can call a validator function yourself instead of using an annotation.
pycountryPyPIYou only want ISO country, language, currency, and subdivision lookups, without a pydantic layer on top.
phonenumbersPyPIPhone numbers are your only case; using the library directly in a field_validator gives you access to carrier, timezone, and geocoding data these wrappers do not expose.