Flask-Migrate is a Flask extension that wraps the powerful Alembic library, allowing you to manage database schema changes with ease.

Here’s a flask-migrate setup in action. Imagine you have a basic Flask app with a User model:

# app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
migrate = Migrate(app, db)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

    def __repr__(self):
        return f'<User {self.username}>'

First, initialize Flask-Migrate in your project. Run this command in your terminal from your Flask app’s root directory:

flask db init

This creates a migrations/ directory. Inside, you’ll find alembic.ini (Alembic’s configuration) and a versions/ folder for your migration scripts.

Now, let’s create your first migration script. After defining your User model above (or any other models), run:

flask db migrate -m "Create user table"

Alembic inspects your database models and compares them to the current database schema. It generates a Python script in migrations/versions/ (e.g., abcdef123456_create_user_table.py). This script contains upgrade() and downgrade() functions.

# migrations/versions/abcdef123456_create_user_table.py
"""Create user table

Revision ID: abcdef123456
Revises:
Create Date: 2023-10-27 10:00:00.000000

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = 'abcdef123456'
down_revision = None
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('user',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('username', sa.String(length=80), nullable=False),
    sa.Column('email', sa.String(length=120), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('email'),
    sa.UniqueConstraint('username')
    )
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('user')
    # ### end Alembic commands ###

The upgrade() function applies the changes (creating the user table in this case), and downgrade() reverts them.

To apply the migration to your database, run:

flask db upgrade

This executes the upgrade() function for any unapplied migrations, bringing your database schema in sync with your models. If you’re using SQLite, you’ll see a site.db file appear or update.

Let’s add a new column, say password_hash, to the User model:

# app.py (updated)
# ...
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(128)) # New column

    def __repr__(self):
        return f'<User {self.username}>'

Now, generate another migration:

flask db migrate -m "Add password_hash to user table"

Alembic will detect the new column and generate a script that adds password_hash to the user table.

# migrations/versions/fedcba987654_add_password_hash_to_user_table.py
"""Add password_hash to user table

Revision ID: fedcba987654
Revises: abcdef123456
Create Date: 2023-10-27 10:05:00.000000

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = 'fedcba987654'
down_revision = 'abcdef123456'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('user', sa.Column('password_hash', sa.String(length=128), nullable=True))
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('user', 'password_hash')
    # ### end Alembic commands ###

And apply it:

flask db upgrade

To revert to the previous state (remove the password_hash column), you’d use:

flask db downgrade

This command rolls back the most recent migration. You can also specify a revision ID to downgrade to a specific point: flask db downgrade abcdef123456.

The flask db history command shows you a log of all applied migrations.

The most surprising truth about database migrations is that they are fundamentally about state management, not just schema definition. Each migration script represents a transition from one known, valid database state to another. This means that even if you’re only making minor tweaks, you’re still defining a state change. The downgrade function is just as critical as upgrade because it guarantees you can always return to a previous known state, which is invaluable for debugging and rollbacks in production.

The core of Alembic (and thus Flask-Migrate) is its ability to track which migrations have been applied to a given database. It does this by creating a special table, usually named alembic_version, in your database. This table stores the revision ID of the latest migration that has been successfully applied. When you run flask db upgrade, Alembic checks this alembic_version table, compares it against the migration scripts it finds in your versions/ directory, and applies only those that are missing. When you run flask db downgrade, it removes the entry for the last migration from alembic_version and executes the corresponding downgrade() function.

This system allows you to maintain a consistent database schema across different environments (development, staging, production) by simply running the flask db upgrade command.

If you ever need to see the current state of your database schema as understood by Alembic, you can use flask db current to display the revision ID of the latest migration applied.

Want structured learning?

Take the full Flask course →