Skip to content

Pydantic v2 integration

TypeID ships with an optional Pydantic v2 adapter. It lets you use TypeID in Pydantic models without pulling Pydantic into the TypeID core.

The adapter:

  • validates values using the TypeID core,
  • optionally enforces a fixed prefix,
  • serializes TypeIDs as strings,
  • exposes sensible JSON Schema metadata.

Basic usage

Use TypeIDField with a fixed prefix.

from typing import Literal
from pydantic import BaseModel
from typeid.integrations.pydantic import TypeIDField

class User(BaseModel):
    id: TypeIDField[Literal["user"]]

u = User(id="user_01ke82dtesfn9bjcrzyzz54ya9")
assert str(u.id) == "user_01ke82dtesfn9bjcrzyzz54ya9"

Accepted inputs

You can pass either a string or a TypeID instance.

from typing import Literal
from pydantic import BaseModel
from typeid.integrations.pydantic import TypeIDField

class User(BaseModel):
    id: TypeIDField[Literal["user"]]

u = User(id="user_01ke82dtesfn9bjcrzyzz54ya9")
assert u.id is not None
from typing import Literal
from pydantic import BaseModel
from typeid import TypeID
from typeid.integrations.pydantic import TypeIDField

class User(BaseModel):
    id: TypeIDField[Literal["user"]]

tid = TypeID.from_string("user_01ke82dtesfn9bjcrzyzz54ya9")
u = User(id=tid)

assert u.id == tid

In both cases, id is stored as a TypeID object inside the model.

Prefix validation

The prefix in TypeIDField[...] is enforced.

import pytest
from typing import Literal
from pydantic import BaseModel, ValidationError
from typeid.integrations.pydantic import TypeIDField

class Order(BaseModel):
    id: TypeIDField[Literal["order"]]

with pytest.raises(ValidationError):
    Order(id="user_01ke82dtesfn9bjcrzyzz54ya9")

This fails with a validation error because the prefix does not match.

This is useful when you want the model itself to encode domain meaning (e.g. this field must be a user ID, not just any ID).

Serialization

When exporting a model, TypeIDs are always serialized as strings.

from typing import Literal
from pydantic import BaseModel
from typeid.integrations.pydantic import TypeIDField

class User(BaseModel):
    id: TypeIDField[Literal["user"]]

u = User(id="user_01ke82dtesfn9bjcrzyzz54ya9")
data = u.model_dump(mode="json")

assert data == {"id": "user_01ke82dtesfn9bjcrzyzz54ya9"}
from typing import Literal
from pydantic import BaseModel
from typeid.integrations.pydantic import TypeIDField

class User(BaseModel):
    id: TypeIDField[Literal["user"]]

u = User(id="user_01ke82dtesfn9bjcrzyzz54ya9")
json_data = u.model_dump_json()

assert json_data == '{"id":"user_01ke82dtesfn9bjcrzyzz54ya9"}'

This keeps JSON output simple and predictable.

JSON Schema / OpenAPI

The generated schema looks roughly like this:

id:
  type: string
  format: typeid
  description: TypeID with prefix 'user'
  examples:
    - user_01ke82dtesfn9bjcrzyzz54ya9

Notes:

  • The schema does not hard-code internal regex details.
  • Actual validation is handled by the TypeID core.
  • The schema is meant to document intent, not re-implement parsing rules.

Why Literal["user"]?

The recommended form is:

TypeIDField[Literal["user"]]

This works cleanly with:

  • Ruff
  • Pyright / MyPy
  • IDE type checkers

Using Literal makes the prefix a real compile-time constant and avoids annotation edge cases.

Design notes

  • The TypeID core does not import Pydantic.
  • All framework-specific code lives in typeid.integrations.pydantic.
  • Parsing and validation rules live in the core, not in the adapter.

This keeps the integration small and easy to maintain.

That’s it — no magic, no hidden behavior.