model_json_schema(), and dottxt can enforce that schema through the OpenAI-compatible API.
Use with dottxt
Pydantic is a good fit when you want Python-native types, schemas defined as code, and runtime validation from the same model definitions.Install
Basic usage
Generate JSON Schema from a Pydantic model and send it inresponse_format:
model_config = ConfigDict(extra="forbid") when you want strict object schemas. Without it, the generated schema allows extra properties.
Pydantic also generates a title for fields and models. Those are omitted from the examples below for readability.
Add constraints and descriptions
UseField() for constraints and descriptions. Use Literal for enum values:
enum, pattern, or required.
Reference
Use the sections below as a reference for how common Pydantic patterns map to JSON Schema.Enums
Use enums when a field must be one of a fixed set of known values. UseLiteral when a field should be limited to a fixed set of values:
enum.Enum also works. Pydantic puts the enum definition in $defs and references it:
Literal when the values are only used once. Use Enum when you want to reuse the same set of values across fields or models.
Const
Use const-style fields when a value should never vary. Use a single-valueLiteral[...] when a field must always have one exact value:
Optional and nullable fields
Use optional and nullable fields carefully because they produce different schema contracts.T | None with a default of None become nullable and optional. Fields typed as T | None = Field(...) stay required but nullable. See Optional vs Null for the semantic difference.
Arrays and lists
Use list types for repeated values, then add bounds on the list or its items as needed. Uselist[T] for array fields. Pydantic maps list min_length and max_length to minItems and maxItems:
Formats and specialized types
Use specialized Pydantic types when you want the generated schema to carry semantic format information. Use Pydantic’s built-in types when you want semantic formats in the generated schema:EmailStr and date over plain str when you want the schema to carry format information.
Nested models
Use nested models to reuse object shapes and keep larger schemas maintainable. Nested models become$defs references in the generated schema:
Discriminated unions
Use discriminated unions when the output can take one of several object shapes. UseLiteral with Field(discriminator=...) to generate tagged oneOf schemas in Pydantic:
discriminator metadata in the generated schema, but the important part for dottxt is the oneOf structure and the const tag values on each branch. That is what makes the output unambiguous at generation time.
See AnyOf Object Variants for the schema design side of this pattern.
Recursive models
Use recursive models for trees and other nested structures where items can contain more items of the same shape. Models that reference themselves produce recursive$defs:
from __future__ import annotations enables forward references so the model can reference itself. Set bounds on recursive lists so generation does not expand without limit.
Keep the recursive type under a named object property rather than using the recursive node itself as the top-level response schema.
Composition and inheritance
Use inheritance to combine shared field groups without repeating schema definitions by hand. Use multiple inheritance to combine reusable field groups:Validators do not affect schema
Use validators for application-side checks, but do not rely on them to shape the generated schema. Pydantic validators run at parse time, but they do not appear in the generated JSON Schema. If you need to constrain generation, express it in the type annotation orField():
Notes
- Use Pydantic when you want Python types, runtime validation, and JSON Schema generation from one model definition. Use raw JSON Schema directly when you need full control over the output shape or keywords that do not map cleanly from Pydantic types.
- Set
ConfigDict(extra="forbid")when you wantadditionalProperties: false. - Use
Literalfor enums rather thanjson_schema_extra={"enum": [...]}. - Use validators for parse-time checks, not generation-time constraints.
- See String Bounds, Bounded Arrays, AnyOf Object Variants, and Optional vs Null for schema design details that matter during generation.