# How to Roll Back a Migration in Supabase

- Tool: Supabase
- Difficulty: Advanced
- Time required: 15-20 min
- Compatibility: Supabase CLI v1.100+, PostgreSQL 15+
- Last updated: March 2026

## TL;DR

Supabase does not have a built-in rollback command. To reverse a migration, write a new migration that undoes the changes (drop the table, remove the column, or revert the policy), then apply it with supabase db push. For local development, use supabase db reset to reapply all migrations from scratch. If a migration was applied but is broken, use supabase migration repair to mark it as reverted in the migration history.

## Rolling Back Migrations in Supabase

Unlike ORMs like Prisma that have built-in rollback commands, Supabase follows a forward-only migration strategy. When you need to undo a migration, you write a new migration that explicitly reverses the changes. This tutorial explains the three rollback strategies: writing reverse migrations for production, using supabase db reset for local development, and using supabase migration repair to fix broken migration history. You will learn when to use each approach and how to avoid data loss.

## Before you start

- Supabase CLI installed and linked to your project
- Familiarity with SQL (CREATE TABLE, ALTER TABLE, DROP statements)
- Existing migrations in your supabase/migrations/ directory
- A backup of your database if rolling back in production

## Step-by-step guide

### 1. Identify the migration to roll back

List your migrations to find the one you need to reverse. Each migration file in supabase/migrations/ has a timestamp prefix and a descriptive name. Check the supabase_migrations.schema_migrations table to see which migrations have been applied to your remote database. This tells you whether the migration has already been pushed to production or only exists locally.

```
# List local migration files
ls supabase/migrations/

# Check which migrations have been applied remotely
supabase migration list
```

**Expected result:** You can see the list of migration files and which ones have been applied to the remote database.

### 2. Write a reverse migration for production rollback

Create a new migration file that undoes the changes made by the original migration. If the original created a table, the reverse drops it. If it added a column, the reverse removes it. If it created an RLS policy, the reverse drops that policy. Be careful with destructive operations — dropping a table permanently deletes all data in it. Consider backing up the data before running the reverse migration.

```
# Create the reverse migration
supabase migration new revert_add_products_table

# Then edit the generated file:
# supabase/migrations/20260327120000_revert_add_products_table.sql
```

**Expected result:** A new empty migration file is created in supabase/migrations/ ready for your reverse SQL.

### 3. Write the reverse SQL statements

Open the generated migration file and write SQL that reverses each change from the original migration. Work in reverse order — if the original migration created a table and then added an RLS policy, first drop the policy and then drop the table. If the original added a column with data, decide whether you need to preserve that data (copy it to another column or table first) before removing the column.

```
-- supabase/migrations/20260327120000_revert_add_products_table.sql

-- Step 1: Drop RLS policies first (they reference the table)
DROP POLICY IF EXISTS "Users can view products" ON products;
DROP POLICY IF EXISTS "Users can insert products" ON products;
DROP POLICY IF EXISTS "Users can update own products" ON products;
DROP POLICY IF EXISTS "Users can delete own products" ON products;

-- Step 2: Drop indexes
DROP INDEX IF EXISTS idx_products_user_id;

-- Step 3: Drop the table
DROP TABLE IF EXISTS products;

-- For column removal instead of table drop:
-- ALTER TABLE products DROP COLUMN IF EXISTS category_id;
```

**Expected result:** The reverse migration file contains SQL statements that undo all changes from the original migration.

### 4. Apply the reverse migration

For local development, run supabase migration up to apply the new reverse migration. For production, push the migration to your remote database with supabase db push. The CLI will apply any pending migrations in order, including your reverse migration. Verify the changes by checking the table structure in the Dashboard or running a query in the SQL Editor.

```
# Apply locally
supabase migration up

# Push to production
supabase db push

# Verify the rollback
supabase db diff  # Should show no unexpected changes
```

**Expected result:** The reverse migration is applied, and the original changes are undone.

### 5. Use supabase migration repair for broken migrations

If a migration was recorded in the history table but failed partially, or if you manually reverted changes in the Dashboard and need to update the migration history, use supabase migration repair. This command marks a specific migration as either applied or reverted without actually running any SQL. Use it to fix inconsistencies between your migration files and the database state.

```
# Mark a migration as reverted (not applied)
supabase migration repair --status reverted 20260327100000

# Mark a migration as applied (already applied manually)
supabase migration repair --status applied 20260327100000

# Verify the migration status
supabase migration list
```

**Expected result:** The migration history is updated to reflect the correct state of each migration.

### 6. Use supabase db reset for local development rollback

During local development, the fastest way to roll back is supabase db reset. This drops the entire local database, reapplies all migrations from scratch, and runs the seed file. Simply delete or rename the migration file you want to undo before running reset. This approach is only for local development — never use it on a production database.

