Migration File Structure — Complete Guide
In this tutorial, you will learn about Migration File Structure. We cover key concepts, practical examples, and best practices to help you master this topic.
Learn migration file structure: naming conventions, upgrade/downgrade functions, metadata headers, dependency declarations, and organizing migration files for maintainability across teams.
What You Learn
You will learn how to structure migration files properly: naming conventions that prevent conflicts, upgrade and downgrade function patterns, metadata for tracking, dependency declarations for ordered execution, and organizational best practices.
Why It Matters
Well-structured migration files are maintainable over years of development. Poorly structured files cause naming conflicts, ordering issues, and confusion about what each migration does. Consistent structure makes migrations reviewable and reliable.
Real-World Use
DodaTech's Django application has 847 migrations across 15 apps. Each migration follows a strict naming convention and structure. New developers can understand any migration by its file name and header. Migration reviews take minutes instead of hours.
Naming Convention
# Timestamp-based naming (recommended)
# Format: YYYYMMDD_HHMMSS_description.py
#
# 20260628_120000_add_phone_to_users.py
# 20260628_130000_create_orders_table.py
# 20260628_140000_add_foreign_key_to_orders.py
# Sequential naming (simple projects)
# 001_add_phone_to_users.py
# 002_create_orders_table.py
# 003_add_foreign_key_to_orders.py
# Description guidelines
# GOOD: 20260628_120000_add_phone_to_users.py
# GOOD: 20260628_130000_create_orders_table.py
# BAD: 20260628_120000_fix.py (describes what, not why)
# BAD: 20260628_130000_migration.py (duplicate name risk)
def get_migration_name(description):
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
safe_desc = description.lower().replace(' ', '_').replace('-', '_')[:50]
return f'{timestamp}_{safe_desc}'
Expected output: Migration names include a timestamp or sequence number and a description. Descriptions are concise and descriptive. Names are unique and ordered chronologically.
Alembic Migration Structure
"""add phone column to users table
Revision ID: abc123def456
Revises: prev_revision_id
Create Date: 2026-06-28 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# Revision identifiers
revision = 'abc123def456'
down_revision = 'prev_revision_id'
branch_labels = None
depends_on = None
def upgrade():
op.add_column('users',
sa.Column('phone', sa.String(20), nullable=True)
)
op.create_index('idx_users_phone', 'users', ['phone'])
def downgrade():
op.drop_index('idx_users_phone', table_name='users')
op.drop_column('users', 'phone')
Expected output: Alembic migration includes a docstring with description, revision ID, parent revision, and timestamp. The upgrade() function applies changes. The downgrade() function reverts them.
Flyway Migration Structure
-- Flyway SQL migration
-- File: V20260628_120000__add_phone_to_users.sql
-- Version: V (versioned), U (undo), R (repeatable)
-- Description: Add phone column to users table
-- Author: developer@example.com
-- Date: 2026-06-28
-- UP migration
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
CREATE INDEX idx_users_phone ON users (phone);
-- Note: Flyway does not support undo automatically.
-- Use separate U migration files for rollback.
-- Flyway undo migration
-- File: U20260628_120000__add_phone_to_users.sql
-- DOWN migration
DROP INDEX IF EXISTS idx_users_phone;
ALTER TABLE users DROP COLUMN IF EXISTS phone;
Expected output: Flyway uses SQL files with version prefix (V), description, and double-underscore separator. Undo migrations use U prefix. Repeatable migrations use R prefix and are re-applied when checksum changes.
Knex Migration Structure
// Knex migration
// File: 20260628120000_add_phone_to_users.js
exports.up = function(knex) {
return knex.schema.table('users', function(table) {
table.string('phone', 20).nullable();
table.index('phone', 'idx_users_phone');
});
};
exports.down = function(knex) {
return knex.schema.table('users', function(table) {
table.dropIndex('phone', 'idx_users_phone');
table.dropColumn('phone');
});
};
Expected output: Knex migration exports up and down functions. Each function receives the knex instance. Schema Builder methods make migrations database-agnostic. Migrations are ordered by timestamp prefix.
Django Migration Structure
# Django auto-generated migration
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True # First migration for this app
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='phone',
field=models.CharField(max_length=20, blank=True, null=True),
),
migrations.AddIndex(
model_name='user',
index=models.Index(fields=['phone'], name='idx_users_phone'),
),
]
Expected output: Django migrations define dependencies on previous migrations and list operations. Operations are ORM-agnostic. Django auto-generates migrations from model changes.
Migration Organization
const migrationDirectoryStructure = {
// Good: flat directory with all migrations
'migrations/': [
'20260628_120000_add_phone_to_users.py',
'20260628_130000_create_orders_table.py',
'20260628_140000_add_foreign_key.py',
],
// Good: separated by schema/app
'migrations/users/': [
'20260628_120000_add_phone.py',
],
'migrations/orders/': [
'20260628_130000_create_table.py',
],
// Grouped by release
'migrations/releases/': [
'v2.0/',
'v2.1/',
],
};
Expected output: Migrations are organized in a flat directory or separated by schema/app. Flat directories are simpler. Schema-separated directories scale better for large projects with many schemas.
Common Mistakes
1. Unclear Migration Descriptions
A migration named fix.py or migration.py requires opening the file to understand what it does. Use descriptive names: add_phone_to_users, create_orders_table. Descriptions should describe the change.
2. Missing Dependency Declaration
If migration B depends on table created in migration A, but B does not declare A as a dependency, B may run before A. Always declare dependencies. Migration tools enforce ordered execution based on dependencies.
3. Mixing Schema and Data Changes in One Migration
Schema changes and data migrations have different failure modes. Schema changes are fast and transactional. Data migrations are slow and may need batching. Separate them into different migration files.
4. Not Committing Migration Files
Migration files in .gitignore are invisible to the team. Each team member must independently create the same migrations. Always commit migration files. They are part of the codebase.
5. Editing Applied Migrations
Editing a migration that has already been applied changes its hash. Migration tools detect the change and refuse to proceed. Create a new migration for the correction instead.
Practice Questions
1. What is the recommended naming convention for migration files?
Timestamp-based: YYYYMMDD_HHMMSS_description.py. Timestamps prevent conflicts, ensure ordering, and provide chronological history. Descriptions should be concise and descriptive.
2. Why do migrations need both upgrade and downgrade functions?
Upgrade applies the change. Downgrade reverts it. The downgrade enables rollback to the previous schema version. Without it, rollback requires manual reverse engineering.
3. How do migration tools know the order to apply migrations?
Through dependency declarations. Each migration references the previous migration it depends on. The tool builds a dependency graph and applies migrations in the correct order.
4. What is the difference between versioned and repeatable migrations (Flyway)?
Versioned migrations (V prefix) are applied once in order. Repeatable migrations (R prefix) are re-applied when content changes. Use versioned for schema changes. Use repeatable for views, functions, and procedures.
Challenge
Design a migration file structure for a multi-service application with: shared schema (users, accounts), service-specific schemas (orders, payments, analytics), repeatable migrations for views and functions, CI/CD integration requiring dependency ordering, and support for 10+ developers working concurrently.
FAQ
Mini Project: Migration Structure Setup
Set up migration files for a new application with: Alembic initialization, three migration files (users table, orders table, foreign key), proper naming convention, complete docstrings and metadata, upgrade and downgrade functions, and dependency declarations between migrations.
What's Next
Now that you understand migration file structure, learn about Up and Down Methods for writing effective migration operations.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro