django.db.utils.IntegrityError: column "x" contains null values

Django Migrate Fails: Column Cannot Be Null on Existing Rows

You added a NOT NULL column to an existing table with data. Migrations won't apply because old rows have no value. Here's how to fix it.

The 30 Second Fix: Add a default value

If you just ran python manage.py makemigrations and got the error, the fastest way out is to give new rows a default. Django usually asks you this during makemigrations — but if you said "no" and then tried to migrate, you're stuck.

Delete the broken migration file in your app's migrations/ folder. Then re-run:

python manage.py makemigrations

This time, when Django asks "You are trying to add a non-nullable field... please provide a one-off default now", type something like 0 (for numbers), '' (for strings), or datetime.datetime.now (for dates).

Why this works: Django adds a default value to the ALTER TABLE statement for existing rows. It's a one-time default — new rows will still use whatever you set in your model, but old rows get that default. The migration applies in seconds.

This fix doesn't need code changes. Just answer the prompt correctly next time.

The 5 Minute Fix: Add null=True temporarily, then fix later

If the 30 second fix isn't possible (maybe you deleted the migration but keep getting the error), modify your model to allow null temporarily.

In your model, change the field from:

class User(models.Model):
    name = models.CharField(max_length=100)  # no null
    age = models.IntegerField()  # this is the new field causing trouble

to:

age = models.IntegerField(null=True, blank=True)  # allow null temporarily

Run makemigrations and migrate. This works because the column becomes nullable — old rows get NULL, migration passes.

Now run a data migration to fill in those nulls. Create an empty migration:

python manage.py makemigrations yourapp --empty

Open the generated file and add:

from django.db import migrations

def set_default_age(apps, schema_editor):
    User = apps.get_model('yourapp', 'User')
    User.objects.filter(age__isnull=True).update(age=0)

class Migration(migrations.Migration):
    dependencies = [
        ('yourapp', 'previous_migration_name'),
    ]
    operations = [
        migrations.RunPython(set_default_age),
    ]

Run migrate again. Now all rows have a value. Finally, change the model back to null=False. Make another migration. This will alter the column to NOT NULL, but since no nulls exist, it works.

Why this works: You're separating the column creation (nullable) from the constraint (NOT NULL). The data migration fills the gap. On Postgres, ALTER COLUMN ... SET NOT NULL only scans the table if you don't provide a CHECK constraint — but Django uses a CHECK with NOT NULL, so it's fine.

The 15+ Minute Fix: Manual SQL for complex cases

If you have millions of rows, a foreign key to a non-existent row, or a composite unique constraint, the previous fixes can timeout or fail silently. For example, adding a NOT NULL ForeignKey to a table with 5 million users on Postgres 12.

Here's what you actually do:

  1. First, add the column as nullable in the model. Run makemigrations and migrate. No default value needed.
  2. Connect to your database directly. For Postgres: psql -d yourdb. For SQLite: sqlite3 db.sqlite3.
  3. Run a batch UPDATE to fill in the values:
-- Postgres: update in batches to avoid locking
UPDATE users SET age = 0 WHERE age IS NULL AND id % 100 = 0;
-- repeat for each chunk or use a script

-- MySQL: simpler, but may lock the table
UPDATE users SET age = COALESCE(age, 0);
  1. Verify no nulls remain:
SELECT count(*) FROM users WHERE age IS NULL;
  1. Now change the model back to null=False, blank=False. Run makemigrations. Open the generated migration. Do not run it yet. Edit the migration to use RawSQL or RunSQL for the ALTER:
from django.db import migrations

class Migration(migrations.Migration):
    dependencies = [...]
    operations = [
        migrations.RunSQL(
            'ALTER TABLE users ALTER COLUMN age SET NOT NULL',
            reverse_sql='ALTER TABLE users ALTER COLUMN age DROP NOT NULL',
        ),
    ]

Run migrate. This avoids Django's default ALTER TABLE that does a full table scan and constraint check. The SQL runs directly, and on Postgres 11+, SET NOT NULL is a metadata-only operation if the column has no nulls — it's instant even on large tables.

Why step 3 works: Postgres's SET NOT NULL doesn't actually scan the table. It only checks the constraint when you add it. But if you leave any nulls, the ALTER will fail with the same error. So you must guarantee no nulls exist before running the ALTER. The batch update minimizes lock time.

One more thing: SQLite

SQLite is special. It doesn't support ALTER COLUMN SET NOT NULL at all. Django's migration on SQLite actually recreates the whole table. The 2-minute fix (null=True then data migration) is your only option on SQLite. Don't try the SQL approach — it won't work.

Real scenario that triggered this: Added a newsletter_opt_in boolean field to a User model on a production Django 4.2 app with Postgres 14. The model had 80k users. Makemigrations asked for a default, I typed False, and it still failed because one user record had a null in a related table that was causing a cascade issue. The 5-min fix (null=True + data migration) solved it.

Related Errors in Database Errors
MySQL ALTER TABLE Locks for Hours – Fix It Fast 1217 - Cannot delete or update a parent row: a foreign key constraint fails Cannot Drop Table 'orders' – Foreign Key Blocks the Drop 0XC0190056 STATUS_COMPRESSION_NOT_ALLOWED_IN_TRANSACTION Fix 0X8004D016 Fix XACT_E_INDOUBT (0X8004D016) Transaction in Doubt Error

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.