Knowledge > Runbooks > Technical Ops > Run a Database Schema Migration
Run a Database Schema Migration
Apply a schema change to the shared Supabase production database safely and repeatably.
Prerequisites
- Access to the Supabase SQL editor OR the
mcp__plugin_supabase_supabase__execute_sqlMCP tool - Migration SQL written and reviewed
- Founder approval if the migration includes any DROP, large UPDATE, or column removal
- Feature branch checked out in the relevant codebase
Steps
-
Create the migration file
Name the file using the pattern
YYYYMMDD_short_description.sqland place it in the shared migrations directory:pewsearch/migrations/20260325_add_index_churches_slug.sql -
Write safe SQL
-- 20260325_add_index_churches_slug.sql-- Purpose: Speed up church detail page lookups by slug-- NOTE: CREATE INDEX CONCURRENTLY cannot run inside a transaction block.-- Run the index creation as a standalone statement OUTSIDE BEGIN/COMMIT.CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_churches_slugON churches (slug)WHERE directory_visible = true;-- Verify: check index existsSELECT indexname FROM pg_indexes WHERE tablename = 'churches' AND indexname = 'idx_churches_slug';If you have multiple DDL changes (column additions, constraints, etc.), wrap them in
BEGIN; ... COMMIT;— but keep anyCREATE INDEX CONCURRENTLYstatements outside that block.Rules for safe SQL:
- Use
IF NOT EXISTSandIF EXISTSguards CREATE INDEX CONCURRENTLYcannot run inside a transaction block — run it as a standalone statement- Never use plain
CREATE INDEXon large tables (blocks reads) — always useCONCURRENTLY - Never use
DROP TABLE,TRUNCATE, or bulkDELETEwithout founder approval
- Use
-
Test the query logic on a non-critical table first
Before running on
churchesorunified_rag_content, run aSELECTversion of your logic to confirm it targets the right rows:SELECT count(*) FROM churches WHERE directory_visible = true LIMIT 10; -
Get founder approval if required
Require founder approval before proceeding if the migration includes:
- Any
DROP COLUMN,DROP TABLE, orDROP INDEX - Any
UPDATEaffecting more than ~10K rows - Any change to
unified_rag_content(327K irreplaceable rows) - Any change to RLS policies or auth-related tables
- Any
-
Run the migration
Option A — Supabase MCP tool:
mcp__plugin_supabase_supabase__execute_sqlproject_id: wrwkszmobuhvcfjipasiquery: [paste migration SQL]Option B — Supabase SQL editor:
- Go to
https://supabase.com/dashboard/project/wrwkszmobuhvcfjipasi/sql - Paste the SQL and run
- Go to
-
Verify the migration applied
Run a verification query to confirm the change is in place:
-- Example: confirm index existsSELECT indexname, tablename FROM pg_indexesWHERE indexname = 'idx_churches_slug';-- Example: confirm new column existsSELECT column_name, data_type FROM information_schema.columnsWHERE table_name = 'churches' AND column_name = 'new_column'; -
Update knowledge docs if schema referenced
Search
C:\dev\knowledge\for any document that references the changed table or column. Update the relevant doc in the same commit as the migration file. -
Commit the migration file to version control
git add pewsearch/migrations/20260325_add_index_churches_slug.sqlgit commit -m "feat: add index on churches.slug for directory_visible rows"
Verification
- Query returns expected results without errors
- Application routes that use the changed schema return correct data
- No new errors in
ops_error_reportsafter deploying dependent code
Rollback
Write a rollback migration file (YYYYMMDD_rollback_description.sql) that reverses the change:
DROP INDEX CONCURRENTLY IF EXISTS idx_churches_slug;
Run it through the same process. Never reverse a migration by editing the original file.