Flask-Migrate (backed by Alembic) is fantastic right up until it isn’t. Migration errors tend to arrive at the worst moments — right before a deploy, in a fresh developer environment, or when two branches get merged. This guide covers the most common causes and how to fix each one.

Quick Answer: Most Flask-Migrate errors fall into four buckets:

  1. Multiple heads — two migration branches need merging (flask db merge heads)
  2. Target not up to date — your database is behind the current revision (flask db upgrade)
  3. Can’t locate revision — a migration file references a revision that doesn’t exist locally
  4. Schema drift — the database schema no longer matches what Alembic thinks it looks like

Read on to identify which one you’re hitting and get the right fix.


Cause #1: Multiple Heads

What you see:

Error: Multiple head revisions are present for given argument 'head'; please specify a specific target revision

Why it happens:

When two developers (or two branches) each add a migration at the same time, Alembic ends up with two separate “head” revisions that both claim to be the latest. The revision graph forks instead of staying linear.

You can confirm this with:

flask db heads

If you see two revision IDs printed, that’s your problem.

The fix — merge the heads:

flask db merge heads -m "merge migration branches"
flask db upgrade

flask db merge heads generates a new migration file that has both heads as its down_revision. It doesn’t alter the database schema; it just stitches the revision graph back together. After that, flask db upgrade applies it.

If you want to preview what the merge migration will look like before running it:

flask db merge --rev-range head1:head2 -m "merge" --no-exec

Replace head1 and head2 with the actual revision IDs from flask db heads.

Preventing it on a team:

Consider a CI check that runs flask db heads and fails the build if there’s more than one head. You’ll catch the conflict before it ever reaches your shared environment.


Cause #2: Target Database Is Not Up to Date

What you see:

ERROR [alembic.util.messaging] Target database is not up to date.

This usually happens when you try to generate a new migration (flask db migrate) while unapplied migrations are still sitting in the queue.

Why it happens:

Alembic refuses to autogenerate a new migration if the database isn’t at the current head. It’s a safety guard — if the schema it’s comparing against doesn’t reflect pending migrations, the autogenerated diff will be wrong.

The fix:

Step 1: Apply all pending migrations first.

flask db upgrade

Step 2: Then generate your new migration.

flask db migrate -m "add user preferences table"

