Claude-skill-registry Database Schema Extension
Extend the Supabase PostgreSQL database schema following this project's declarative schema patterns, migration workflow, and type generation pipeline. Use when adding tables, columns, enums, RLS policies, triggers, or database functions.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/database-schema-extension" ~/.claude/skills/majiayu000-claude-skill-registry-database-schema-extension && rm -rf "$T"
skills/data/database-schema-extension/SKILL.mdDatabase Schema Extension
This skill guides you through extending the database schema using declarative schema files, Supabase migrations, and automatic type generation.
Quick Reference
Key Commands:
- Generate migration from schema changesbun db:diff <migration_name>
- Apply migrations to local databasebun migrate:up
- Regenerate TypeScript types and Zod schemasbun gen:types
- Reset database (destructive!)bun db:reset
Schema File Organization:
supabase/schemas/ ├── 00-extensions.sql # PostgreSQL extensions ├── 01-schema.sql # Tables, enums, indexes ├── 02-policies.sql # Row Level Security policies └── 03-functions.sql # Database functions and triggers
Complete Workflow
Step 1: Modify Declarative Schema Files
The project uses declarative schema files in
supabase/schemas/. These files define the desired state of your database, and Supabase CLI generates migrations by comparing them with your local database.
Step 2: Follow SQL Style Guidelines
Naming Conventions:
- Tables:
, plural (e.g.,snake_case
,todos
)user_profiles - Columns:
, singular (e.g.,snake_case
,user_id
)created_at - Enums:
, singular (e.g.,snake_case
,priority_level
). Always prefer enums over text for fixed sets.user_role - Foreign keys:
(e.g.,{singular_table_name}_id
referencesuser_id
)users - Indexes:
(e.g.,{table}_{column}_idx
)todos_user_id_idx - Policies: Descriptive text in quotes (e.g.,
). Keep them short and clear."Users can view their own todos"
SQL Standards:
- All SQL keywords in lowercase (e.g.,
,create table
,select
)where - Always use
schema prefix (e.g.,public
)public.todos - Add table comments:
comment on table public.todos is 'User todo items' - Add column comments for enums:
comment on column public.todos.priority is 'Priority level: low, medium, or high' - Always prefer enums over text for fixed sets.
- Use
for timestamps (includes timezone)timestamptz - Default timestamps:
created_at timestamptz default now() not null - Use
for primary keys:uuidid uuid default gen_random_uuid() primary key
Example Table Creation:
-- Priority enum type create type public.priority_level as enum ('low', 'medium', 'high'); -- Todos table create table public.todos ( id uuid default gen_random_uuid() primary key, user_id uuid references public.profiles(id) on delete cascade not null, title text not null, description text, completed boolean default false not null, priority public.priority_level, due_date timestamptz, created_at timestamptz default now() not null, updated_at timestamptz default now() not null ); -- Indexes for performance create index todos_user_id_idx on public.todos(user_id); create index todos_completed_idx on public.todos(completed); create index todos_due_date_idx on public.todos(due_date); -- Comments for documentation comment on table public.todos is 'User todo items'; comment on column public.todos.priority is 'Priority level: low, medium, or high';
Step 3: Add Row Level Security (RLS)
Critical RLS Rules:
- Always enable RLS on new tables (even public tables)
- Create separate policies for each operation (select, insert, update, delete)
- Specify roles explicitly using
orto authenticatedto anon - Add indexes on columns used in policies (usually
)user_id
Policy Structure:
-- Enable RLS alter table public.todos enable row level security; -- SELECT policy create policy "Users can view their own todos" on public.todos for select to authenticated using (auth.uid() = user_id); -- INSERT policy create policy "Users can create their own todos" on public.todos for insert to authenticated with check (auth.uid() = user_id); -- UPDATE policy create policy "Users can update their own todos" on public.todos for update to authenticated using (auth.uid() = user_id) with check (auth.uid() = user_id); -- DELETE policy create policy "Users can delete their own todos" on public.todos for delete to authenticated using (auth.uid() = user_id);
Key Policy Guidelines:
- SELECT policies: Use
only (notusing
)with check - INSERT policies: Use
only (notwith check
)using - UPDATE policies: Use both
andusingwith check - DELETE policies: Use
only (notusing
)with check - Never use
: Always separate into individual policiesFOR ALL - Avoid joins: Rewrite policies to use
orIN
insteadANY
Public Access Example:
-- Public read access create policy "Avatar media is viewable by everyone" on public.media for select to authenticated, anon using (media_type = 'avatar');
Step 4: Add SQL Functions and Triggers
SQL Function Best Practices:
- Default to
(run with caller's permissions)security invoker - Set
and use fully qualified namessearch_path = '' - Use explicit typing for parameters and return values
- Declare as
orimmutable
when possible for optimizationstable
Common Pattern:
Triggerupdated_at
-- Reuse existing function for updated_at create trigger my_table_updated_at before update on public.my_table for each row execute function public.handle_updated_at();
The project already has
public.handle_updated_at() function - just create the trigger!
Custom Function Example:
-- RPC function example create or replace function public.get_user_stats(user_uuid uuid) returns table ( user_id uuid, total_items bigint, completed_items bigint ) as $$ begin return query select user_uuid as user_id, count(*) as total_items, count(*) filter (where completed = true) as completed_items from public.todos where user_id = user_uuid; end; $$ language plpgsql security invoker set search_path = '';
Step 5: Generate Migration
After modifying schema files, generate a migration:
bun db:diff add_bookings_table
What This Does:
- Compares
with your local databasesupabase/schemas/*.sql - Generates SQL migration in
supabase/migrations/YYYYMMDDHHMMSS_add_bookings_table.sql - Shows you the diff for review
Important: Always review the generated migration SQL before applying!
Step 6: Review and Apply Migration
-
Review the migration file in
supabase/migrations/ -
Check for:
- Destructive operations (drop, truncate, alter column types)
- Missing RLS policies
- Correct foreign key relationships
- Proper indexes
-
Apply the migration:
bun migrate:up
Step 7: Regenerate Types
Critical Final Step: Always regenerate types after schema changes!
bun gen:types
What This Does:
- Runs
- Generates TypeScript types from databasebun db:types - Runs
- Generates Zod schemas from TypeScript typesbun db:types:zod - Runs
- Cleans up schema namesbun remove:public:prefix
Generated Files:
- TypeScript types for all tables, enums, functionstypes/database.types.ts
- Zod schemas for validationschemas/database.schema.ts
Usage in Code:
// Import types import type { Database } from "@/types/database.types"; // Use table types type Booking = Database["public"]["Tables"]["bookings"]["Row"]; type BookingInsert = Database["public"]["Tables"]["bookings"]["Insert"]; type BookingUpdate = Database["public"]["Tables"]["bookings"]["Update"]; // Import Zod schemas import { bookingsInsertSchema } from "@/schemas/database.schema"; // Use in server actions const { data: validatedData, success } = bookingsInsertSchema.safeParse(input);
Troubleshooting
Migration Conflicts
If
bun db:diff shows unexpected changes:
-
Check if local database is out of sync:
bun db:reset # Resets local DB to match migrations + seed data -
Check if you have unapplied migrations:
bun migrate:up
Type Generation Fails
If
bun gen:types fails:
-
Ensure local database is running:
bun db:start -
Check for SQL syntax errors in schema files
-
Verify all migrations are applied:
bun migrate:up
Policy Not Working
Common issues:
- RLS not enabled:
alter table public.my_table enable row level security; - Missing role specification: Add
orto authenticatedto anon - Missing index: Add index on
or columns used in policyuser_id - Function not wrapped in select: Use
not(select auth.uid())auth.uid()
Workflow Checklist
When extending the database schema, follow this checklist:
- Modify appropriate schema file (
,00-extensions.sql
,01-schema.sql
, or02-policies.sql
)03-functions.sql - Follow SQL style guide (lowercase, snake_case, schema prefix)
- Add table and column comments
- Create indexes for foreign keys and frequently queried columns
- Enable RLS on new tables
- Create separate policies for select/insert/update/delete
- Add
trigger if table hasupdated_at
columnupdated_at - Run
to generate migrationbun db:diff <migration_name> - Review generated migration SQL
- Run
to apply migrationbun migrate:up - Run
to regenerate TypeScript/Zod typesbun gen:types - Test new schema in application code
Best Practices Summary
- Always work declaratively - Edit schema files, let Supabase generate migrations
- One migration per logical change - Don't bundle unrelated changes
- Review before applying - Always check generated SQL
- Regenerate types immediately - Run
after every schema changebun gen:types - Enable RLS by default - Security first, even for "public" tables
- Index foreign keys - Always add indexes on reference columns
- Use timestamps - Add
andcreated_at
to most tablesupdated_at - Comment everything - Future you will thank present you
- Test locally first - Use local database, never modify production directly
- Follow naming conventions - Consistency makes collaboration easier
References
- Project package.json scripts:
/package.json - Existing schema examples:
/supabase/schemas/01-schema.sql - RLS policy examples:
/supabase/schemas/02-policies.sql - Function examples:
/supabase/schemas/03-functions.sql