Supabase migrations are versioned SQL files stored in supabase/migrations/ that track every schema change. Use the Supabase CLI to create migrations with supabase migration new, apply them locally with supabase migration up, pull remote changes with supabase db pull, and deploy to production with supabase db push. This gives you version-controlled, reproducible database schemas across environments.
Managing Database Schema Changes with Supabase Migrations
Migrations are the standard way to version-control your database schema. Instead of making ad-hoc changes in the Dashboard SQL Editor, you write SQL files that can be committed to Git, reviewed in pull requests, and applied consistently across local, staging, and production environments. This tutorial covers the full migration workflow from initialization to production deployment.
Prerequisites
- Supabase CLI installed (brew install supabase/tap/supabase or npm install supabase --save-dev)
- A Supabase project (local or remote)
- Docker Desktop installed and running (required for supabase start)
- Basic SQL knowledge for writing CREATE TABLE and ALTER TABLE statements
Step-by-step guide
Initialize the Supabase project and start the local stack
Initialize the Supabase project and start the local stack
Run supabase init to create the supabase/ directory structure in your project. This creates config.toml (project configuration), a migrations/ folder (for SQL migration files), and a seed.sql file (for test data). Then run supabase start to launch the local development stack, which includes PostgreSQL, Auth, Storage, Realtime, and Studio. The local stack gives you the same environment as production without affecting your remote database.
1# Initialize the project structure2supabase init34# Start the local development stack (requires Docker)5supabase start67# Output shows local URLs and keys:8# API URL: http://localhost:543219# Studio URL: http://localhost:5432310# anon key: eyJ...11# service_role key: eyJ...Expected result: The supabase/ directory is created with config.toml and migrations/ folder. The local stack is running with PostgreSQL accessible at localhost.
Create your first migration file
Create your first migration file
Use supabase migration new to create a timestamped SQL file in the migrations/ folder. The filename format is YYYYMMDDHHMMSS_description.sql. Write your CREATE TABLE, ALTER TABLE, or other DDL statements in this file. Migrations run in order based on the timestamp prefix, so always use the CLI to generate the filename. Never rename migration files after they have been applied.
1# Create a new migration2supabase migration new create_posts_table34# This creates: supabase/migrations/20260327120000_create_posts_table.sql5# Open the file and write your SQL:67-- supabase/migrations/20260327120000_create_posts_table.sql8create table public.posts (9 id bigint generated always as identity primary key,10 title text not null,11 content text,12 author_id uuid not null references auth.users on delete cascade,13 published boolean default false,14 created_at timestamptz default now(),15 updated_at timestamptz default now()16);1718-- Always enable RLS on new tables19alter table public.posts enable row level security;2021-- Add RLS policies22create policy "Public can read published posts"23 on public.posts for select24 to anon, authenticated25 using (published = true);2627create policy "Authors can manage own posts"28 on public.posts for all29 to authenticated30 using ((select auth.uid()) = author_id)31 with check ((select auth.uid()) = author_id);3233-- Add index for RLS policy performance34create index idx_posts_author_id on public.posts using btree (author_id);Expected result: A new migration file is created in supabase/migrations/ with your table schema, RLS policies, and indexes.
Apply migrations to the local database
Apply migrations to the local database
Run supabase migration up to apply all pending migrations to the local database. You can also use supabase db reset to drop the entire local database and reapply all migrations from scratch, plus the seed.sql file. Use db reset when you want a clean slate or when testing that migrations run correctly in order. The local migration state is tracked in the supabase_migrations.schema_migrations table.
1# Apply pending migrations to the local database2supabase migration up34# Or reset the entire local database (drops + reapplies all migrations + seed)5supabase db reset67# List applied migrations8supabase migration listExpected result: All migration files are applied to the local database in timestamp order. Tables, policies, and indexes exist as defined in the migrations.
Generate migrations from Dashboard changes using db diff
Generate migrations from Dashboard changes using db diff
If you made schema changes in the local Studio Dashboard instead of writing SQL manually, use supabase db diff to generate a migration file from the difference. This compares the current database state against the last applied migration and outputs the SQL needed to reproduce the change. Review the generated SQL carefully — it may include unnecessary statements or miss subtleties like RLS policies.
1# Generate a migration from Dashboard changes2supabase db diff -f add_categories_table34# This creates a migration file with the detected changes5# Review the generated file before committing6cat supabase/migrations/20260327130000_add_categories_table.sql78# If you need to diff against the remote database9supabase db diff --linkedExpected result: A new migration file is created containing the SQL diff between the current schema and the last migration state.
Pull the remote schema as a local migration
Pull the remote schema as a local migration
If you started building in the Dashboard and want to switch to a migration-based workflow, use supabase db pull to download the remote database schema as a migration file. First link your local project to the remote one with supabase link, then run db pull. This creates a baseline migration that captures the current state of your production database. All future changes should be made through new migration files.
1# Link to your remote Supabase project2supabase link --project-ref your-project-ref34# Pull the remote schema as a local migration5supabase db pull67# This creates a migration file with the complete remote schema8# Now you can start making changes via new migrations9supabase migration new add_comments_tableExpected result: A migration file is created containing the complete schema of your remote database. Your local project now has a baseline for future migrations.
Add seed data for local development
Add seed data for local development
Create or edit supabase/seed.sql to insert test data that populates your local database during supabase db reset. Seed data is only for development — it never runs on production. Write idempotent INSERT statements or use ON CONFLICT DO NOTHING to prevent errors when seeds run multiple times. Seed data makes it easy for team members to start with a working local database.
1-- supabase/seed.sql2-- Insert test data for local development34insert into public.posts (title, content, author_id, published)5values6 ('Getting Started with Supabase', 'A beginner guide...', '00000000-0000-0000-0000-000000000000', true),7 ('Advanced RLS Patterns', 'Deep dive into policies...', '00000000-0000-0000-0000-000000000000', true),8 ('Draft Post', 'Work in progress...', '00000000-0000-0000-0000-000000000000', false)9on conflict do nothing;Expected result: Running supabase db reset applies all migrations and then inserts seed data, giving you a fully populated local database for development.
Deploy migrations to production
Deploy migrations to production
When your migrations are tested locally and committed to Git, deploy them to your remote Supabase project with supabase db push. This applies all migrations that have not yet been applied on the remote database. The command shows a preview of the SQL that will run and asks for confirmation. Never run db push without testing locally first — there is no automatic rollback for production migrations.
1# Link to remote project if not already linked2supabase link --project-ref your-project-ref34# Push all pending migrations to production5supabase db push67# The command shows which migrations will be applied8# and asks for confirmation before executingExpected result: All pending migrations are applied to the remote production database. The schema matches your local development environment.
Complete working example
1-- Migration: Create posts table with RLS and indexes2-- Created with: supabase migration new create_posts_table3-- Apply with: supabase migration up4-- Deploy with: supabase db push56-- Create the posts table7create table public.posts (8 id bigint generated always as identity primary key,9 title text not null,10 content text,11 author_id uuid not null references auth.users on delete cascade,12 published boolean default false,13 metadata jsonb default '{}',14 created_at timestamptz default now(),15 updated_at timestamptz default now()16);1718-- Enable Row Level Security19alter table public.posts enable row level security;2021-- RLS: Anyone can read published posts22create policy "Published posts are publicly readable"23 on public.posts for select24 to anon, authenticated25 using (published = true);2627-- RLS: Authors can read all their own posts (including drafts)28create policy "Authors can read own posts"29 on public.posts for select30 to authenticated31 using ((select auth.uid()) = author_id);3233-- RLS: Authors can insert their own posts34create policy "Authors can create posts"35 on public.posts for insert36 to authenticated37 with check ((select auth.uid()) = author_id);3839-- RLS: Authors can update their own posts40create policy "Authors can update own posts"41 on public.posts for update42 to authenticated43 using ((select auth.uid()) = author_id)44 with check ((select auth.uid()) = author_id);4546-- RLS: Authors can delete their own posts47create policy "Authors can delete own posts"48 on public.posts for delete49 to authenticated50 using ((select auth.uid()) = author_id);5152-- Performance indexes53create index idx_posts_author_id on public.posts using btree (author_id);54create index idx_posts_published on public.posts using btree (published);55create index idx_posts_created_at on public.posts using btree (created_at desc);Common mistakes when using Supabase Migrations
Why it's a problem: Making schema changes in the Dashboard SQL Editor without creating a corresponding migration file
How to avoid: Always use supabase migration new to create a migration file, or use supabase db diff to capture Dashboard changes as a migration. Untracked changes will be lost on db reset and will not be deployed to other environments.
Why it's a problem: Editing or renaming a migration file after it has been applied to any database
How to avoid: Never modify applied migrations. Create a new migration to make additional changes. If you need to fix a broken migration, use supabase migration repair to mark it and then create a corrective migration.
Why it's a problem: Running supabase db push to production without testing migrations locally first
How to avoid: Always run supabase db reset locally to verify all migrations apply cleanly from scratch before pushing to production. There is no automatic rollback for production migrations.
Why it's a problem: Forgetting to include RLS policies in migration files, leaving new tables exposed
How to avoid: Include ALTER TABLE ... ENABLE ROW LEVEL SECURITY and all CREATE POLICY statements in the same migration that creates the table. Treat RLS as part of the table definition.
Best practices
- Write one migration per logical change — do not combine unrelated schema changes in a single migration file
- Include RLS policies, indexes, and grants in the same migration as the table creation
- Always test migrations with supabase db reset before committing to verify they apply cleanly from scratch
- Use supabase db diff to capture Dashboard changes, but always review and clean up the generated SQL
- Write idempotent seed data with ON CONFLICT DO NOTHING to prevent errors on repeated resets
- Add supabase db push to your CI/CD pipeline to automate production deployments
- Keep migration files small and focused — this makes them easier to review, debug, and roll back
- Never use supabase db push with the --include-all flag in production without thorough review
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Supabase project where I have been making changes in the Dashboard. Help me set up a migration-based workflow: pull the current schema, create new migrations for future changes, and deploy them to production using the Supabase CLI.
Create a migration file that creates a posts table with id, title, content, author_id (references auth.users), published boolean, and timestamps. Include RLS policies for public read of published posts and author-only CRUD. Add btree indexes on author_id and published columns.
Frequently asked questions
What is the difference between supabase db push and supabase migration up?
supabase migration up applies migrations to your local database. supabase db push applies migrations to the remote production database. Always test locally with migration up or db reset before deploying to production with db push.
Can I roll back a migration after deploying to production?
Supabase does not have automatic rollback. You need to create a new migration that reverses the changes (e.g., DROP TABLE, ALTER TABLE DROP COLUMN). For critical failures, you can use supabase migration repair to mark a migration as reverted and then apply a corrective migration.
What happens if two developers create migrations with the same timestamp?
Migration timestamps are generated to the second, so conflicts are unlikely. If they do occur, rename the timestamp of the later migration before applying. Git merge conflicts in the migrations/ folder will alert you to this issue.
Should I commit migration files to Git?
Yes, always commit migration files to Git. They are the source of truth for your database schema. This lets team members sync their local databases and enables automated deployments via CI/CD.
How do I handle migrations when multiple team members are working on the same project?
Each developer creates migrations on their own branch. During code review, check for conflicting migrations. After merging, each developer runs supabase db reset to apply all migrations in order. Use a CI job to verify migrations apply cleanly on every pull request.
Can I use Prisma migrations with Supabase instead of Supabase migrations?
Yes, but be careful. Prisma migrations can conflict with Supabase's auth schema and RLS policies. If you use Prisma, manage only your application tables with Prisma and handle RLS and auth-related schemas with Supabase migrations.
Can RapidDev help set up a migration workflow for my Supabase project?
Yes, RapidDev can configure your migration workflow, set up CI/CD pipelines for automated deployments, and help establish best practices for team collaboration on database schemas.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation