Database and migrations
Version schema changes
Create, review, reset, test, and push migration files rather than relying on undocumented dashboard state.
Editing tables in the dashboard feels fast. It stays fast until the day you need a second environment and can’t say what production’s schema actually is. Migration files fix that. Every schema change becomes a SQL file, committed to Git, applied in order.
Create one with the CLI:
supabase migration new add_notes_pinned
# Created new migration at supabase/migrations/20260803141530_add_notes_pinned.sql
The timestamp in the filename is the order. Write the change in that file:
alter table notes
add column pinned boolean not null default false;
Then prove the whole history still builds from zero:
supabase db reset
# Resetting local database...
# Applying migration 20260803141530_add_notes_pinned.sql...
# Seeding data from supabase/seed.sql...
supabase db reset rebuilds the local database from nothing but your migration files and seed data. If it fails, your history is broken, and you found out on your laptop instead of during a deploy. That is the whole point. Run it after every migration you write.
Capture changes you made by hand
Sometimes you do click around in the local Studio to try something. Don’t lose that work, capture it:
supabase db diff -f add_notes_archived
This compares your migration history with the live local schema and writes the difference into a new migration file.
Treat generated diffs as drafts. The diff tool records what changed, not whether the change was wise. Read the file and look for permissions, extensions, destructive statements, and anything that touches data. A generated drop column deserves particular suspicion, because a diff has no idea whether that column held something you need.
Shipping to the remote project
Link the directory to your hosted project once, then push:
supabase link --project-ref abcdefghijkl
supabase db push --dry-run
# Would push these migrations: 20260803141530_add_notes_pinned.sql
supabase db push
The dry run previews exactly which migrations would apply. Read it before the real push. If it lists more files than you expect, stop and find out why.
The classic disaster in this workflow is drift: someone edits production through the dashboard, your local history no longer matches the remote state, and the next db push fails or half-applies. When that happens, capture the remote change with supabase db diff --linked and fold it into a proper migration file. Then the history is true again.
And never reset a production database. db reset is a local rebuilding tool. On anything shared it is data loss with a progress bar.
Try the full loop on your own project: add one column through a migration, rebuild locally, apply it to a disposable remote project, then run one old code path against it to prove the change was additive and nothing that used to work broke.
Lesson completed