Managing Supabase Schema Migrations

D5.0

Safe workflow for database schema changes with automatic type generation and RLS validation.

intermediateCoding & Developmentcodingclaude-skill
Get This Skill on GitHub

Overview


name: Managing Supabase Schema Migrations description: Guides creation, validation, and application of Supabase database migrations with RLS policy checks and type generation. Use when adding tables, modifying schema, or updating database structure.

Managing Supabase Schema Migrations

Safe workflow for database schema changes with automatic type generation and RLS validation.

When to Use This Skill

  • Adding new tables
  • Modifying existing columns
  • Creating indexes
  • Adding or updating RLS policies
  • Any database schema changes

Migration Workflow

Follow these steps for safe migrations:

Step 1: Create Migration File

# Create a new migration with descriptive name
supabase migration new add_recipe_tags_table

# Or with bash helper
bash .claude/skills/supabase-migrations/scripts/create-migration.sh "add_recipe_tags_table"

This creates: supabase/migrations/[timestamp]_add_recipe_tags_table.sql

Step 2: Write Migration SQL

Edit the generated file with your schema changes.

Example: Adding a new table

-- Create recipe_tags table
CREATE TABLE IF NOT EXISTS public.recipe_tags (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  recipe_id UUID NOT NULL REFERENCES public.saved_recipes(id) ON DELETE CASCADE,
  tag VARCHAR(50) NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW(),

  -- Ensure unique tag per recipe
  UNIQUE(recipe_id, tag)
);

-- Create index for faster lookups
CREATE INDEX idx_recipe_tags_recipe_id ON public.recipe_tags(recipe_id);
CREATE INDEX idx_recipe_tags_tag ON public.recipe_tags(tag);

-- Enable RLS
ALTER TABLE public.recipe_tags ENABLE ROW LEVEL SECURITY;

-- RLS Policy: Users can only see tags for recipes they own
CREATE POLICY "Users can view their own recipe tags"
  ON public.recipe_tags
  FOR SELECT
  USING (
    recipe_id IN (
      SELECT id FROM public.saved_recipes WHERE user_id = auth.uid()
    )
  );

-- RLS Policy: Users can insert tags for their own recipes
CREATE POLICY "Users can insert tags for their recipes"
  ON public.recipe_tags
  FOR INSERT
  WITH CHECK (
    recipe_id IN (
      SELECT id FROM public.saved_recipes WHERE user_id = auth.uid()
    )
  );

-- RLS Policy: Users can delete tags from their own recipes
CREATE POLICY "Users can delete their recipe tags"
  ON public.recipe_tags
  FOR DELETE
  USING (
    recipe_id IN (
      SELECT id FROM public.saved_recipes WHERE user_id = auth.uid()
    )
  );

Common migration patterns: See supabase-security.md

Step 3: Validate RLS Policies

CRITICAL: Every table MUST have RLS enabled and policies defined.

# Check if migration includes RLS
bash .claude/skills/supabase-migrations/scripts/validate-rls.sh supabase/migrations/[your-migration-file].sql

Checks for:

  • ALTER TABLE ... ENABLE ROW LEVEL SECURITY
  • At least one CREATE POLICY statement

Step 4: Test Migration Locally

Apply migration to local Supabase instance first:

# Ensure local Supabase is running
supabase start

# Apply migration to local database
supabase db push

# Check status
supabase db diff

If errors occur:

  • Fix the SQL in migration file
  • Reset local DB: supabase db reset
  • Try again: supabase db push

Step 5: Regenerate TypeScript Types

After successful local migration, update types:

# Generate types from local database
bash .claude/skills/supabase-migrations/scripts/update-types.sh

# Or manually:
supabase gen types typescript --local > lib/supabase/types.ts

This updates lib/supabase/types.ts with new schema.

Step 6: Update Application Code

Search codebase for files that need updating:

# Find files using the affected table
grep -r "from('old_table_name')" app/ components/ lib/

Update TypeScript code to use new types:

import type { Database } from '@/lib/supabase/types'
type RecipeTag = Database['public']['Tables']['recipe_tags']['Row']

Step 7: Test Changes

  • Run build: npm run build (must pass)
  • Run tests: npm test (must pass)
  • Test affected features manually

Step 8: Apply to Production

Only after local testing passes:

# Push migration to remote database
supabase db push --remote

# Or via Supabase Dashboard:
# 1. Copy migration SQL
# 2. Run in SQL Editor
# 3. Verify with table view

Important: Migrations are irreversible in production. Always test locally first.

Migration Safety Checklist

Before applying to production:

  • Migration tested locally with supabase db push?
  • RLS enabled on all new tables?
  • RLS policies created (SELECT, INSERT, UPDATE, DELETE)?
  • Indexes created for foreign keys and frequent queries?
  • Types regenerated with update-types.sh?
  • Application code updated to use new schema?
  • Build passes (npm run build)?
  • Tests pass (npm test)?
  • Manual testing of affected features completed?

Common Migration Patterns

Adding a Column

ALTER TABLE public.saved_recipes
ADD COLUMN difficulty VARCHAR(20) CHECK (difficulty IN ('easy', 'medium', 'hard'));

Renaming a Column

ALTER TABLE public.saved_recipes
RENAME COLUMN old_name TO new_name;

Adding an Index

CREATE INDEX idx_recipes_user_id ON public.saved_recipes(user_id);

Adding a Foreign Key

ALTER TABLE public.recipe_images
ADD CONSTRAINT fk_recipe_images_recipe_id
FOREIGN KEY (recipe_id)
REFERENCES public.saved_recipes(id)
ON DELETE CASCADE;

RLS Policy Patterns

Policy: User owns resource

CREATE POLICY "Users can view their own recipes"
  ON public.saved_recipes
  FOR SELECT
  USING (user_id = auth.uid());

Policy: Public read, authenticated write

CREATE POLICY "Anyone can view recipes"
  ON public.saved_recipes
  FOR SELECT
  USING (true);

CREATE POLICY "Authenticated users can insert"
  ON public.saved_recipes
  FOR INSERT
  WITH CHECK (auth.uid() IS NOT NULL);

Policy: Relationship-based access

CREATE POLICY "Users can view tags for their recipes"
  ON public.recipe_tags
  FOR SELECT
  USING (
    recipe_id IN (
      SELECT id FROM public.saved_recipes WHERE user_id = auth.uid()
    )
  );

More RLS patterns: See supabase-security.md

Rollback Strategy

If a migration causes issues in production:

Option 1: Create reverse migration

supabase migration new revert_add_recipe_tags

# Write SQL to undo changes
# - DROP TABLE
# - DROP COLUMN
# - etc.

Option 2: Restore from backup (via Supabase Dashboard)

  • Settings → Database → Point-in-time Recovery
  • Select time before migration
  • Restore (creates new project)

Prevention is better: Always test locally first!

Script Usage

create-migration.sh

bash .claude/skills/supabase-migrations/scripts/create-migration.sh "migration_name"

Creates timestamped migration file.

validate-rls.sh

bash .claude/skills/supabase-migrations/scripts/validate-rls.sh supabase/migrations/[file].sql

Checks for RLS policies in migration.

update-types.sh

bash .claude/skills/supabase-migrations/scripts/update-types.sh

Regenerates TypeScript types from local database.

Common Issues

"Supabase not running"

supabase start

"Migration file not found"

Check path: supabase/migrations/[timestamp]_name.sql

"RLS validation failed"

Add ALTER TABLE ... ENABLE ROW LEVEL SECURITY and CREATE POLICY statements.

"Types not updating"

supabase db reset
supabase db push
bash .claude/skills/supabase-migrations/scripts/update-types.sh

"Build errors after migration"

  • Check type imports: Database['public']['Tables']['table_name']['Row']
  • Update queries to match new schema
  • Fix any breaking column renames

Quick Reference

Create migration:

supabase migration new name

Test locally:

supabase db push

Update types:

supabase gen types typescript --local > lib/supabase/types.ts

Apply to production:

supabase db push --remote

Related Documentation

What This Skill Can Do

AI-generated examples showing real capabilities

Ready to use this skill?

Visit the original repository to get the full skill configuration and installation instructions.

View on GitHub

Related Skills