If flask db upgrade itself fails with an error, address that first (likely Cause #1 or Cause #3). Check the current state with:

flask db current
flask db history --indicate-current

flask db history shows the full revision chain; the --indicate-current flag marks where your database actually sits.


Cause #3: Can’t Locate Revision

What you see:

alembic.util.exc.CommandError: Can't locate revision identified by 'abc123def456'

Why it happens:

Your database’s alembic_version table says it’s at revision abc123def456, but that file doesn’t exist in your local migrations/versions/ folder. This happens most often when:

  • Someone deleted a migration file from version control.
  • You switched branches and the migration you’re looking for only exists on the other branch.
  • A team member pushed a migration but you haven’t pulled yet.

How to diagnose:

# See what revision the DB thinks it's on
flask db current

# List all revision files you actually have
ls migrations/versions/

If the revision from flask db current doesn’t match any file in migrations/versions/, you’ve found the gap.

Fix option A — pull the missing file:

Most of the time, the file exists somewhere (another branch, a teammate’s machine). Grab it and put it in migrations/versions/. Then run flask db upgrade.

Fix option B — stamp and rebuild (development environments only):

If you’re in a dev or CI environment and don’t care about preserving data, you can reset Alembic’s tracking and start fresh:

# Drop all tables and the alembic_version table
flask db downgrade base

# Re-apply from scratch
flask db upgrade

Fix option C — stamp to a known good revision (careful!):

If the database schema is actually correct but the alembic_version table is just pointing at a ghost revision, you can stamp it to a revision you do have:

flask db stamp <revision_id>

Only do this if you’re confident the schema matches that revision. Getting it wrong leads to Alembic generating bogus autogenerated migrations later.


Cause #4: Schema Drift (Autogenerate Produces Unexpected Changes)

This one’s subtler. flask db migrate runs without errors but generates a migration that touches tables or columns you didn’t change, or misses changes you know you made.

Why it happens:

Alembic autogenerate works by comparing your SQLAlchemy models against the actual database schema. If those two drift apart — because someone applied manual SQL, because a migration was applied directly to production but not locally, or because Alembic simply can’t detect certain change types — the diff will be off.

Common things Alembic won’t autogenerate:

  • Changes to stored procedures, functions, or triggers
  • Changes to data (only schema changes)
  • Table or column renames (it sees a drop + add)
  • Index changes on some databases

Diagnosing drift:

flask db migrate --no-exec -m "drift check"

The --no-exec flag (some versions use --dry-run) generates the migration file without writing it to disk. Review the output carefully. If you see drops or additions you didn’t expect, compare:

# In a Flask shell, inspect what Alembic sees
from flask import current_app
from flask_migrate import Migrate
from your_app import db

# Show tables SQLAlchemy knows about
print(db.engine.dialect.get_table_names(db.engine.connect()))

The fix:

If autogenerate is touching something you don’t want changed, exclude it in env.py:

# migrations/env.py

def include_object(object, name, type_, reflected, compare_to):
    # Don't autogenerate changes for legacy tables we manage manually
    if type_ == "table" and name in ("legacy_audit_log", "external_data_cache"):
        return False
    return True

context.configure(
    connection=connection,
    target_metadata=target_metadata,
    include_object=include_object,
    compare_type=True,  # Also detect column type changes
)

The compare_type=True option is worth turning on globally — by default Alembic doesn’t detect type changes (e.g., String(50) to String(200)), and you’ll miss those silently.


Cause #5: Migration Works Locally but Fails on Production

You’ve run migrations locally with no issues, but flask db upgrade on the production server throws an error. A few common culprits:

A — Environment variable not set:

Flask-Migrate needs FLASK_APP (or SQLALCHEMY_DATABASE_URI through your config) to find the right database. Double-check that these are set in your production environment before running migrations.

# Verify the target URI before upgrading
flask db show  # shows current revision
echo $SQLALCHEMY_DATABASE_URI

B — Migration requires a column default but the table has rows:

Alembic generates:

# This will fail on a non-empty table with strict NOT NULL enforcement
op.add_column('users', sa.Column('preferences', sa.JSON(), nullable=False))

The fix: add a server default or make it nullable first, backfill, then add the constraint:

# Step 1: Add nullable
op.add_column('users', sa.Column('preferences', sa.JSON(), nullable=True))

# Step 2: Backfill in the migration itself (small tables) or via a data migration script
op.execute("UPDATE users SET preferences = '{}' WHERE preferences IS NULL")

# Step 3: Add the NOT NULL constraint
op.alter_column('users', 'preferences', nullable=False)

C — Concurrent migrations hitting a lock:

On busy production databases, running flask db upgrade while the app is serving traffic can cause table lock timeouts. Consider running migrations during low-traffic windows, or use your database’s built-in online DDL support.


Still Not Working?

A few edge cases worth checking:

Alembic version table in the wrong schema: If you’re using a non-default PostgreSQL schema (e.g., myapp.users instead of public.users), make sure version_table_schema is set in env.py.

Multiple apps sharing a database: If you have multiple Flask apps writing migrations to the same database, use separate version_locations and version_table names in each app’s env.py to prevent collision.

Flask shell vs. migrations env: Code in env.py runs in a different context than your Flask app. If your models import app-specific config or context, make sure env.py initializes things correctly — missing this is a common source of “table not found” errors during autogenerate.


Summary Checklist

  • [ ] Run flask db heads — if there are two, merge them first
  • [ ] Run flask db current — make sure the DB is at the expected revision
  • [ ] Check migrations/versions/ for the revision file the DB is referencing
  • [ ] Confirm FLASK_APP and SQLALCHEMY_DATABASE_URI are set in your environment
  • [ ] For production migrations with existing data, handle NOT NULL columns carefully
  • [ ] Add compare_type=True to env.py to catch column type changes
  • [ ] Use include_object in env.py to exclude unmanaged tables from autogenerate

Migration errors are frustrating, but they’re almost always recoverable. The key is knowing which state Alembic thinks the database is in versus what’s actually there — flask db current and flask db history are your best diagnostic tools.

When a migration failure produces a long traceback, use Debugly’s trace formatter to quickly parse and analyze Python tracebacks and pinpoint the exact line causing the problem. You can also check out our guides on Flask SQLAlchemy session errors and Flask request context errors for related database and context issues.