Skip to content

Supabase Migrations — Version Control for Your Database Schema

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Supabase Migrations. We cover key concepts, practical examples, and best practices to help you master this topic.

Supabase migrations use versioned SQL files to track and apply database schema changes, enabling reproducible database states across local development, staging, and production environments.

What You'll Learn

By the end of this lesson you will create Migration files using the Supabase CLI, apply migrations to local and remote databases, roll back changes, and integrate migrations into your deployment workflow.

Why It Matters

Without migrations, schema changes are manual and unrepeatable. Migrations ensure every environment has the same schema, changes are reviewed in pull requests, and rollbacks are possible when things go wrong.

Real-World Use

DodaZIP uses Supabase migrations for all schema changes. A developer creates a migration, reviews it in a Pull Request, applies it to staging for testing, and then deploys to production via CI/CD.

flowchart LR
    A[Developer] -->|Write migration| B[Migration SQL File]
    B -->|git commit| C[Git Repository]
    C -->|CI/CD| D[Staging DB]
    C -->|CI/CD| E[Production DB]
    D -->|Test passes| E
    style C fill:#3ecf8e,color:#fff

Creating Migrations

Generate migration files with the Supabase CLI.

# Initialize Supabase project
supabase init

# Create a new migration
supabase migration new add_files_table

# This creates:
# supabase/migrations/<timestamp>_add_files_table.sql

# Edit the SQL file with your schema changes
-- supabase/migrations/<timestamp>_add_files_table.sql
-- Migration: Add files table

CREATE TABLE IF NOT EXISTS files (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    size_bytes BIGINT NOT NULL DEFAULT 0,
    mime_type TEXT,
    storage_path TEXT,
    status TEXT DEFAULT 'pending',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_files_user_id ON files(user_id);
CREATE INDEX idx_files_status ON files(status);

ALTER TABLE files ENABLE ROW LEVEL SECURITY;

Applying Migrations

Apply migrations to your local and remote databases.

# Apply all pending migrations to local database
supabase migration up

# Apply to remote Supabase project
supabase db push

# Check migration status
supabase migration list

# View applied migrations
supabase db remote changes
# migration_workflow.py
# Migration workflow steps

def migration_workflow():
    steps = [
        "supabase init -- Initialize local Supabase project",
        "supabase migration new <name> -- Create migration file",
        "Write SQL migration in the generated file",
        "supabase migration up -- Apply to local DB",
        "supabase db push -- Apply to remote Supabase project",
        "supabase migration list -- Verify applied migrations",
    ]
    
    print("Migration Workflow:")
    for step in steps:
        print(f"  {step}")

migration_workflow()

Migration Patterns

Common migration patterns and best practices.

-- 001_add_tables.sql
-- Initial schema creation

CREATE TABLE projects (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name TEXT NOT NULL,
    user_id UUID REFERENCES auth.users(id),
    created_at TIMESTAMPTZ DEFAULT NOW()
);

---

-- 002_add_description.sql
-- Adding a column (safe, no data loss)

ALTER TABLE projects ADD COLUMN description TEXT;

---

-- 003_rename_column.sql
-- Rename a column (use RENAME COLUMN)

ALTER TABLE projects RENAME COLUMN description TO summary;

---

-- 004_add_index.sql
-- Performance index

CREATE INDEX idx_projects_user_id ON projects(user_id);

---

-- 005_change_type.sql
-- Change column type (requires USING clause)

ALTER TABLE projects ALTER COLUMN status TYPE TEXT 
  USING status::TEXT;
# migration_patterns.py
# Common migration patterns

def describe_patterns():
    patterns = {
        "Add column": "ALTER TABLE table ADD COLUMN column type;",
        "Drop column": "ALTER TABLE table DROP COLUMN column;",
        "Rename column": "ALTER TABLE table RENAME COLUMN old TO new;",
        "Change type": "ALTER TABLE table ALTER COLUMN col TYPE newtype USING col::newtype;",
        "Add index": "CREATE INDEX name ON table(column);",
        "Add constraint": "ALTER TABLE table ADD CONSTRAINT name CHECK (condition);",
        "Enable RLS": "ALTER TABLE table ENABLE ROW LEVEL SECURITY;",
        "Create policy": "CREATE POLICY name ON table FOR SELECT USING (condition);",
    }
    
    print("Common Migration Patterns:")
    for pattern, sql in patterns.items():
        print(f"  {pattern:20s} | {sql}")

describe_patterns()

Migration Safety

Write safe, reversible migrations.

# migration_safety.py
# Migration safety checklist

def migration_safety_checklist():
    checks = [
        "Test migration on a local copy of production data",
        "Write DOWN migration for rollback capability",
        "Avoid long-running locks on large tables",
        "Use IF NOT EXISTS / IF EXISTS for idempotent migrations",
        "Do not modify data in the same migration as schema changes",
        "Include default values for new NOT NULL columns",
        "Run ANALYZE after bulk data changes",
    ]
    
    print("Migration Safety Checklist:")
    for check in checks:
        print(f"  [ ] {check}")

migration_safety_checklist()

Common Mistakes

  1. Not testing migrations locally: Applying an untested migration can lock or crash your production database. Always test locally first.

  2. Forgetting to back up before migrations: Take a backup before applying migrations to production. Recovery without a backup may be impossible.

  3. Adding NOT NULL columns without defaults: Adding a NOT NULL column to a table with existing rows fails unless you provide a DEFAULT value.

  4. Mixing schema and data changes: Separate schema changes from data migrations. Data changes are harder to roll back.

  5. Writing irreversible migrations: Always consider how to reverse a migration. Some changes (like DROP COLUMN) are destructive and require a restore from backup.

Practice Questions

  1. What command creates a new migration file? supabase migration new <name>.

  2. How do you apply migrations to a remote project? supabase db push.

  3. Why should you test migrations locally? To catch errors before they affect production data or availability.

  4. What does IF NOT EXISTS do in a migration? It makes the migration idempotent -- safe to run multiple times without errors.

  5. Challenge: Create a migration that adds a team_id column to an existing projects table, adds a foreign key constraint, creates an index, and writes the rollback migration.

FAQ

Can I use other migration tools with Supabase?

Yes. You can use any PostgreSQL migration tool, including Flyway, Sqitch, or dbmate.

What happens if a migration fails?

The migration stops at the failed point. Fix the issue and re-run remaining migrations.

Does Supabase support rollback?

Supabase does not have a built-in rollback command. Write manual DOWN migrations or use pg_restore.

Are migrations run automatically on deploy?

You must run supabase db push as part of your CI/CD pipeline. Supabase does not auto-run migrations.

Can I modify a previous migration?

Never modify a migration that has been applied to production. Create a new migration with the fix instead.

Mini Project

Create a migration plan for adding a new feature to an existing application: a comments table with proper indexes, RLS policies, and a migration to backfill existing data.

def migration_plan():
    migrations = [
        "001_create_comments_table.sql -- Create table with id, user_id, file_id, content, created_at",
        "002_add_comments_indexes.sql -- Add indexes on user_id and file_id",
        "003_enable_comments_rls.sql -- Enable RLS on comments table",
        "004_create_comments_policies.sql -- Users can CRUD own comments",
        "005_backfill_existing_data.sql -- Optional: import legacy comments",
    ]
    
    print("Feature Migration Plan:")
    for m in migrations:
        print(f"  {m}")

migration_plan()

What's Next

Next: Supabase Local Development for local development setup.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro