Diagnosing Django \"no such table\" After makemigrations and migrate
A diagnostic guide for Django's OperationalError: no such table after makemigrations and migrate, with ordered checks, fixes, and escalation criteria.
08 Jul 2025, 12:29 UTC

The problem and the useful takeaway
\nYou reload a Django page and receive django.db.utils.OperationalError: no such table: app_model even though you just ran makemigrations and migrate. The useful takeaway is that creating migration files is not the same as applying them to the database your running process is connected to. Start by confirming which migrations Django thinks are applied, then verify the database alias and file path before editing or deleting migration files.
Terms used below: a migration is a Python file in an app's migrations/ directory that describes schema changes. The django_migrations table is Django's internal record of which migrations have been applied. An OperationalError is a database-level error, not a Python code error.
Recognizable condition
\n- \n
- The traceback contains
no such tablefollowed by an app and model table name. \n - The model exists in
models.pyand a migration file exists inmigrations/. \n - The error appears after running
makemigrationsandmigrate, often on the first request that queries the model. \n - It can happen in the development server, a management command, or a production worker. \n
Common causes and diagnostic signals
\n| Observed condition | Likely cause | First signal to check |
|---|---|---|
Migration files exist, but showmigrations shows empty boxes [ ] | Migration was never applied or was rolled back | Run showmigrations for the app |
showmigrations shows [X], but the table is absent | Table dropped manually, or migration applied to a different database | Inspect django_migrations and the actual table list |
DATABASES points to a different SQLite file or database instance | Migrations ran on another database file or host | Print settings.DATABASES from the running project |
Multiple database aliases or DATABASE_ROUTERS are configured | Migration applied to one alias; the app queries another | Run showmigrations --database=<alias> |
| SQLite database file or directory is read-only | migrate could not create the table | Check file and directory permissions |
| Migration file deleted after being applied | Migration history out of sync with files | Compare django_migrations rows against migrations/ files |
Ordered checks
\n- \n
Check which migrations Django considers applied
\nRun from the project directory that contains
\nmanage.py, with the project's virtual environment activated:
\npython manage.py showmigrationsFor one app:
\n
\npython manage.py showmigrations <app>Look for
\n[X](applied) versus[ ](not applied). If the migration for the missing table shows[ ], the table was never created. You can also query Django's history table directly, for example with SQLite:
\nsqlite3 db.sqlite3 \"SELECT * FROM django_migrations WHERE app = '<app>';\";Required access: read permission on the database file or instance. Risk: none; this is read-only.
\n \nConfirm the database the running process is using
\nOpen
\nsettings.pyand inspectDATABASES. For SQLite, note theNAMEpath. Relative paths resolve from the current working directory, so starting the server from a different folder can create or open a differentdb.sqlite3file.Print the active configuration from the same environment the server uses:
\n
\npython manage.py shell -c 'from django.conf import settings; print(settings.DATABASES)'Compare the printed path or host with the database you inspected in step 1. If they differ, Django applied migrations to a different database than the one serving requests.
\n \nCheck multi-database routers and aliases
\nIf
\nDATABASEScontains more than adefaultalias, or ifDATABASE_ROUTERSis set,migratetargetsdefaultunless you specify another alias:
\npython manage.py migrate --database=<alias>Check the other alias:
\n
\npython manage.py showmigrations --database=<alias>If the migration appears under a non-default alias, the app's database router may be sending queries to a database that never received the migration.
\n \nCheck SQLite file permissions
\nFor SQLite, the user running the web server or worker needs write permission on both the
\n.sqlite3file and its directory, because creating a table modifies the file and may create temporary files:
\nls -l db.sqlite3If the file is read-only or owned by another user,
\nmigratemay fail silently or raise a permission error instead of creating the table. \nLook for an interrupted migration
\nIf a previous
\nmigratecommand crashed, the database may be partially updated. Most Django backends run each migration in a transaction, but manual SQL or disabled transactions can leave the schema anddjango_migrationsout of sync. \n
Fixes tied to findings
\nFix 1: Apply the missing migration
\nIf showmigrations shows [ ], run:
python manage.py migrate <app>\nThis applies all unapplied migrations for that app. Back up the database first in production. This command changes database state.
\nFix 2: Correct the database path or alias
\nIf settings.DATABASES points to the wrong SQLite file or database instance, update NAME, HOST, PORT, or other connection parameters, then restart the application process so it picks up the new setting. Alternatively, if the migration was intentionally applied to another alias, run:
python manage.py migrate --database=<alias>\nand update the router or application code so queries use the intended alias.
\nFix 3: Recreate a deleted migration file safely
\nIf django_migrations contains a row for a migration whose file is missing, do not delete the row. Recreate the migration file with the same name and dependencies, then mark it applied without running SQL:
python manage.py migrate <app> <migration_name> --fake\nRisk: --fake records the migration as applied without executing its SQL. If the table is genuinely missing, the error will persist. Only use --fake after confirming the schema already matches the migration, or after manually creating the missing table.
Fix 4: Recover from manual schema changes
\nIf you dropped or renamed the table with direct SQL, either recreate the table to match the migration's intended schema, or restore from a backup. In development only, you can reset migration history for the app and reapply migrations, but this deletes data.
\nFix 5: Fix SQLite permissions
\nChange ownership or permissions on the database file and directory so the application user can write. Then rerun migrate.
Fix 6: Resolve migration dependency loops
\nIf migrations reference each other in a loop, edit the dependencies list in the migration files to remove the cycle, then run makemigrations and migrate again.
Rollback guidance
\nmigrate changes state. To reverse a specific migration:
python manage.py migrate <app> <previous_migration_name>\nBack up the database first. Rolling back removes data stored in tables created by the reversed migration. Do not roll back in production unless you have confirmed the data loss is acceptable.
\nEscalation criteria
\n- \n
- You are in production and cannot identify which database file or instance the running process uses. \n
django_migrationsand the physical schema disagree across multiple apps. \n- The error returns after
migratereports success. \n - You suspect corruption in
django_migrationsor repeated partial migrations. \n
In these cases, take a database backup, stop further state-changing commands, and involve a database administrator or engineer with access to the production environment.
\nLimitations and practical verification
\nshowmigrations reflects Django's tracking table, not the physical schema. A row can exist while the table is absent. Use sqlmigrate to inspect the SQL Django intends to run:
python manage.py sqlmigrate <app> <migration_number>\nCompare that SQL against the actual tables in your database. After applying a fix, verify by running the request or management command that previously failed, or by running:
\npython manage.py check --database <alias>\ncheck validates configuration but does not confirm table existence; the real test is querying the model through the application.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.