Use this file to discover all available pages before exploring further.
Database migrations are version-controlled SQL files that track changes to your database schema over time. They provide a systematic way to evolve your database structure while maintaining consistency across different environments.
create table if not exists employees ( id bigint primary key generated always as identity, name text not null, email text unique, department text, created_at timestamptz default now());-- Add an indexcreate index idx_employees_department on employees(department);-- Enable Row Level Securityalter table employees enable row level security;-- Create a policycreate policy "Employees are viewable by authenticated users" on employees for select to authenticated using (true);
create table if not exists employees ( id bigint primary key generated always as identity, name text not null, email text, created_at timestamptz default now());
alter table if exists public.employees add column department text default 'Engineering';-- Add a check constraintalter table employees add constraint valid_department check (department in ('Engineering', 'Sales', 'Marketing', 'HR'));
From Supabase source - creating a content schema with error tracking:
-- Create utility schemacreate schema if not exists utils;grant usage on schema utils to anon;grant usage on schema utils to authenticated;-- Reusable timestamp update functioncreate or replace function utils.update_timestamp()returns triggerset search_path = ''language plpgsqlas $$begin new.updated_at = now(); return new;end;$$;grant execute on function utils.update_timestamp() to anon;grant execute on function utils.update_timestamp() to authenticated;-- Create content schemacreate schema if not exists content;grant usage on schema content to anon;grant usage on schema content to authenticated;-- Service tablecreate table if not exists content.service ( id uuid primary key default gen_random_uuid(), name text not null unique, created_at timestamptz default now(), updated_at timestamptz default now(), deleted_at timestamptz default null);-- Add update triggercreate trigger sync_updated_at_content_service before update on content.service for each row execute function utils.update_timestamp();-- Soft delete rulecreate or replace rule soft_delete_content_service as on delete to content.service do instead ( update content.service set deleted_at = now() where id = old.id );-- Enable RLSalter table content.service enable row level security;-- Create indexescreate index if not exists idx_content_service_id_nondeleted_only on content.service (id) where deleted_at is null;create index if not exists idx_content_service_name_nondeleted_only on content.service (name) where deleted_at is null;-- Insert seed datainsert into content.service (name) values ('AUTH'), ('REALTIME'), ('STORAGE')on conflict (name) do nothing;-- Error tracking tablecreate table if not exists content.error ( code text not null, service uuid not null references content.service (id) on delete restrict, http_status_code smallint, message text, created_at timestamptz default now(), updated_at timestamptz default now(), deleted_at timestamptz default null, primary key (service, code));create trigger sync_updated_at_content_error before update on content.error for each row execute function utils.update_timestamp();alter table content.error enable row level security;-- Grant permissionsgrant select on content.service to anon, authenticated;grant select on content.error to anon, authenticated;-- Create policiescreate policy content_service_anon_select_all on content.service for select to anon using (deleted_at is null);create policy content_error_anon_select_all on content.error for select to anon using (deleted_at is null);
-- Enable vector extensioncreate extension if not exists vector with schema public;-- Create page tablecreate table "public"."page" ( id bigserial primary key, path text not null unique, checksum text, meta jsonb);-- Create page sections with embeddingscreate table "public"."page_section" ( id bigserial primary key, page_id bigint not null references public.page on delete cascade, content text, token_count int, embedding vector(1536) -- OpenAI embeddings dimension);-- Create index for vector similarity searchcreate index on page_section using ivfflat (embedding vector_cosine_ops) with (lists = 100);
-- Create regular indexcreate index idx_products_category on products(category_id);-- Create partial indexcreate index idx_active_products on products(status) where deleted_at is null;-- Create unique indexcreate unique index idx_users_email on users(lower(email));-- Create composite indexcreate index idx_orders_user_date on orders(user_id, created_at desc);
-- First, drop dependent objectsalter table products drop constraint if exists check_price;-- Then drop the columnalter table products drop column if exists old_column;
-- Simple type changealter table products alter column price type numeric(12,2);-- With USING clause for conversionalter table products alter column price type integer using price::integer;
# Login to Supabase CLIsupabase login# Link to your projectsupabase link --project-ref your-project-ref# Push migrations to remotesupabase db push# Push with seed datasupabase db push --include-seed
# Rollback to a specific migrationsupabase migration down --target 20241205075911# Create a new migration to undo changessupabase migration new rollback_employees_department
Migration to undo changes
-- Undo the department column additionalter table employees drop column if exists department;
create table if not exists products (...);alter table if exists products add column ...;
Include rollback migrations
Always know how to undo a migration if needed.
Test migrations locally first
Use supabase db reset to test the full migration sequence.
Keep migrations focused
One migration should do one thing. Split complex changes into multiple migrations.
Add comments
Explain why a migration was needed:
-- Add department column to support new org structure-- See: https://github.com/yourorg/project/issues/123alter table employees add column department text;
Use transactions
Migrations run in transactions by default. For explicit control:
create extension if not exists "uuid-ossp" with schema extensions;create extension if not exists pgvector with schema extensions;create extension if not exists postgis with schema public;
alter table products enable row level security;create policy "Users can view published products" on products for select to anon, authenticated using (status = 'published');create policy "Users can manage their own products" on products for all to authenticated using (auth.uid() = user_id);
-- Create functioncreate or replace function update_updated_at()returns trigger as $$begin new.updated_at = now(); return new;end;$$ language plpgsql;-- Create triggercreate trigger update_products_updated_at before update on products for each row execute function update_updated_at();