| name | supabase-patterns |
| description | Generic Supabase best practices for Row Level Security, realtime subscriptions, storage, and edge functions. Framework-agnostic. |
Supabase Patterns Skill
Universal patterns for working with Supabase in any project. Covers RLS policies, realtime, storage, edge functions, and migrations.
Design Principle
This skill is framework-generic. It provides universal Supabase patterns:
- NOT tailored to Book-Vetting, ocr-service, or any specific project
- Covers common patterns applicable across all Supabase projects
- Project-specific configurations go in project-specific skills
Variables
| Variable | Default | Description |
|---|---|---|
| SUPABASE_DIR | supabase | Directory for Supabase config |
| ENFORCE_RLS | true | Require RLS on all tables |
| REALTIME_ENABLED | auto | Auto-detect realtime tables |
Instructions
MANDATORY - Follow the Workflow steps below in order.
- Check Supabase project configuration
- Review existing RLS policies
- Follow security-first patterns
- Keep migrations organized
Red Flags - STOP and Reconsider
If you're about to:
- Create a table without RLS policies
- Use service role key in client-side code
- Skip migrations for schema changes
- Expose sensitive data in realtime
STOP -> Add RLS policies -> Use appropriate keys -> Then proceed
Cookbook
RLS Policies
- IF: Creating or modifying RLS policies
- THEN: Read and execute
./cookbook/rls-policies.md
Realtime Subscriptions
- IF: Setting up realtime features
- THEN: Read and execute
./cookbook/realtime-subscriptions.md
Storage Patterns
- IF: Working with Supabase Storage
- THEN: Read and execute
./cookbook/storage-patterns.md
Quick Reference
Project Structure
supabase/
├── config.toml # Project config
├── migrations/ # SQL migrations
│ ├── 20231201000000_initial.sql
│ └── 20231202000000_add_users.sql
├── seed.sql # Seed data
└── functions/ # Edge functions
└── hello/
└── index.ts
Key Commands
# Initialize project
supabase init
# Start local development
supabase start
# Generate migration
supabase migration new my_migration
# Push to remote
supabase db push
# Generate types
supabase gen types typescript --local > types/supabase.ts
RLS Policy Patterns
-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- User owns row
CREATE POLICY "Users can view own posts"
ON posts FOR SELECT
USING (auth.uid() = user_id);
-- User can insert own
CREATE POLICY "Users can create posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- Public read
CREATE POLICY "Public read"
ON posts FOR SELECT
USING (is_public = true);
Client Patterns
// Initialize client
import { createClient } from '@supabase/supabase-js';
import type { Database } from './types/supabase';
const supabase = createClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
);
// Query with types
const { data, error } = await supabase
.from('posts')
.select('*')
.eq('user_id', userId);
// Insert
const { data, error } = await supabase
.from('posts')
.insert({ title, content, user_id: userId })
.select()
.single();
Realtime Pattern
// Subscribe to changes
const subscription = supabase
.channel('posts')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'posts' },
(payload) => {
console.log('Change:', payload);
}
)
.subscribe();
// Cleanup
subscription.unsubscribe();
Storage Pattern
// Upload file
const { data, error } = await supabase.storage
.from('avatars')
.upload(`${userId}/avatar.png`, file, {
upsert: true,
contentType: 'image/png'
});
// Get public URL
const { data: { publicUrl } } = supabase.storage
.from('avatars')
.getPublicUrl(`${userId}/avatar.png`);
Security Checklist
Before Production
- RLS enabled on ALL tables
- Service role key NOT in client code
- Anon key for public operations only
- Storage buckets have policies
- Sensitive columns excluded from realtime
- API rate limiting configured
- CORS properly configured
RLS Checklist
- Every table has RLS enabled
- SELECT policies defined
- INSERT/UPDATE/DELETE policies defined
- Policies tested with different roles
- No overly permissive policies
Integration
With Schema Alignment
Supabase migrations should align with ORM models:
-- supabase/migrations/20231201000000_users.sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
name TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
Should match:
# SQLAlchemy model
class User(Base):
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
email: Mapped[str] = mapped_column(unique=True)
name: Mapped[str | None]
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
Type Generation
# Generate TypeScript types from local schema
supabase gen types typescript --local > types/supabase.ts
# Use in client
import type { Database } from './types/supabase';
type Post = Database['public']['Tables']['posts']['Row'];
Best Practices
- RLS first: Always add RLS policies when creating tables
- Migrations for everything: Never modify schema directly
- Type safety: Generate and use TypeScript types
- Key hygiene: Use anon key client-side, service key server-side only
- Test policies: Test RLS with actual user contexts
- Realtime carefully: Only enable for tables that need it