Skip to content
Aventheon

Flask and Django upgrade paths: what breaks and in what order

October 12, 2026

Python web apps have a way of going quiet. They keep working, nobody touches them, and then one day a security notice or a hosting change forces the question: what would it take to get this onto a current version?

The honest answer is that it depends on where you start, and the useful part is knowing in advance which changes are documented as breaking. This post sticks to what the official release notes and docs say, with links, so you can check each claim against the source. Where we could not verify something, we left it out.

Facts below were checked against the linked pages on 2026-10-12. Versions move, so confirm against the current docs before you plan.

Start with the supported picture

The Django project's download page lists 5.2 (an LTS release) with extended support through April 2028, 6.0 through April 2027, and 6.1 as the current release, with 6.2 LTS planned for April 2027. It also says 6.2 is the last release under the old versioning and support policy, and that from Django 2028 every feature release gets the same three-year support period and releases move to a year-based version number.

For Flask, the changelog is the authoritative record. At the time of writing the latest entry is 3.1.3, released 2026-02-18.

If your app is older than any of these, the rest of this post is your reading list.

Rule one for both: go one hop at a time

Django's own upgrade guide says that when upgrading through more than one feature version, "it's usually easier to upgrade through each feature release incrementally" and that the same applies when moving from one LTS to the next. It also recommends resolving deprecation warnings under your current version before upgrading, and gives the command:

python -Wa manage.py test
# or with pytest
PYTHONWARNINGS=always pytest tests --capture=no

The reasoning is simple and applies to Flask too. Deprecation warnings are the project telling you which of your code will break next. Fixing them while still on the old version keeps each step small.

Flask: what breaks, in version order

Flask's changelog is detailed, so you can build your checklist straight from it.

Flask 2.2 (released 2022-08-01). This one mostly deprecates, and the deprecations become removals later:

  • The FLASK_ENV environment variable and app.env are deprecated. Debug mode is controlled with --debug or app.run(debug=True).
  • The JSON_AS_ASCII, JSON_SORT_KEYS, JSONIFY_MIMETYPE and JSONIFY_PRETTYPRINT_REGULAR config keys are deprecated, with JSON settings moving to the app.json provider.
  • Custom json_encoder and json_decoder classes are deprecated in favor of the app.json provider interface.

Flask 2.3 (released 2023-04-25). This is where the 2.2 deprecations are removed, and it is the hop that most often bites:

  • Python 3.7 is dropped, and minimum dependencies rise (Werkzeug 2.3.0 or later, among others).
  • FLASK_ENV, the ENV config key and app.env are removed.
  • The JSON config keys listed above and the json_encoder / json_decoder attributes are removed.
  • app.before_first_request and bp.before_app_first_request are removed.
  • Calling setup methods on a blueprint after registration is now an error rather than a warning.
  • Importing escape and Markup from flask is deprecated; import from markupsafe instead.
  • SESSION_COOKIE_DOMAIN no longer falls back to SERVER_NAME.

A quick way to find the likely breakage before you start:

grep -rnE "before_first_request|FLASK_ENV|json_encoder|json_decoder|JSON_SORT_KEYS|JSON_AS_ASCII|from flask import .*(escape|Markup)" .

Flask 3.0 (released 2023-09-30). Removes previously deprecated code, deprecates the __version__ attribute (use importlib.metadata.version("flask") instead), and requires Werkzeug 3.0.0 or later.

Flask 3.1 (released 2024-11-13). Drops Python 3.8 and raises minimums to Werkzeug 3.1, ItsDangerous 2.2 and Blinker 1.9. It also adds things you may want: SECRET_KEY_FALLBACKS for key rotation (extensions need to add support) and a TRUSTED_HOSTS config.

The practical order: get clean of 2.2 deprecation warnings first, then take 2.3, then 3.0, then 3.1. Pin Werkzeug deliberately at each step, because Flask's minimum Werkzeug jumps with each major hop.

Django: what breaks, in version order

Django's pattern is different. Its deprecation policy removes things on a published schedule, and the release notes for each version list what was removed.

Python version floor. Per the 5.2 release notes, Django 5.2 supports Python 3.10 through 3.14 (as of 5.2.8). The 6.0 release notes say 6.0 supports Python 3.12, 3.13 and 3.14, and that the 5.2 series is the last to support 3.10 and 3.11. If your servers run an older Python, the interpreter upgrade is a separate, earlier project.

Django 5.2 notable changes:

  • Dropped support for PostgreSQL 13; 5.2 supports PostgreSQL 14 and higher.
  • MySQL connections now default to the utf8mb4 character set instead of utf8.
  • The minimum oracledb version rises from 1.3.2 to 2.3.0.

Django 6.0 notable changes:

  • DEFAULT_AUTO_FIELD now defaults to BigAutoField instead of AutoField. If you relied on the old default, set it explicitly in settings to avoid unwanted migrations.
  • Dropped support for MariaDB 10.5; 6.0 supports MariaDB 10.6 and higher.
  • Raised minimum versions of optional dependencies, including Pillow 10.1.0, psycopg 3.1.12, psycopg2 2.9.9 and redis-py 5.1.0 (the notes give the full list), and the minimum asgiref goes from 3.8.1 to 3.9.1.
  • Removals completing the 5.0 and 5.1 deprecation cycles, including cx_Oracle support, format_html() called without args or kwargs, the FORMS_URLFIELD_ASSUME_HTTPS transitional setting (the forms.URLField default scheme is now https), and positional arguments to Model.save() and Model.asave().
  • Deprecations to clear now: optional parameters of send_mail() and related django.core.mail functions must be passed as keywords, and the ADMINS / MANAGERS tuple format is deprecated in favor of email strings.

Several of these are quiet. The DEFAULT_AUTO_FIELD change in particular will not raise an error. It surfaces as a migration you did not expect, which is a good reason to run makemigrations --check in CI during the upgrade.

python manage.py makemigrations --check --dry-run
python manage.py check --deploy

A sensible order of operations, either framework

  1. Pin and snapshot. Lock current dependency versions and get the existing tests passing. If there are no tests, build characterization tests around the paths that matter before changing anything.
  2. Turn warnings into errors on the old version. Fix everything the current version deprecates.
  3. Upgrade the Python interpreter if needed, separately from the framework.
  4. Upgrade third-party packages. Django's guide notes that in most cases you will need the latest versions of your Django-related dependencies too. Flask extensions are the same story, and the ones that are abandoned are the real risk to your schedule.
  5. Take one framework hop at a time, running tests and reading that version's release notes in full.
  6. Clear caches after deploy if you cache pickled objects, since the Django docs warn they are not guaranteed to be compatible across versions.
  7. Keep a rollback. Database migrations make this harder than it looks, so decide before deploying whether a migration is reversible.

What we left out

We did not include a table of which Django release removed which specific deprecated API beyond what the 6.0 notes list, and we did not make claims about the contents of Django 6.1 or about third-party packages such as DRF, Celery or Flask extensions. Those depend on your dependency set and are best checked against each package's own changelog.

Getting a read on your own app

How hard this is for you comes down to your starting version, your test coverage and how many unmaintained extensions you rely on. If you want a second opinion on the size of the jump, book a free 30-minute Triage and we will look at your versions with you. The instant check is a quicker way to see where you stand first.