Fix: Django Migration Conflict (Conflicting Migrations Cannot Be Applied)
Part of: Python Errors
Quick Answer
How to fix Django migration conflicts — why multiple leaf migrations conflict, how to merge conflicting migrations, resolve dependency chains, and set up a team workflow to prevent migration conflicts.
The Error
Running python manage.py migrate or python manage.py makemigrations fails with:
CommandError: Conflicting migrations detected; multiple leaf nodes in the
migration graph: (0003_add_email, 0003_add_phone) in app 'users'.
To fix them run 'python manage.py makemigrations --merge'.Or:
django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the
django_migrations table (table "django_migrations" already exists)Or when running showmigrations:
users
[X] 0001_initial
[X] 0002_add_username
[ ] 0003_add_email
[ ] 0003_add_phone ← Two migrations with the same numberWhy This Happens
Django migration conflicts occur when two migrations claim to be the “next” migration in the same app’s history — both have the same parent but were created independently. Django models migrations as a directed acyclic graph: each migration has zero or more parents, and the latest unmerged migrations are called “leaf nodes.” A healthy app has exactly one leaf per app; anything more is a fork.
The fork itself is not the bug — it is the natural outcome of branch-based development. The bug is shipping the fork to production without merging it. When a deploy runs migrate against a graph with two leaves, Django refuses to pick a winner and stops the deploy.
Common root causes:
- Two developers created migrations in parallel — Developer A creates
0003_add_emailon their branch; Developer B creates0003_add_phoneon their branch. When merged, Django sees two migrations with parent0002, creating a fork in the graph. - Stale migration state — a migration was created based on an outdated model state.
- Squashing went wrong — a squashed migration has incorrect dependencies.
- Migrations not committed before branching — a developer forgets to commit migrations before branching, then makes a migration on the branch that conflicts with the one on the main branch.
There is also a less obvious failure mode: a long-lived feature branch that gets rebased onto a main branch that has added several migrations. The local copies of 0003.py and 0004.py get carried along, but their parent pointers now reference migrations that have new siblings. The branch builds locally because the developer last ran migrate weeks ago, but CI explodes the moment it tries to apply the graph against a fresh database. Treat any migration-touching PR that has been open longer than a week with suspicion.
Fix 1: Merge Conflicts with makemigrations —merge
The standard fix when Django itself detects the conflict:
# Django tells you exactly what to do
python manage.py makemigrations --merge
# If you have multiple apps with conflicts, specify the app
python manage.py makemigrations --merge users
# Review the generated merge migration before applying
cat users/migrations/0004_merge_0003_add_email_0003_add_phone.pyThe generated merge migration looks like this — it simply depends on both conflicting migrations:
# users/migrations/0004_merge_0003_add_email_0003_add_phone.py
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('users', '0003_add_email'),
('users', '0003_add_phone'),
]
operations = [
# Empty — the merge migration just links the two branches
]After creating the merge migration:
python manage.py migrate users
python manage.py showmigrations users
# All migrations should now show [X]Fix 2: Resolve Conflicting Migrations Manually
When the auto-merge would produce incorrect ordering (e.g., one migration adds a column that the other migration also references), resolve manually:
Step 1 — identify which migrations conflict:
python manage.py showmigrations users
# users
# [X] 0001_initial
# [X] 0002_add_username
# [ ] 0003_add_email ← Conflicts with:
# [ ] 0003_add_phone ← This one
python manage.py migrate --check # Returns non-zero if migrations are unappliedStep 2 — decide on the correct order:
Determine which migration should come first logically. If add_phone depends on the email field being present, then add_email must run first.
Step 3 — rename and re-number one migration:
# Rename 0003_add_phone to 0004_add_phone to make it come after 0003_add_email
mv users/migrations/0003_add_phone.py users/migrations/0004_add_phone.pyStep 4 — update the dependencies in the renamed migration:
# users/migrations/0004_add_phone.py
class Migration(migrations.Migration):
dependencies = [
('users', '0003_add_email'), # Now depends on 0003_add_email, not 0002
]
operations = [
migrations.AddField(
model_name='user',
name='phone',
field=models.CharField(max_length=20, blank=True),
),
]Step 5 — apply:
python manage.py migrate usersFix 3: Fix Conflicts in Development After git merge
The most common real-world scenario — you merge a branch and suddenly have conflicting migrations:
git merge feature/add-email-field
# Now you have:
# users/migrations/0003_add_email.py (from main)
# users/migrations/0003_add_phone.py (from your branch)Option A — use makemigrations —merge (safe, recommended):
python manage.py makemigrations --merge --no-input
git add users/migrations/0004_merge_*.py
git commit -m "Merge migration conflict in users app"Option B — delete your local migration and recreate it:
If you haven’t pushed your branch yet and your migration is simpler:
# 1. Unapply your local migration
python manage.py migrate users 0002 # Roll back to before your migration
# 2. Delete your conflicting migration
rm users/migrations/0003_add_phone.py
# 3. Recreate it — it will now depend on 0003_add_email
python manage.py makemigrations users
# Creates: 0004_add_phone.py with correct dependency
# 4. Apply
python manage.py migrate usersOption C — squash and restart (for local dev databases only):
# Reset all migrations for the app (DESTROYS DATA — dev only)
python manage.py migrate users zero # Unapply all migrations
find users/migrations -name "0*.py" -not -name "0001_initial.py" -delete
python manage.py makemigrations users
python manage.py migrate usersFix 4: Fix “django_migrations table already exists” Error
This happens when you try to initialize Django migrations on a database that already has the tables but not the migrations tracking table — common when migrating from a legacy schema:
# Create the migrations table without running any migrations
python manage.py migrate --run-syncdb
# Or — fake the initial migration to tell Django the schema already exists
python manage.py migrate users 0001 --fake-initial--fake-initial tells Django to mark the initial migration as applied without actually running it — useful when the tables already exist from a syncdb or manual SQL.
For all apps at once:
python manage.py migrate --fake-initialFix 5: Prevent Migration Conflicts in Teams
Use a sequential naming convention:
# Bad — Django auto-names by content: 0003_add_email vs 0003_add_phone
python manage.py makemigrations
# Good — use --name to make intent clear (but numbering still conflicts)
python manage.py makemigrations users --name add_email_fieldEstablish a team workflow:
Always pull and migrate before creating a new migration:
git pull origin main python manage.py migrate python manage.py makemigrationsCommit migrations immediately after creating them — never leave them uncommitted.
Use a migration lock in CI — fail the build if unapplied migrations exist:
# In CI before running tests python manage.py migrate --check # Returns exit code 1 if there are unapplied migrations — catches conflicts earlySquash migrations periodically to clean up the history and reduce conflict surface:
python manage.py squashmigrations users 0001 0010 # Creates users/migrations/0001_squashed_0010_...py
Fix 6: Fix Dependency Errors in Migration Files
When a migration references a model or field from another app and the dependency is missing:
django.db.migrations.exceptions.NodeNotFoundError:
Migration users.0003_add_profile references model 'profiles.Profile'
from app 'profiles', which is not installedAdd the missing dependency explicitly:
# users/migrations/0003_add_profile.py
class Migration(migrations.Migration):
dependencies = [
('users', '0002_add_username'),
('profiles', '0001_initial'), # ← Add this dependency
]
operations = [
migrations.AddField(
model_name='user',
name='profile',
field=models.OneToOneField(
'profiles.Profile',
on_delete=models.CASCADE,
null=True,
),
),
]Check the dependency graph:
# Show the full migration dependency graph
python manage.py showmigrations --planFix 7: Fix Migrations on Production After a Bad Deploy
If a bad migration was deployed to production and needs to be rolled back:
Roll back to the previous migration:
# Check current state
python manage.py showmigrations users
# Roll back to the migration before the bad one
python manage.py migrate users 0005 # Reverts 0006 and later
# Verify
python manage.py showmigrations usersIf the migration is not reversible (e.g., it drops a column), write a reverse migration:
class Migration(migrations.Migration):
dependencies = [('users', '0005_safe_migration')]
operations = [
# Forward: removes the column
migrations.RemoveField(model_name='user', name='legacy_field'),
# This migration CANNOT be reversed automatically
# Add database_backwards to make it reversible:
]
def database_backwards(self, app_label, schema_editor, from_state, to_state):
# Manually re-add the column for rollback
schema_editor.execute(
"ALTER TABLE users_user ADD COLUMN legacy_field VARCHAR(255) DEFAULT ''"
)In Production: Incident Lens
The classic incident: two PRs both add migrations the same week. Each passes its own CI run because each migration graph is single-leaf in isolation. Merge them in either order and the second merge ships a fork. The next deploy hits python manage.py migrate in the release step, Django refuses to run with two leaf nodes, and the deploy job exits non-zero. Blast radius: the entire release is stuck. Any other change bundled with that release — bug fixes, security patches — also cannot ship until the migration graph is single-leaf again.
Detection. Run python manage.py makemigrations --check --dry-run in CI on every PR. The command exits non-zero if there are unapplied model changes or if conflicting leaves exist on the branch. Pair it with python manage.py migrate --check against a freshly built database to catch dependency errors that only surface on apply. Together they fail the PR before it merges, not the deploy after.
Recovery. Generate the merge migration locally with python manage.py makemigrations --merge, review the generated file (it should have no operations and only list both leaves under dependencies), commit it, push, and redeploy. If the two migrations modify the same field — for example one adds a unique=True constraint and the other backfills data — do not use the auto-merge. Manually re-number one migration to come after the other, edit its dependencies to point at the now-earlier sibling, and add any reconciling operations. Verify with python manage.py migrate --plan before deploying.
Prevention. The cultural fix is single-owner-per-app migrations: a PR that touches users/migrations/ cannot land while another users/migrations/ PR is open. Enforce it with a CODEOWNERS rule or a simple GitHub Action that fails if git diff --name-only origin/main...HEAD shows migration files in an app already touched by an open PR. Squash old migrations on a quarterly cadence so the graph stays small. For data migrations that take more than a few seconds, ship them as separate RunPython migrations behind a feature flag so a deploy stuck on the second migration does not block the schema change in the first.
Still Not Working?
Check for circular dependencies. If two apps depend on each other’s migrations, you get a circular dependency error. Resolve by moving the shared model to a third app or using a lazy reference:
# Instead of ForeignKey to a model in another app that depends on this one:
field=models.ForeignKey('other_app.Model', ...) # Lazy string reference — avoids circular importCheck that INSTALLED_APPS includes all apps with migrations. If an app is missing from INSTALLED_APPS, Django cannot find its migrations and reports dependency errors.
Use python manage.py migrate --plan to see what migrations will be applied before running them:
python manage.py migrate --plan
# Lists every migration in the order it will be appliedCheck for orphaned merge migrations that were committed but never applied. When a deploy is rolled back mid-migration, the database can sit in a half-merged state where the merge migration exists in code but is not in django_migrations. showmigrations shows it as [ ] even though the leaves it merges are [X]. Apply it explicitly with python manage.py migrate users 0004_merge_... — this is safe because merge migrations have empty operations.
Check for migration state drift between environments. If staging and production were deployed at different points in time, they can disagree on which leaves exist. Compare python manage.py showmigrations output across environments before promoting. A common trap is creating a migration on a hotfix branch off production while a feature branch with its own migration is still in staging — both will appear single-leaf in their own environment, then conflict when merged.
Disable atomic migrations for data-heavy operations. Long RunPython operations inside an atomic transaction can hold locks long enough to time out a deploy or block reads in production. Set atomic = False on the migration class and chunk the data work yourself with iterator() and explicit bulk_update() calls. The migration becomes idempotent and resumable, which matters when a conflicting leaf forces a partial retry.
For related Django and database issues, see Fix: Django OperationalError No Such Table, Fix: Prisma Migration Failed, Fix: Postgres Deadlock Detected, and Fix: SQLAlchemy Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Peewee Not Working — Connection Pooling, Field Errors, and Migration Setup
How to fix Peewee errors — OperationalError database is locked, connection already open, field type mismatch, model meta database missing, N+1 queries, and peewee-migrate setup.
Fix: Tortoise ORM Not Working — Model Registration, Async Init, and Relationship Errors
How to fix Tortoise ORM errors — Tortoise.init not called, no module imported model, fetch_related missing, aerich migration setup, FastAPI integration patterns, and ConfigurationError missing connection.
Fix: psycopg Not Working — psycopg2 to psycopg3 Migration, Connection Pool, and Async Errors
How to fix psycopg errors — psycopg2 to psycopg 3 import migration, connection pool setup, row factory tuple vs dict, COPY protocol changes, async psycopg pool, server-side cursor confusion, and binary mode performance.
Fix: asyncpg Not Working — Connection Pool, Prepared Statements, and Transaction Errors
How to fix asyncpg errors — connection refused localhost 5432, pool exhausted timeout, prepared statement does not exist, type codec not registered, JSON automatic conversion, and transaction rollback on exception.