Supabase CLI migrations are not just a way to track schema changes; they are the single source of truth for your database’s evolution, fundamentally dictating its state across environments.

Let’s see how this plays out with a simple example. Imagine you’re building a users table and then decide to add an email_verified boolean column.

Here’s your initial migration file (supabase/migrations/0001_create_users_table.sql):

create table users (
  id uuid primary key default gen_random_uuid(),
  email text not null unique,
  created_at timestamp with time zone default timezone('utc'::text, now())
);

After running supabase migration up, your database has this table.

Now, you want to add that email_verified column. You’d generate a new migration:

supabase migration generate add_email_verified_to_users

This creates a new file, say supabase/migrations/0002_add_email_verified_to_users.sql, with content like this:

-- This migration was generated by Supabase CLI
-- ... (timestamp and author info)

alter table "public"."users" add column "email_verified" boolean default false;

When you run supabase migration up again, the CLI applies this ALTER TABLE statement to your database, adding the new column. The magic here is that the CLI keeps track of which migrations have been applied to which database, ensuring consistency.

The core problem Supabase migrations solve is the chaos of manual database changes. Without a system, you’d be manually running SQL scripts, potentially forgetting steps, applying them inconsistently across development, staging, and production, and having no clear audit trail. This leads to "works on my machine" syndrome and production outages.

Internally, the Supabase CLI uses a special table in your database, typically named schema_migrations, to record which migration files have been applied. Each migration file has a timestamp prefix (e.g., 1678886400000_create_users_table.sql). When you run supabase migration up, the CLI checks the schema_migrations table, finds the highest applied timestamp, and then executes all subsequent migration files in chronological order.

The key levers you control are:

  • Migration Files: These are plain SQL files. You write CREATE TABLE, ALTER TABLE, INSERT, UPDATE, DELETE statements here. They are version-controlled alongside your code.
  • Migration Generation: The supabase migration generate <name> command is your best friend. It creates a new, timestamped SQL file, so you don’t have to worry about ordering.
  • Migration Application: supabase migration up applies pending migrations. supabase migration down (or supabase migration goto <timestamp>) reverts them.
  • Environment Sync: The CLI ensures that the migrations applied to your local database match what’s intended for your remote Supabase project.

The supabase db reset command is a powerful tool for development, but it completely wipes and reapplies all migrations from scratch. This is invaluable for ensuring a clean slate and testing your entire migration history, but it’s a destructive operation and should never be used on a production database.

The next concept you’ll grapple with is how to handle complex data transformations within migrations without causing downtime.

Want structured learning?

Take the full Supabase course →