Database Migrations With Flask-Migrate
Introduction
db.create_all() creates tables once but cannot safely evolve schema when you add columns or rename fields. Flask-Migrate wraps Alembic to generate versioned migration scripts and apply them with flask db upgrade—the standard workflow for Flask apps in team and production environments.
Prerequisites
- Flask-SQLAlchemy and Models
- Application factory with
dbinitialized pip install Flask-Migrate
Install Flask-Migrate
pip install Flask-Migrateapp/extensions.py:
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()app/__init__.py:
from app.extensions import db, migrate
def create_app(config_name="development"):
app = Flask(__name__)
...
db.init_app(app)
migrate.init_app(app, db)
...
return appCode explanation:
migrate.init_app(app, db)registers Alembic CLI commands underflask db
Import models so Alembic sees metadata:
from app import models # noqa: F401 — inside create_app after db.init_appInitialize Migrations (Once Per Project)
export FLASK_APP=app:create_app
flask db initCreates:
migrations/
├── alembic.ini
├── env.py
├── README
└── versions/Commit migrations/ to Git—see Git basics.
Generate a Migration
After changing models:
class User(db.Model):
...
bio = db.Column(db.String(500))Create revision:
flask db migrate -m "Add bio to users"Alembic writes migrations/versions/xxxx_add_bio_to_users.py with upgrade() and downgrade().
Review the script before applying—autogenerate can miss renames or data moves.
Apply Migrations
flask db upgradeRollback one step:
flask db downgradeShow history:
flask db history
flask db currentCode explanation:
upgradeapplies pending revisions to match models- Production deploy runs
flask db upgradebefore or during app restart
Workflow Summary
Edit models → flask db migrate -m "message" → review script → flask db upgradeTeam flow:
- Developer A creates migration, commits
- Developer B pulls, runs
flask db upgrade - CI/CD runs
upgradeon staging/production
MySQL-Specific Notes
Use utf8mb4 when creating database:
CREATE DATABASE flask_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Alembic handles ALTER TABLE—align column types with MySQL data types.
Seed data is separate from migrations—use scripts or Flask CLI commands, not raw INSERT in Alembic unless intentional.
Warning
Never Edit Production DB by Hand
Manual ALTER drifts from migration history—always generate or hand-write a revision file.
Empty Database vs Existing Data
Adding nullable=False column without default fails on rows already present:
status = db.Column(db.String(20), nullable=False, server_default="active")Or multi-step migration: add nullable column → backfill → set not null.
Testing Migrations
def test_upgrade(app):
with app.app_context():
from flask_migrate import upgrade
upgrade()
user = User(username="test", email="t@example.com")
db.session.add(user)
db.session.commit()Use in-memory SQLite in TestingConfig for speed.
FAQ
flask db command not found?
Install Flask-Migrate; set FLASK_APP=app:create_app.
Target database is not up to date?
Run flask db upgrade or resolve merge heads with flask db merge.
Multiple migration heads?
Two branches created revisions—merge with flask db merge heads.
Autogenerate missed a change?
Alembic only detects model metadata—verify imports and write manual ops in revision.
SQLite limitation in dev?
Some MySQL types differ—test final migrations against MySQL before production.
Delete migrations folder?
Only before first deploy—otherwise you lose history; never on production.
Stamp without running?
flask db stamp head marks DB at revision without executing—use when aligning existing DB carefully.