Database
Supabase security model and data access patterns.
Security Architecture
This template uses a security-first approach:
- RLS is enabled on all tables
- No RLS policies are created (deny all by default)
- The
anonkey has zero data access - All queries use
createAdminClient()(service role key) - Data access happens exclusively in Server Actions and API Routes
This means even if someone gets your anon key, they cannot read or write any data.
Data Access Pattern
Never use the browser Supabase client for data queries. Always use Server Actions:
// actions/your-feature.ts
'use server'
import { createAdminClient } from '@/lib/supabase/server'
export async function getData() {
const supabase = createAdminClient()
const { data, error } = await supabase
.from('your_table')
.select('*')
if (error) throw error
return data
}
Database Schema
The schema is defined in supabase/schema.sql:
profiles
Extends auth.users with app-specific fields:
id(references auth.users)full_nameavatar_urlbilling_address,payment_method- Auto-created via database trigger on signup
customers
Maps Supabase users to Stripe customers:
id(references auth.users)stripe_customer_id
subscriptions
Tracks active subscriptions:
id(Stripe subscription ID)user_id,status,price_idcurrent_period_start,current_period_endcancel_at_period_end
purchases
Tracks one-time payments:
id,user_id,stripe_session_idamount,currency,status
Adding Tables
- Add the SQL to
supabase/schema.sql - Run the migration in Supabase SQL Editor
- Update types in
lib/supabase/database.types.ts - Create Server Actions in
actions/