SwapSQL

Migrate a Django project from MySQL to PostgreSQL

PostgreSQL is Django's reference database — features like JSONField, ArrayField and full-text search are best supported there. Switching is a few lines of config plus a careful data move. Here is the order that avoids surprises.

1. Install the PostgreSQL adapter

Django talks to PostgreSQL through psycopg (version 3). Install it into your virtualenv:

pip install "psycopg[binary]"

The [binary] extra pulls a prebuilt wheel so you do not need PostgreSQL's development headers locally. On older projects you may still see psycopg2-binary; both work with Django 4.2+, but new code should prefer psycopg 3.

2. Change DATABASES in settings.py

Swap the MySQL engine for the PostgreSQL backend:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "myapp",
        "USER": "myapp",
        "PASSWORD": "secret",
        "HOST": "127.0.0.1",
        "PORT": "5432",
    }
}

The MySQL backend (django.db.backends.mysql) often carried an OPTIONS entry forcing utf8mb4 and STRICT_TRANS_TABLES. Delete it — PostgreSQL is UTF-8 throughout and strict by default, so those options have no equivalent and will error if left in.

3. Know what changes under the hood

Django abstracts most differences, but a few leak through:

4. Move the data

Small / fixture-friendly projects. Django's own serializer is database-agnostic and the simplest path. Dump from MySQL, point settings at the empty PostgreSQL database, migrate, then load:

python manage.py dumpdata --natural-foreign --natural-primary \
  -e contenttypes -e auth.Permission > data.json
# switch settings.py to PostgreSQL, then:
python manage.py migrate
python manage.py loaddata data.json

Excluding contenttypes and auth.Permission avoids the classic "duplicate key" error, because migrate recreates those rows itself.

Large / production databases. dumpdata loads every row into memory and is slow at scale. Instead, take a real SQL dump and convert the dialect — type mappings, AUTO_INCREMENT → sequences, backtick quoting and all:

mysqldump --single-transaction --no-tablespaces \
  -u root -p myapp > myapp.sql

Convert myapp.sql to PostgreSQL, then import with psql -d myapp -f converted.sql. Run python manage.py migrate --fake afterward so Django records the schema as already applied.

5. Reset the sequences

After importing rows with explicit IDs, realign every table's sequence so new inserts get fresh IDs. Django prints the exact SQL for you:

python manage.py sqlsequencereset myapp | python manage.py dbshell

6. Verify

Run python manage.py check, then your test suite against PostgreSQL. Compare row counts on key models (Model.objects.count()) with the MySQL originals before pointing production at the new database.

Convert your dump in seconds — turn your MySQL export into PostgreSQL now →