supabase
Tools and workflow patterns for managing Supabase database migrations, RLS, and edge functions locally.
Install
mkdir -p .claude/skills/supabase && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/5808" && unzip -o skill.zip -d .claude/skills/supabase && rm skill.zipInstalls to .claude/skills/supabase
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Core Supabase CLI, migrations, RLS, Edge FunctionsKey capabilities
- →Initialize local Supabase projects with CLI
- →Manage database schema via SQL migrations
- →Configure Row Level Security policies for tables
- →Develop and deploy serverless Edge Functions
- →Generate TypeScript types from database schema
How it works
The CLI manages a local development stack that mirrors the remote environment, allowing developers to track schema changes as version-controlled migrations.
Inputs & outputs
When to use supabase
- →Initialize a local Supabase project
- →Run database migrations
- →Manage row level security
- →Deploy edge functions
About this skill
Supabase Core Skill
Core concepts, CLI workflow, and patterns common to all Supabase projects.
Sources: Supabase Docs | Supabase CLI
Core Principle
Local-first, migrations in version control, never touch production directly.
Develop locally with the Supabase CLI, capture all changes as migrations, and deploy through CI/CD.
Supabase Stack
| Service | Purpose |
|---|---|
| Database | PostgreSQL with extensions |
| Auth | User authentication, OAuth providers |
| Storage | File storage with RLS |
| Edge Functions | Serverless Deno functions |
| Realtime | WebSocket subscriptions |
| Vector | AI embeddings (pgvector) |
CLI Setup
Install & Login
# macOS
brew install supabase/tap/supabase
# npm (alternative)
npm install -g supabase
# Login
supabase login
Initialize Project
# In your project directory
supabase init
# Creates:
# supabase/
# ├── config.toml # Local config
# ├── seed.sql # Seed data
# └── migrations/ # SQL migrations
Link to Remote
# Get project ref from dashboard URL: https://supabase.com/dashboard/project/<ref>
supabase link --project-ref <project-id>
# Pull existing schema
supabase db pull
Start Local Stack
supabase start
# Output:
# API URL: http://localhost:54321
# GraphQL URL: http://localhost:54321/graphql/v1
# DB URL: postgresql://postgres:postgres@localhost:54322/postgres
# Studio URL: http://localhost:54323
# Anon key: eyJ...
# Service role key: eyJ...
Migration Workflow
Option 1: Dashboard + Diff (Quick Prototyping)
# 1. Make changes in local Studio (localhost:54323)
# 2. Generate migration from diff
supabase db diff -f <migration_name>
# 3. Review generated SQL
cat supabase/migrations/*_<migration_name>.sql
# 4. Reset to test
supabase db reset
Option 2: Write Migrations Directly (Recommended)
# 1. Create empty migration
supabase migration new create_users_table
# 2. Edit the migration file
# supabase/migrations/<timestamp>_create_users_table.sql
# 3. Apply locally
supabase db reset
Option 3: ORM Migrations (Best DX)
Use Drizzle (TypeScript) or SQLAlchemy (Python) - see framework-specific skills.
Deploy Migrations
# Push to remote (staging/production)
supabase db push
# Check migration status
supabase migration list
Database Patterns
Enable RLS on All Tables
-- Always enable RLS
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
-- Default deny - must create policies
CREATE POLICY "Users can view own profile"
ON public.profiles
FOR SELECT
USING (auth.uid() = id);
Common RLS Policies
-- Public read
CREATE POLICY "Public read access"
ON public.posts FOR SELECT
USING (true);
-- Authenticated users only
CREATE POLICY "Authenticated users can insert"
ON public.posts FOR INSERT
WITH CHECK (auth.role() = 'authenticated');
-- Owner access
CREATE POLICY "Users can update own records"
ON public.posts FOR UPDATE
USING (auth.uid() = user_id);
-- Admin access (using custom claim)
CREATE POLICY "Admins have full access"
ON public.posts FOR ALL
USING (auth.jwt() ->> 'role' = 'admin');
Link to auth.users
-- Profile table linked to auth
CREATE TABLE public.profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
username TEXT UNIQUE NOT NULL,
avatar_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Auto-create profile on signup
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.profiles (id, username)
VALUES (NEW.id, NEW.email);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
Seed Data
supabase/seed.sql
-- Runs on `supabase db reset`
-- Use ON CONFLICT for idempotency
INSERT INTO public.profiles (id, username, avatar_url)
VALUES
('d0e1f2a3-b4c5-6d7e-8f9a-0b1c2d3e4f5a', 'testuser', null),
('a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d', 'admin', null)
ON CONFLICT (id) DO NOTHING;
Environment Variables
Required Variables
# Public (safe for client-side)
SUPABASE_URL=https://xxxxx.supabase.co
SUPABASE_ANON_KEY=eyJ...
# Private (server-side only - NEVER expose)
SUPABASE_SERVICE_ROLE_KEY=eyJ...
DATABASE_URL=postgresql://postgres.[ref]:[password]@aws-0-region.pooler.supabase.com:6543/postgres
Local vs Production
# .env.local (local development)
SUPABASE_URL=http://localhost:54321
SUPABASE_ANON_KEY=<from supabase start>
DATABASE_URL=postgresql://postgres:postgres@localhost:54322/postgres
# .env.production (remote)
SUPABASE_URL=https://xxxxx.supabase.co
SUPABASE_ANON_KEY=<from dashboard>
DATABASE_URL=<connection pooler URL>
Connection Pooling
# Transaction mode (recommended for serverless)
# Add ?pgbouncer=true to URL
DATABASE_URL=postgresql://[email protected]:6543/postgres?pgbouncer=true
# Session mode (for migrations, long transactions)
DATABASE_URL=postgresql://[email protected]:5432/postgres
Edge Functions
Create Function
supabase functions new hello-world
Basic Structure
// supabase/functions/hello-world/index.ts
import { serve } from 'https://deno.land/[email protected]/http/server.ts';
serve(async (req) => {
const { name } = await req.json();
return new Response(
JSON.stringify({ message: `Hello ${name}!` }),
{ headers: { 'Content-Type': 'application/json' } }
);
});
With Auth Context
import { serve } from 'https://deno.land/[email protected]/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{
global: {
headers: { Authorization: req.headers.get('Authorization')! },
},
}
);
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return new Response('Unauthorized', { status: 401 });
}
return new Response(JSON.stringify({ user_id: user.id }));
});
Deploy
# Serve locally
supabase functions serve
# Deploy single function
supabase functions deploy hello-world
# Deploy all
supabase functions deploy
Storage
Create Bucket (in migration)
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true);
-- Storage policies
CREATE POLICY "Avatar images are publicly accessible"
ON storage.objects FOR SELECT
USING (bucket_id = 'avatars');
CREATE POLICY "Users can upload own avatar"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'avatars' AND
auth.uid()::text = (storage.foldername(name))[1]
);
CLI Quick Reference
# Lifecycle
supabase start # Start local stack
supabase stop # Stop local stack
supabase status # Show status & credentials
# Database
supabase db reset # Reset + migrations + seed
supabase db push # Push to remote
supabase db pull # Pull remote schema
supabase db diff -f <name> # Generate migration from diff
supabase db lint # Check for issues
# Migrations
supabase migration new <name> # Create migration
supabase migration list # List migrations
supabase migration up # Apply pending (remote)
# Functions
supabase functions new <name> # Create function
supabase functions serve # Local dev
supabase functions deploy # Deploy all
# Types
supabase gen types typescript --local > types/database.ts
# Project
supabase link --project-ref <id> # Link to remote
supabase projects list # List projects
CI/CD Template
# .github/workflows/supabase.yml
name: Supabase CI/CD
on:
push:
branches: [main]
pull_request:
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }}
SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
- name: Start Supabase
run: supabase start
- name: Run migrations
run: supabase db reset
- name: Lint database
run: supabase db lint
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
- name: Link project
run: supabase link --project-ref $SUPABASE_PROJECT_ID
- name: Push migrations
run: supabase db push
- name: Deploy functions
run: supabase functions deploy
Anti-Patterns
- Direct production changes - Always use migrations
- Disabled RLS - Enable on all user-data tables
- Service key in client - Never expose service role key
- No connection pooling - Use pooler for serverless
- Committing .env - Add to .gitignore
- Skipping migration review - Always check generated SQL
- No seed data - Use seed.sql for consistent local dev
When not to use it
- →When making direct changes to production databases
- →When exposing the service role key in client-side code
Prerequisites
Limitations
- →Requires manual review of generated SQL migrations
- →Must use connection pooler for serverless environments
How it compares
This workflow enforces a local-first development cycle that prevents direct production database manipulation.
Compared to similar skills
supabase side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| supabase (this skill) | 1 | 4mo | Review | Intermediate |
| drizzle-orm | 32 | 2mo | No flags | Intermediate |
| database-design | 6 | 6mo | Review | Intermediate |
| database-schema-designer | 6 | 6mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by alinaqi
View all by alinaqi →You might also like
drizzle-orm
EpicenterHQ
Drizzle ORM patterns for type branding and custom types. Use when working with Drizzle column definitions, branded types, or custom type conversions.
database-design
davila7
Database design principles and decision-making. Schema design, indexing strategy, ORM selection, serverless databases.
database-schema-designer
davila7
Design robust, scalable database schemas for SQL and NoSQL databases. Provides normalization guidelines, indexing strategies, migration patterns, constraint design, and performance optimization. Ensures data integrity, query performance, and maintainable data models.
database-migrations-sql-migrations
sickn33
SQL database migrations with zero-downtime strategies for PostgreSQL, MySQL, SQL Server
postgresql-syntax-reference
pgschema
Consult PostgreSQL's parser and grammar (gram.y) to understand SQL syntax, DDL statement structure, and parsing rules when implementing pgschema features
databases
mrgoonie
Work with MongoDB (document database, BSON documents, aggregation pipelines, Atlas cloud) and PostgreSQL (relational database, SQL queries, psql CLI, pgAdmin). Use when designing database schemas, writing queries and aggregations, optimizing indexes for performance, performing database migrations, configuring replication and sharding, implementing backup and restore strategies, managing database users and permissions, analyzing query performance, or administering production databases.