Execute apply production-ready Supabase SDK patterns for TypeScript and Python.
Use when implementing Supabase integrations, refactoring SDK usage,
or establishing team coding standards for Supabase.
Trigger with phrases like "supabase SDK patterns", "supabase best practices",
"supabase code patterns", "idiomatic supabase".
Production patterns for @supabase/supabase-js v2 and supabase-py. Every Supabase query returns { data, error } — never assume success. This skill covers client initialization, CRUD with filters, auth, realtime subscriptions, storage, RPC, and the Python equivalent for each pattern.
Prerequisites
Supabase project with URL and anon key (or service role key for server-side)
@supabase/supabase-js v2 installed (TypeScript) or supabase pip package (Python)
TypeScript projects: generated database types via supabase gen types typescript
Instructions
Step 1: Initialize a Typed Singleton Client
Create one client instance and reuse it. Never call createClient per-request.
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
let supabase: ReturnType<typeof createClient<Database>>
export function getSupabase() {
if (!supabase) {
supabase = createClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{
auth: { autoRefreshToken: true, persistSession: true },
db: { schema: 'public' },
global: { headers: { 'x-app-name': 'my-app' } },
}
)
}
return supabase
}
Python equivalent:
from supabase import create_client, Client
_client: Client | None = None
def get_supabase() -> Client:
global _client
if _client is None:
_client = create_client(
os.environ["SUPABASE_URL"],
os.environ["SUPABASE_ANON_KEY"],
)
return _client
Step 2: Query, Filter, and Mutate Data
All queries return { data, error }. Always destructure and check error before using data.
Select with filters and chaining:
const { data, error } = await getSupabase()
.from('users')
.select('id, name, email')
.eq('active', true) // WHERE active = true
.gt('age', 18) // AND age > 18
.ilike('name', '%john%') // AND name ILIKE '%john%'
.in('role', ['admin', 'editor']) // AND role IN (...)
.order('name', { ascending: true })
.limit(10)
if (error) throw error
// data is typed as Pick<User, 'id' | 'name' | 'email'>[]
Insert with select (return the inserted row):
const { data: newUser, error } = await getSupabase()
.from('users')
.insert({ name: 'Alice', email: '[email protected]', active: true })
.select() // Without .select(), data is null
.single() // Unwrap from array to single object
if (error) throw error
// newUser is the full row with server-generated id, created_at, etc.
For database schema design, see supabase-schema-from-requirements. For auth deep-dive with RLS policies, see supabase-install-auth. For realtime architecture patterns, see supabase-auth-storage-realtime-core.