Migrating v1 to v2

The renames, the behaviour changes that do not raise, and how to tell which version a tutorial is describing.

Overview

Why this module exists even if you never migrate

Pydantic version 2 was a rewrite. The core moved to Rust, and the API changed in places that matter. Code written for version 1 often fails on version 2, or worse, runs and behaves differently. Many projects still carry some version 1 habits.

Even if you never migrate anything, this article is worth reading. Older tutorials, answers and blog posts are written for version 1, and you will meet them. Knowing what changed lets you tell at a glance which advice still applies and which does not.

The article covers the changes in the order you are likely to hit them. First come the renames, where a familiar method now has a new name. Then come validators, which have a new decorator and a new signature. After that, Config, which is now a dictionary instead of an inner class. The last section covers the change that bites hardest, a shift in behaviour rather than in naming, where the same code gives a different answer. Each point comes with the old way and the new one, so you can translate code in either direction.

Think of it as a translation guide. You are unlikely to rewrite everything in one go, and you do not need to. Reading the changes in sequence makes each one easier to spot when it appears in an old snippet. It also helps you judge how much work a real upgrade would take, which is the question most teams ask first.

Each change is shown briefly and clearly.

Pydantic v1 was common for years. A great deal of writing about it's still the top result for many searches. Reading a v1 answer as though it shows v2 is a genuine source of confusion.

So the most handy thing here may simply be the ability to date a page.

It's v1 if you see: @checker, @root_validator, .dict(), .json(), .parse_obj(), class Config, orm_mode, min_items, regex=.

It's v2 if you see: @field_validator, @model_validator, .model_dump(), model_config = ConfigDict(...), from_attributes, min_length, pattern=.

If a page uses the first set, treat all in it as historical. Not only the names. The action it shows.

Worth knowing

.dict() → model_dump(), .json() → model_dump_json(), .parse_obj() → model_validate(), .schema() → model_json_schema().
@validator → @field_validator (with @classmethod), @root_validator → @model_validator(mode=...).
class Config → model_config = ConfigDict(...). orm_mode became from_attributes; allow_mutation=False became frozen=True.
The one that does not raise a rename error: Optional[X] no longer defaults to None. It is now required-but-nullable unless you add = None.
min_items/max_items became min_length/max_length; regex became pattern.
Error type codes were renamed wholesale. Anything matching on v1 codes or on message text stops working silently.

Migrating v1 to v2: What Changed and What Bites

The renames, the action changes that fail silently, and how to date a tutorial.

The method renames

The most visible change. The old names still exist in v2 and warn, which is why so much code and so many tutorials still use them.

example_01.pyPydantic
Output

Validators were renamed and reshaped

@validator became @field_validator, and @root_validator became @model_validator with an explicit mode.

example_02.pyPydantic
Output

Config became model_config

The inner class Config is the clearest signal that code or a tutorial predates v2.

example_03.pyPydantic
Output

Optional no longer implies a default

The behaviour change most likely to bite, because v1 filled the default in for you and v2 does not.

example_04.pyPydantic
Output

Constraint arguments were renamed

min_items, max_items and regex are gone. The replacements are shorter and consistent across types.

example_05.pyPydantic
Output

Errors changed shape

Error codes were renamed and ctx, url and stable types arrived. Code matching on v1 messages will silently stop working.

example_06.pyPydantic
Output

The renames

The rote part, and the easy part:

.dict() → model_dump(). .json() → model_dump_json(). .parse_obj() → model_validate(). .parse_raw() → model_validate_json(). .schema() → model_json_schema(). .copy() → model_copy(). .make() → model_construct(). __fields__ → model_fields. __fields_set__ → model_fields_set.

The model_ prefix is deliberate: it namespaces the library's methods away from your field names. This Is also why Pydantic warns about fields starting with model_.

Most old names still exist in v2 and emit deprecation warnings. That is a kindness for migration and the reason so much code still uses them. Nothing forced the change.

Validators

@checker became @field_validator, and it now needs @classmethod underneath.

The signature changed too. v1 took (cls, v, values) where values was a dict of before-checked fields. V2 takes (cls, v, info) and the same data is info.data.

@root_validator became @model_validator, and the mode is now clear. v1's pre=True is mode="before". The default post-checker is mode="after" and receives the model rather than a dict of values, returning self.

That last change is more than cosmetic. A v1 root checker worked with a dict. A v2 after-checker works with the finished model. Fields are converted and field access works.

Config

class Config became model_config = ConfigDict(...), with a few settings renamed:

