---
title: 'Announcement: Pydantic v2.12 Release'
description: >-
  What’s new in Pydantic 2.12: Python 3.14 support, experimental MISSING
  sentinel, TypedDict PEP 728 extras, improved URL path handling & more
date: '2025-10-07'
authors:
  - Victorien Plot
categories:
  - Release
  - New Features
canonical: 'https://pydantic.dev/articles/pydantic-v2-12-release'
---

> Markdown version of [Announcement: Pydantic v2.12 Release](https://pydantic.dev/articles/pydantic-v2-12-release) — the canonical HTML page.
>
> By [Victorien Plot](https://pydantic.dev/authors/victorien-plot.md) · 2025-10-07 · Release, New Features
>
> Related: [Announcement: Pydantic v2.13 Release](https://pydantic.dev/articles/pydantic-v2-13-release.md) · [Announcement: Pydantic v2.11 Release](https://pydantic.dev/articles/pydantic-v2-11-release.md)
>
> All articles: [/articles.md](https://pydantic.dev/articles.md) · Site index: [/llms.txt](https://pydantic.dev/llms.txt)

---

The long awaited [Pydantic v2.12](https://github.com/pydantic/pydantic/releases/tag/v2.12.0) is here!
You can install it now from [PyPI](https://pypi.org/project/pydantic/):

```bash
pip install --upgrade pydantic
```

This release features the work of over 20 external contributors and provides useful new features, along with initial Python 3.14 support.
Several minor changes (considered non-breaking changes according to our [versioning policy](https://pydantic.dev/docs/validation/2.12/get-started/version-policy/#pydantic-v2))
are also included in this release. Make sure to look into them before upgrading.

Highlights include:
* [Support for Python 3.14](#support-for-python-314)
* [A new `MISSING` sentinel](#missing-sentinel)
* [PEP 728 support – TypedDict with Typed Extra Items](#pep-728-support--typeddict-with-typed-extra-items)
* [Preserve empty URL paths](#preserve-empty-url-paths)

You can see the full changelog on [GitHub](https://github.com/pydantic/pydantic/releases/tag/v2.12.0).

:::commend
This release ships with a complete rewrite of the serialization and types support documentation. Do not hesitate to check them out!
:::

## Quick Reference:

* [New Features](#new-features)
* [Changes](#changes)

## New Features

### Support for Python 3.14

Python 3.14 ships with new type annotations semantics, introduced by [PEP 649](https://peps.python.org/pep-0649/)
and [PEP 749](https://peps.python.org/pep-0749/). Type annotations are now lazily evaluated, dropping the need
to use [string annotations](https://typing.python.org/en/latest/spec/annotations.html#string-annotations) (or
enabling this behavior per module using [`from __future__ import annotations`](https://docs.python.org/3/library/__future__.html#future__.annotations)).

Here is an example demonstrating the new behavior:

```python
class Model(BaseModel):
    f: ForwardType  # No quotes required.


type ForwardType = int

m = Model(f=1)
```

:::note
Deferred annotations allow more complex cases (mainly involving nested scopes) to work, while it was previously really hard
if not impossible to support them in previous Python versions. Pydantic still has limited support for such cases, and progress
can be tracked in [this issue](https://github.com/pydantic/pydantic/issues/12080).
:::

:::deter{title="Pydantic V1"}
Pydantic V1 core functionality will *not* work properly with Python 3.14 or greater. As such, Python 3.13 is the latest supported Python version
for V1.
:::

PR references:

* [Add initial support for Python 3.14 (#11991)](https://github.com/pydantic/pydantic/pull/11991)
* [Add support for `doc` attribute on dataclass fields (#12077)](https://github.com/pydantic/pydantic/pull/12077)

### `MISSING` sentinel

A highly requested Pydantic feature is having the ability to differentiate between a default value and value provided during
the model creation, especially if `None` has a specific meaning in the context it is used. Until now, Pydantic provided the
[`model_fields_set`](https://pydantic.dev/docs/validation/2.12/api/pydantic/base_model/#pydantic.BaseModel.model_fields_set) property, but
its usage was proven difficult.

Pydantic 2.12 introduces an experimental [`MISSING` sentinel](https://pydantic.dev/docs/validation/2.12/concepts/experimental/#missing-sentinel):
a singleton indicating a field value was not provided during validation. During serialization, any field with `MISSING` as a value is
excluded from the output.

```python
from pydantic import BaseModel
from pydantic.experimental.missing_sentinel import MISSING


class Configuration(BaseModel):
    timeout: int | None | MISSING = MISSING


# configuration defaults, stored somewhere else:
defaults = {'timeout': 200}

conf = Configuration()

# `timeout` is excluded from the serialization output:
conf.model_dump()
# {}

# The `MISSING` value doesn't appear in the JSON Schema:
Configuration.model_json_schema()['properties']['timeout']
#> {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Timeout'}}
```

This feature is marked as experimental because it relies on the draft [PEP 661](https://peps.python.org/pep-0661/),
introducing sentinels in the standard library.

See the [documentation](https://pydantic.dev/docs/validation/2.12/concepts/experimental/#missing-sentinel) for more details.

PR reference: [#11883](https://github.com/pydantic/pydantic/pull/11883).

### PEP 728 support – TypedDict with Typed Extra Items

Back in August, [PEP 728](https://peps.python.org/pep-0728/) was accepted as part of the upcoming Python 3.15 release.
This PEP adds two class parameters, `closed` and `extra_items` to type the extra items on a [TypedDict](https://docs.python.org/3/library/typing.html#typing.TypedDict),
addressing the need to define closed TypedDict types or to type a subset of keys that might appear in a `dict` while permitting additional items of a specified type.

```python
from typing_extensions import TypedDict

from pydantic import TypeAdapter


class TD(TypedDict, extra_items=int):
    a: str


ta = TypeAdapter(TD)

print(ta.validate_python({'a': 'test', 'extra': 1}))
#> {'a': 'test', 'extra': 1}
```

Such behavior was partially achievable using the [`extra`](https://pydantic.dev/docs/validation/2.12/api/pydantic/config/#pydantic.config.ConfigDict.extra)
configuration value. It is now recommended to use PEP 728 capabilities.

Note that PEP 728 is currently only implemented in [typing_extensions](https://typing-extensions.readthedocs.io/en/latest/). Make sure to
import `TypedDict` from there if you want to make use of this feature.

The [`additionalProperties`](https://json-schema.org/understanding-json-schema/reference/object#additionalproperties) JSON Schema keyword
will be populated depending on the `closed`/`extra_items` specification (`False` if closed, `True` if `extra_items` is `Any` or `object`,
matching the schema of the `extra_items` type otherwise).

PR reference: [#12179](https://github.com/pydantic/pydantic/pull/12179).

### Preserve empty URL paths

The [URL types](https://pydantic.dev/docs/validation/2.12/api/pydantic/networks/#pydantic.networks.AnyUrl) had a behavior change in V2, where a trailing
slash would be added if the path was empty. In many cases, this is unwanted.

Pydantic 2.12 adds a new [`url_preserve_empty_path`](https://pydantic.dev/docs/validation/2.12/api/pydantic/config/#pydantic.config.ConfigDict.url_preserve_empty_path)
configuration value to opt-out of this behavior:

```python
from pydantic import AnyUrl, BaseModel, ConfigDict


class Model(BaseModel):
    u: AnyUrl

    model_config = ConfigDict(url_preserve_empty_path=True)


print(Model(u='https://example.com').u)
#> Before: 'https://example/com/'
#> After: 'https://example/com'
```

This is also configurable per-field using the [`UrlConstraints`](https://pydantic.dev/docs/validation/2.12/api/pydantic/networks/#pydantic.networks.UrlConstraints)
metadata class:

```python
from typing import Annotated

from pydantic import TypeAdapter, UrlConstraints

ta = TypeAdapter(Annotated[AnyUrl, UrlConstraints(preserve_empty_path=True)])
print(ta.validate_python('https://example.com'))
#> https://example.com
```

This configuration value may default to `True` in V3.

PR references:

* [#12336](https://github.com/pydantic/pydantic/pull/12336).
* [pydantic-core#1789](https://github.com/pydantic/pydantic-core/pull/1789).

### Control validation behavior of timestamps

Pydantic uses to guess if a timestamp was provided in seconds or milliseconds for temporal types (such as [`datetime`](https://docs.python.org/3/library/datetime.html#datetime.datetime)
or [`date`](https://docs.python.org/3/library/datetime.html#datetime.date)). The new [`val_temporal_unit`](https://pydantic.dev/docs/validation/2.12/api/pydantic/config/#pydantic.config.ConfigDict.val_temporal_unit)
configuration value can now be used to force validation as seconds, milliseconds, or by inferring as before.

```python
from datetime import datetime

from pydantic import BaseModel, ConfigDict


class Model(BaseModel):
    d: datetime

    model_config = ConfigDict(val_temporal_unit='milliseconds')


print(Model(d=datetime(1970, 4, 11, 19, 13).timestamp() * 1000))
#> d=datetime.datetime(1970, 4, 11, 18, 13)), would be somewhere around year 2245 in 'infer' mode.
```

A new [`ser_json_temporal`](https://pydantic.dev/docs/validation/2.12/api/pydantic/config/#pydantic.config.ConfigDict.ser_json_temporal) configuration
value is also introduced, generalizing the existing [`ser_json_timedelta`](https://pydantic.dev/docs/validation/2.12/api/pydantic/config/#pydantic.config.ConfigDict.ser_json_timedelta)
one.

Contributed by [@ollz272](https://github.com/ollz272). PR references:

* [#12068](https://github.com/pydantic/pydantic/pull/12068).
* [pydantic-core#1743](https://github.com/pydantic/pydantic-core/pull/1743).

### `exclude_if` field option

A new `exclude_if` option was added, which can be used at the field level.

```python
from pydantic import BaseModel, Field


class Transaction(BaseModel):
    id: int
    value: int = Field(ge=0, exclude_if=lambda v: v == 0)


print(Transaction(id=1, value=0).model_dump())
#> {'id': 1}
```

Contributed by [@andresliszt](https://github.com/andresliszt). PR references:

* [#12141](https://github.com/pydantic/pydantic/pull/12141).
* [pydantic-core#1535](https://github.com/pydantic/pydantic-core/pull/1535).

### `ensure_ascii` JSON serialization option

A new `ensure_ascii` option was added to the [JSON serialization methods](https://pydantic.dev/docs/validation/2.12/concepts/serialization/#json-mode)
(such as `model_dump_json()`), to ensure non-ASCII characters will be Unicode-encoded. For backwards compatibility, this option defaults to `False`.

```python
from pydantic import TypeAdapter

ta = TypeAdapter(str)

ta.dump_json('🔥', ensure_ascii=True)
#> b'"\\ud83d\\udd25"'
```

PR reference: [pydantic-core#1689](https://github.com/pydantic/pydantic-core/pull/1689).

### `extra` configuration per validation

It is now possible to control the [`extra`](https://pydantic.dev/docs/validation/2.12/concepts/models/#extra-data) behavior per validation call:

```python
from pydantic import BaseModel


class Model(BaseModel):
    x: int

    model_config = ConfigDict(extra='allow')


# Validates fine:
m = Model(x=1, y='a')
# Raises a validation error:
m = Model.model_validate({'x': 1, 'y': 'a'}, extra='forbid')
```

Contributed by [@anvilpete](https://github.com/anvilpete). PR references:

* [#12233](https://github.com/pydantic/pydantic/pull/12233).
* [pydantic-core#1722](https://github.com/pydantic/pydantic-core/pull/1722).

## Changes

This release contains some minor changes that may affect existing code. Make sure to go over through them before upgrading.

### Error when using incompatible `pydantic-core` versions

Pydantic is meant to work with one and only one `pydantic-core` version. However, many users reported errors caused by an incompatible
`pydantic-core` version being used. Starting in 2.12, Pydantic raises an explicit error at startup if this is the case. Note that most
dependency bots (such as GitHub's [Dependabot](https://github.com/dependabot)) do *not* understand the `pydantic-core` exact constraint,
which might be the source of such issues.

PR reference: [#12196](https://github.com/pydantic/pydantic/pull/12196).

### Remove warning for experimental features

Pydantic has a set of [experimental features](https://pydantic.dev/docs/validation/2.12/concepts/experimental/), exposed under the `pydantic.experimental`
module. Until now, a `PydanticExperimentalWarning` was emitted whenever the module was imported. We decided to remove this warning, as the module
name already conveys that it is experimental. As such, it is no longer required to [filter the warning](https://pydantic.dev/docs/validation/2.11/concepts/experimental/#warnings-on-import)
on import.

PR reference: [#12265](https://github.com/pydantic/pydantic/pull/12265).

### Field changes

Small changes and bug fixes affect [Pydantic fields](https://pydantic.dev/docs/validation/2.12/concepts/fields/) and the `Field()` function.

* Warning emitted when field-specific metadata is used in invalid contexts:

  Using field-specific metadata (e.g. `alias` or `exclude`) in invalid contexts will now raise a warning. Previously, it was silently ignored
  and was a common source of confusion. In particular, these two examples don't behave as expected:

    ```python
    from typing import Annotated, Optional

    from pydantic import BaseModel, Field

    # ❌ `alias` can't be used on type aliases:
    type AliasedInt = Annotated[int, Field(alias='b')]


    class Model(BaseModel):
        a: AliasedInt
        # ❌ Instead use: Annotated[Optional[int], Field(exclude=True)]
        c: Optional[Annotated[int, Field(exclude=True)]]
    ```

  (for type aliases, see more details in the [documentation](https://pydantic.dev/docs/validation/2.12/concepts/types/#metadata-type-alias-warning)).

* Refactor of the `FieldInfo` class:

  The `FieldInfo` class (created by the `Field()` function and used to store information about each field) underwent a complete refactor to
  fix many related bugs. While this shouldn't affect anything in theory, there is always a small change that it can break some edge cases.
  Do not hesitate to report any issues that may be occur from this refactor.

  As a result, the undocumented `FieldInfo.merge_field_infos()` is deprecated. If you made use of this function previously, please
  [open an issue](https://github.com/pydantic/pydantic/issues/new/choose) to discuss potential alternatives.

* Dataclasses fields inconsistencies:

  Two small inconsistencies were found when mixing dataclasses and the Pydantic [`Field()`] function. These two bugs were fixed in 2.12, but may
  result in a behavior change in your code. The two issues can be found in issue [#12045](https://github.com/pydantic/pydantic/issues/12045).

PR references:

* [field-specific metadata warning](https://github.com/pydantic/pydantic/pull/12028).
* [`FieldInfo` refactor](https://github.com/pydantic/pydantic/pull/11898).
* [Dataclasses fields inconsistencies](https://github.com/pydantic/pydantic/pull/12051).

### Unify `serialize_as_any`/`SerializeAsAny` behavior

Pydantic provides a way to serialize values as if they were typed as `Any`. In this case, Pydantic does *not* make use of the type annotation
to infer how to serialize the value, but instead inspects the actual type of the value to do so.

This `serialize_as_any` behavior is useful when you want "duck typing" behavior with model subclasses
(as per [the documentation](https://pydantic.dev/docs/validation/latest/concepts/serialization/#subclasses-of-model-like-types)):

```python
from pydantic import BaseModel, SerializeAsAny


class User(BaseModel):
    name: str


class UserLogin(User):
    password: str


class OuterModel(BaseModel):
    user: User


user = UserLogin(name='pydantic', password='password')

print(OuterModel(user=user).model_dump(serialize_as_any=True))
"""
{
    'user': {'name': 'pydantic', 'password': 'password'}
}
"""
```

If `serialize_as_any` wasn't set, the `password` field wouldn't have been included in the output, because it isn't present in the `User` annotation.

Such behavior can also be enabled at the field level, by using annotating `user` as `SerializeAsAny[User]` instead.

Before 2.12, the `serialize_as_any` parameter was behaving quite differently from the
[`SerializeAsAny`](https://pydantic.dev/docs/validation/latest/api/pydantic/functional_serializers/#pydantic.functional_serializers.SerializeAsAny) annotation,
and such behavior has been unified in this release. This may result in serialization errors when using the `serialize_as_any` flag,
which would have happened already if using the `SerializeAsAny` annotation. To mitigate the issue, you can apply the `SerializeAsAny`
annotation only on the relevant fields (as `serialize_as_any` will apply the behavior to *every* value, which in most cases isn't wanted).

If you still require `serialize_as_any` to be set, please refer to [this issue](https://github.com/pydantic/pydantic/issues/12382).

### JSON Schema changes

While not breaking, some JSON Schema changes in this release might affect your tests if you make assertions on the generated JSON Schemas
for your data. Here are the potential changes that may affect you:

* Add regex patterns to JSON schema for `Decimal` type (contributed by [@Dima-Bulavenko](https://github.com/Dima-Bulavenko) in [#11987](https://github.com/pydantic/pydantic/pull/11987)).
* Respect custom title in functions JSON Schema (in [#11892](https://github.com/pydantic/pydantic/pull/11892)).
* When manually creating [TypedDict schemas](https://pydantic.dev/docs/validation/2.12/api/pydantic-core/pydantic_core_schema/#pydantic_core.core_schema.typed_dict_schema), the
  `extra_behavior` key is now used to populate the [`additionalProperties`](https://json-schema.org/understanding-json-schema/reference/object#additionalproperties)
  keyword.

### After model validators

[Model *after* validators](https://pydantic.dev/docs/validation/2.12/concepts/validators/#model-validators) are documented as being instance methods.
However, class methods used to be accepted:

```python
class Model(BaseModel):
    @model_validator(mode='after')
    @classmethod
    def validator(cls, model, info): ...
```

Starting in 2.12, using this signature will now raise a deprecation warning. Instead, make sure to define the validator as an *instance* method:

```python
class Model(BaseModel):
    @model_validator(mode='after')
    def validator(self, info): ...
```

:::note
In the initial 2.12 release, using the invalid signature would error. However, we realized it broke such signatures *without* the `info` parameter,
and we underestimated the amount of occurrences of this invalid signature. In 2.12.3, this was changed to a deprecation warning instead.
:::

PR references:

* [#11957](https://github.com/pydantic/pydantic/pull/11957).
* [#12414](https://github.com/pydantic/pydantic/pull/12414).

### Mypy version support

Until now, Pydantic supported the mypy versions released less than 6 months ago to work with the mypy plugin. This incurred extra maintenance
cost on our side, and as such we now only explicitly support the latest mypy released version.

PR reference: [#11832](https://github.com/pydantic/pydantic/pull/11832).

### Disable virtual subclassing capabilities on Pydantic models

:::note
This issue is not likely to affect any user, but is documented for completeness.
:::

The CPython implementation has a long-standing [performance issue](https://github.com/python/cpython/issues/92810) when defining a large
number of subclasses of an abstract base class (to be able to define abstract Pydantic models, the `BaseModel` class uses
[`abc.ABCMeta`](https://docs.python.org/3/library/abc.html#abc.ABCMeta) as a metaclass and as such is affected).

To work around this issue, Pydantic implemented partial optimization in `isinstance()`/`issubclass()` model checks. However, this ended up
breaking virtual subclasses (see the [`register()`](https://docs.python.org/3/library/abc.html#abc.ABCMeta.register) method). The partial
optimization still resulted in performance and memory issues, and was not working correctly with some logic from the
[`unittest.mock`](https://docs.python.org/3/library/unittest.mock.html) module.

As such, we restored the `isinstance()`/`issubclass()` behavior to how it is normally implemented, and using the
[`register()`](https://docs.python.org/3/library/abc.html#abc.ABCMeta.register) method now raises a warning. Doing
so also improves validation performance slightly.

PR reference: [#11669](https://github.com/pydantic/pydantic/pull/11669).
