| name | dev-supabase |
| description | Backend development with Supabase. Trigger when the user wants to configure auth, the database, or Supabase storage. |
| allowed-tools | ["Read","Write","Edit","Bash","Glob","Grep"] |
| context | fork |
Supabase Development
Configuration
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
Authentication
await supabase.auth.signUp({ email, password });
await supabase.auth.signInWithPassword({ email, password });
await supabase.auth.signInWithOAuth({ provider: 'google' });
await supabase.auth.signOut();
Database with RLS
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users read own profile"
ON profiles FOR SELECT
USING (auth.uid() = id);
CREATE POLICY "Users update own profile"
ON profiles FOR UPDATE
USING (auth.uid() = id);
Queries
const { data } = await supabase
.from('profiles')
.select('*')
.eq('id', userId);
await supabase.from('profiles').insert({ name, email });
await supabase.from('profiles').update({ name }).eq('id', userId);
await supabase.from('profiles').delete().eq('id', userId);
Storage
await supabase.storage.from('avatars').upload(path, file);
supabase.storage.from('avatars').getPublicUrl(path);
Realtime
supabase
.channel('messages')
.on('postgres_changes', { event: 'INSERT', table: 'messages' }, callback)
.subscribe();
Postgres Performance Best Practices
Critical priority: Query Performance
CREATE INDEX idx_profiles_email ON profiles(email);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_created_at ON orders(created_at);
CREATE INDEX idx_active_users ON profiles(id) WHERE is_active = true;
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 'xxx';
Critical priority: Connection Management
const supabase = createClient(url, key, {
db: { schema: 'public' },
auth: { persistSession: true },
});
High priority: Schema Design
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES profiles(id),
total_cents INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT now()
);
const { data } = await supabase
.from('orders')
.select('id, status, total_cents')
.eq('user_id', userId);
Medium priority: Security & RLS
CREATE POLICY "own_data" ON orders
FOR ALL USING (user_id = auth.uid());
CREATE POLICY "team_data" ON orders
FOR ALL USING (
user_id IN (SELECT member_id FROM team_members WHERE team_id = current_setting('app.team_id'))
);
CREATE POLICY "team_data" ON orders
FOR ALL USING (
team_id = (auth.jwt() -> 'app_metadata' ->> 'team_id')::uuid
);
Medium priority: Data Access Patterns
const { data } = await supabase
.from('orders')
.select('*')
.gt('created_at', lastSeenDate)
.order('created_at', { ascending: true })
.limit(20);
const { data } = await supabase
.from('orders')
.select('*')
.range(1000, 1020); // Scans 1020 rows
Monitoring
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
SELECT relname, seq_scan, seq_tup_read
FROM pg_stat_user_tables
WHERE seq_scan > 100
ORDER BY seq_tup_read DESC;
SELECT indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0;