Skip to main content

Migration Hooks Reference

Framework M supports modular, app-specific hooks that execute during the declarative synchronization process. These hooks allow you to perform manual database preparation or post-sync cleanup without needing to manage full Alembic version files for every small change.

Hook Discovery

The migration engine automatically scans the migrations module (specifically migrations/__init__.py) of every app listed in your INSTALLED_APPS (or discovered via entry points).

File Location: your_app/migrations/__init__.py

Available Hooks

before_sync(engine: Engine)

Executed before the declarative DDL sync begins.

  • When to use:
    • Creating database schemas or extensions required by your DocTypes.
    • Renaming legacy tables so the sync engine identifies them correctly.
    • Pre-calculating data required for new columns.
  • Arguments:
    • engine: A standard SQLAlchemy Engine instance connected to the target database.

after_sync(engine: Engine)

Executed after the declarative DDL sync completes successfully.

  • When to use:
    • Populating default data for newly created tables.
    • Creating complex database views or triggers that aren't managed by SQLAlchemy models.
    • Updating system settings or caches that depend on the new schema.
  • Arguments:
    • engine: A standard SQLAlchemy Engine instance connected to the target database.

Example Implementation

# my_app/migrations/__init__.py
from sqlalchemy import text

def before_sync(engine):
with engine.begin() as conn:
# Ensure a specific Postgres extension is available
conn.execute(text("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";"))
print(" [my_app] Checked for UUID extension.")

def after_sync(engine):
with engine.begin() as conn:
# Populate a default record if the table was just created
conn.execute(text("""
INSERT INTO my_table (id, name)
SELECT 'default', 'System Default'
WHERE NOT EXISTS (SELECT 1 FROM my_table WHERE id = 'default');
"""))
print(" [my_app] Ensured system default record.")

Safety and Idempotency

Hooks should always be idempotent. Since m migrate sync can be run multiple times (and is run automatically in m migrate all), your hooks must check for the existence of objects before creating or modifying them.

  • Use CREATE ... IF NOT EXISTS.
  • Use INSERT ... WHERE NOT EXISTS.
  • Wrap destructive operations in checks.

Execution Order and Independence

When running m migrate all or m migrate sync:

  1. All before_sync hooks from all discovered apps are executed.
  2. The Declarative Sync Engine performs the DDL synchronization.
  3. All after_sync hooks are executed.

[!IMPORTANT] Treat hooks as independent. While hooks currently execute in the order apps are loaded, you should never design hooks that depend on the execution of a hook in another app. Each hook should be self-contained and assume it is running in isolation. If you have cross-app dependencies, consider using an explicit Alembic migration with depends_on.