orm_mode → from_attributes. allow_mutation = False → frozen=True. allow_population_by_field_name → populate_by_name. anystr_strip_whitespace → str_strip_whitespace. min_anystr_length → str_min_length.

An inner class Config still works and warns. It's the single clearest signal that code hasn't been migrated.

The change that bites

all above makes a warning or an error. This one doesn't:

summary: Optional[str]

In v1 that was optional and defaulted to None. In v2 it's needed and nullable. You must pass it, and you may pass None.

Nothing warns, because the annotation is still valid. What happens is that code which used to work starts raising missing at runtime, in what code path first makes the model without that field.

The fix is one addition per field: Optional[str] = None.

The reason for the change is the one the defaults module gave. V1's silent default hid the gap between "may be omitted" and "may be null". This Are truly other contracts. v2 made you say which you meant.

If you're migrating anything large, search for Optional[ first. It will be the largest single source of failures.

Constraint renames

min_items/max_items → min_length/max_length, now steady with strings.

regex → pattern.

allow_mutation on a field → frozen.

const=True is gone. Use Literal[value].

Some v1 helper types were removed or changed too. constr(regex=...) turns Annotated[str, Field(pattern=...)]. This Is the modern spelling anyway.

Errors

Error type codes were renamed wholesale. v1's value_error.any_str.min_length is v2's string_too_short. v1's type_error.int is v2's int_type or int_parsing based on the cause.

ctx now carries the rule's params, and url links to docs for the type.

This is where the standing advice in this track. Match on type, never on msg. Comes from. Code that matched v1 message strings broke on upgrade with no warning at all. This Is just the failure mode that advice blocks.

If you're migrating, any error-handling code is worth reviewing right rather than trusting tests. A broken branch that never matches won't fail loudly.

A practical order

If you're doing this on a real code:

Install v2 and run the test suite. Deprecation warnings tell you where the renames are, and they're rote.

**Search for Optional[ and add the defaults.** Largest source of genuine failures, and invisible until ran.

Rewrite checkers. Add @classmethod, rename the decorators, change values to info.data. Make mode clear on root checkers.

**Convert class Config blocks**, renaming the settings that moved.

Review error handling last, because that is where the failures are silent.

There's a tool, bump-pydantic. This Does the rote parts on its own. It's worth running first and reviewing carefully. It handles the renames well and can't know your intent on the Optional question.

What you get for it

The migration isn't free, and it's worth knowing what it buys.

check is much faster, because the core is Rust rather than Python. Strict mode exists. Discriminated unions became a first-class feature. TypeAdapter arrived. With it check without a wrapper model. Error output became structured and stable. computed_field exists. output gained real control.

Most of this track shows features that are v2-only. If you're reading it against a v1 code, that is the gap.

Things that were removed outright

A few v1 features have no v2 equal, and finding them late is unpleasant.

copy_on_model_validation is gone. The action is controlled by revalidate_instances instead.

GetterDict, the customisation hook for orm_mode, was removed. from_attributes reads fields right. Anything more involved belongs in a model_validator(mode="before").

json_encoders in Config is deprecated in favour of field and model serialisers. This Are more precise and appear in the right place.

parse_file is gone. Read the file yourself and use model_validate_json on the bytes. This Is better anyway. The errors name the spot in the doc.

const=True is gone. A Literal says the same thing and makes a better schema.

Running both versions at once

For a large migration, pydantic.v1 is on hand inside v2: from pydantic.v1 import BaseModel gives the old library alongside the new one.

That makes an incremental migration possible. Move one module at a time, with both versions installed as a single package.

Two cautions. The two aren't interoperable: a v1 model can't be a field of a v2 model. Mixing them at a boundary means converting through dicts. And a dependency that hasn't migrated may pull in its own expectations. Check what your libraries need before given you can take it slowly.

It's a transitional tool. Code left half-migrated for a year tends to stay that way. The two vocab lists side by side are truly confusing to read.

Migrating v1 to v2: summary

Renames are rote and mostly warn. Optional[X] losing its silent None is the change that fails silently. Searching for it first will save the most time.

checkers changed shape and name. @classmethod, info.data, clear modes. Config moved into model_config with a few settings renamed. Error codes were replaced wholesale. Error-handling code deserves direct review rather than trust in tests.

bump-pydantic handles the rote parts. What it can't do is decide what you meant by Optional. This Is just the part that matters.

Mistakes people make with Migrating v1 to v2

Trusting the test suite to find all. Renames warn and errors are loud. Two groups fail quietly: Optional fields that are now needed. Error-handling branches matching v1 codes that simply never match again. Neither always fails a test.

**Running bump-pydantic and shipping.** It handles the rote renames well and can't know what you meant by Optional[X]. Review its output rather than treating it as a migration.

Migrating models and not checkers. A @checker still importable from pydantic in v2 is the deprecated shim. The signature changed. values became info.data. And a checker reading the wrong arg name won't behave as it did.

**Leaving class Config because it still works.** It warns rather than failing. It lives indefinitely. It's the single clearest marker of code that hasn't really been migrated.

**Living in pydantic.v1 permanently.** The match import exists for a transition. Two vocab lists side by side in one code are truly confusing. Half-migrated code tends to stay that way.

Reading v1 answers as v2. The most common problem for people who never migrate anything. Check for @checker, .dict() and class Config before trusting a page. The action it shows has changed too, not only the spelling.

Deciding whether to migrate

If you're on v1 and wondering whether it's worth it, the honest spot.

v1 is no longer developed and receives only security fixes. The ecosystem has moved: FastAPI, LangChain, SQLModel and most libraries that integrate with Pydantic now target v2. Staying on v1 increasingly means pinning things around it.

Against that, the migration is real work on a large code. It's work with no visible feature at the end of it.

The pragmatic answer for most teams is to migrate when a thing else forces the question. A dependency that needs v2, or a piece of work that touches the models anyway. Rather than as a standalone project. The pydantic.v1 match import exists to make that gradual approach viable.

What isn't viable is starting new code on v1. all in this track past the first tier is v2, and the gap widens.

Where this leaves the track

That is the last module. Thirty of them, from a type annotation that does nothing at runtime to an API whose docs writes itself.

The through-line has been one idea: be exact at the boundary, once. exact enough that the annotation says what you mean, that the constraint reaches the schema, that the error names the field. That all downstream can stop defending itself.

all else has been method. Coercion rules so text can arrive as text. checkers for the rules types can't hold. output so output is data rather than presentation. Schemas so other tools can read what you already wrote.

The version history matters here only because v2 is where most of that became true. If you're reading this against a v1 code, the gap isn't stylistic.

A migration checklist

Condensed, in the order that finds problems soonest.

Install v2 and run the suite. Fix what fails loudly. Imports, removed args, renamed helpers.

Search Optional[ and add = None wherever the field was meant to be omissible. Largest source of silent breakage.

Rewrite checkers: @field_validator with @classmethod, @model_validator with an clear mode, values turns info.data.

Convert class Config blocks, renaming orm_mode, allow_mutation and the anystr_ family.

Rename constraint args: min_items, max_items, regex.

Review error handling by hand. Anything matching v1 type codes or message text has stopped matching, silently, and tests may not notice.

Then drop the deprecated method names once the warnings are the only thing left.

Questions people ask about Migrating v1 to v2

Do I have to rewrite everything to move to v2? Mostly renames. Install v2 and run bump-pydantic, which rewrites the common ones, then fix the behaviour changes by hand.

What are the most common renames? dict() became model_dump(), json() became model_dump_json(), parse_obj() became model_validate(), and Config became model_config.

What happened to @validator? It is deprecated in favour of @field_validator, which needs @classmethod, and @root_validator became @model_validator. The old names still work with a warning for now.

Which change breaks code silently? Optional[int] no longer implies a default of None. In v2 a field with no default is required even if it accepts None.

Can I run v1 and v2 side by side? Yes. Import pydantic.v1 from a v2 install to keep old models running while you migrate module by module.

Is v2 faster? Yes, validation runs in the Rust-based pydantic-core and is commonly several times faster than v1, with larger gains on big payloads.

If you are only reading, not migrating

The most common use of this module isn't migration at all. It's dating an answer you found while looking for a thing else.

That skill is worth more than it sounds. A confident, well-written, highly-ranked answer showing v1 action will send you in the wrong direction for an afternoon. Nothing about it announces its age.

The tell is the vocab. @checker, .dict(), class Config, orm_mode, min_items, regex=. Any one of those means the page predates v2. The action it shows may have changed and the spelling. Optional being the sharpest example. The code will look correct and simply not work.

Check yourself

0 of 4

Answer without scrolling back up.

  1. In v2, what does `summary: Optional[str]` with no default mean?

  2. What replaced `@root_validator`?

  3. Which of these dates a tutorial as v1?

  4. Why review error-handling code carefully during a migration?

Cheat sheet

Migrating v1 to v2

Walk through the renames, the silent behaviour changes and the patterns that break when moving a Pydantic v1 codebase to v2.

PYDANTIC · vizlearn.in/pydantic/migrating_v1_to_v2.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.