```
# Remove the migration you want to undo
rm supabase/migrations/20260327100000_add_products_table.sql

# Reset the local database (reapplies remaining migrations + seed)
supabase db reset

# Or keep the file but reset to test the full sequence
supabase db reset
```

**Expected result:** The local database is rebuilt from scratch without the removed migration.

## Complete code example

File: `supabase/migrations/20260327120000_revert_add_products_table.sql`

```sql
-- Reverse migration: Undo the add_products_table migration
-- Original migration created: products table, RLS policies, indexes
-- This migration removes all of those in reverse order

-- =============================================
-- Step 1: Remove RLS policies
-- (Must be dropped before the table they reference)
-- =============================================
DROP POLICY IF EXISTS "Anyone can view products"
  ON public.products;

DROP POLICY IF EXISTS "Authenticated users can insert products"
  ON public.products;

DROP POLICY IF EXISTS "Users can update their own products"
  ON public.products;

DROP POLICY IF EXISTS "Users can delete their own products"
  ON public.products;

-- =============================================
-- Step 2: Remove indexes
-- =============================================
DROP INDEX IF EXISTS public.idx_products_user_id;
DROP INDEX IF EXISTS public.idx_products_created_at;

-- =============================================
-- Step 3: Remove the trigger (if any)
-- =============================================
DROP TRIGGER IF EXISTS set_products_updated_at
  ON public.products;

-- =============================================
-- Step 4: Drop the table
-- (CASCADE would also drop dependent objects,
--  but explicit drops above are safer)
-- =============================================
DROP TABLE IF EXISTS public.products;
```

## Common mistakes

- **Deleting a migration file from supabase/migrations/ without updating the remote migration history** — undefined Fix: If the migration was already pushed to production, use supabase migration repair --status reverted <version> to update the migration history before removing the file.
- **Dropping a table in a reverse migration without backing up its data first** — undefined Fix: Before running the reverse migration in production, export the table data using pg_dump -t tablename or the SQL Editor CSV export. Only then apply the destructive migration.
- **Running supabase db reset on a production database instead of writing a reverse migration** — undefined Fix: Never use db reset on production. It drops and recreates the entire database. Always write a targeted reverse migration for production rollbacks.
- **Forgetting to drop RLS policies before dropping the table they reference** — undefined Fix: Drop policies, then indexes, then triggers, then the table itself. Using IF EXISTS on each statement prevents errors if some objects were already removed.

## Best practices

- Always write reverse migrations as new forward migrations rather than editing existing migration files
- Use IF EXISTS in all DROP statements to make reverse migrations idempotent
- Name reverse migrations with a revert_ prefix for clarity
- Back up production data before applying any destructive reverse migration
- Test reverse migrations locally with supabase db reset before pushing to production
- Keep migration files in version control so the full history of changes and reversals is tracked
- Use supabase migration repair only when the migration history is inconsistent with the actual database state
- Drop dependent objects (policies, indexes, triggers) before the objects they reference (tables, columns)

## Frequently asked questions

### Does Supabase have a built-in rollback command?

No. Supabase uses forward-only migrations. To undo a migration, write a new migration that reverses the changes. This is a deliberate design choice that keeps the migration history clear and auditable.

### What happens if I delete a migration file that was already pushed to production?

The remote database will still have the changes applied, but supabase migration list will show a mismatch. Use supabase migration repair --status reverted to update the history, then write a new reverse migration to undo the schema changes.

### Can I use supabase db reset on my production database?

No. supabase db reset drops and recreates the entire database. It is designed for local development only. For production, always write targeted reverse migrations.

### How do I roll back a migration that added a column with data?

First export or back up the column data if you need it. Then create a reverse migration with ALTER TABLE your_table DROP COLUMN IF EXISTS column_name. Apply it with supabase db push.

### What is supabase migration repair used for?

It updates the migration history table without running any SQL. Use it when the recorded migration state does not match the actual database state — for example, if you manually reverted changes via the Dashboard SQL Editor.

### Can I revert multiple migrations at once?

Yes, write a single reverse migration that undoes multiple original migrations. Order the DROP statements carefully — reverse the order of the original creations. Or write separate reverse migrations for each one and apply them in sequence.

### Can RapidDev help with complex database migration rollbacks?

Yes. RapidDev can audit your migration history, write safe reverse migrations, test them against a staging environment, and apply them to production with proper backup procedures in place.

---

Source: https://www.rapidevelopers.com/supabase-tutorial/how-to-roll-back-a-migration-in-supabase
© RapidDev — https://www.rapidevelopers.com/supabase-tutorial/how-to-roll-back-a-migration-in-supabase
