supabase-sdk-patterns
Use when implementing Supabase queries, auth, realtime, storage, or RPC calls with @supabase/supabase-js or supabase-py and you need production-ready, type-safe patterns that always check the { data, error } contract. Trigger with phrases like "supabase SDK patterns", "supabase query", "supabase typescript", "supabase python", "supabase client setup", "supabase realtime", "supabase auth", "supabase storage".
Allowed Tools
Provided by Plugin
supabase-pack
Claude Code skill pack for Supabase (30 skills)
Installation
This skill is included in the supabase-pack plugin:
/plugin install supabase-pack@claude-code-plugins-plus
Click to copy
Instructions
Supabase SDK Patterns
Overview
Production patterns for @supabase/supabase-js v2 and supabase-py, where every call returns { data, error } and success is never assumed. Covers client initialization, CRUD with filters, auth, realtime, storage, and RPC, with Python equivalents for the query patterns.
Prerequisites
- Supabase project with URL and anon key (or service role key for server-side)
@supabase/supabase-jsv2 installed (TypeScript) orsupabasepip 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 — a
singleton preserves the auth session and connection pool.
// 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
Destructure { data, error } and check error before touching data. Chain
filters onto .from(table).select(...); use .select().single() after an
insert/upsert to return the affected row. Skeleton:
const { data, error } = await getSupabase()
.from('users')
.select('id, name, email')
.eq('active', true)
.order('name')
.limit(10)
if (error) throw error
For the full CRUD set (insert-with-select, upsert on conflict, update, delete,
RPC), the complete 12-filter reference table, and the Python equivalents, see
Step 3: Auth, realtime, and storage
Auth, realtime channels, and storage all follow the same { data, error }
contract. Auth exposes signUp / signInWithPassword / getSession /
onAuthStateChange; realtime subscribes to postgres_changes on a channel and
requires removeChannel cleanup; storage does upload / download /
getPublicUrl / createSignedUrl per bucket. Full walkthroughs with code for
each: see auth, realtime, and storage.
Output
Applying these patterns yields:
- Type-safe singleton client with
Databasegenerics - CRUD operations using the full filter chain (eq, gt, in, ilike, etc.)
- Insert-with-select and upsert patterns that return the affected row
- Auth flows for sign-up, sign-in, session management, and state listeners
- Realtime subscriptions with row-level filtering and cleanup
- Storage upload/download with signed URLs for private buckets
- Python equivalents for the query patterns
Error Handling
Every Supabase call returns { data, error }. Never skip the error check.
const { data, error } = await getSupabase().from('users').select('*')
if (error) {
// error is a PostgrestError with these fields:
// error.message — human-readable description
// error.code — Postgres error code (e.g., '23505')
// error.details — additional context
// error.hint — suggested fix from Postgres
console.error(`Query failed [${error.code}]: ${error.message}`)
throw error
}
// Only safe to use data after the error check
| Error Code | Meaning | What to Do |
|---|---|---|
PGRST116 |
No rows found (.single()) |
Return null or 404, don't throw |
23505 |
Unique-constraint violation (Postgres duplicate key) | Use .upsert() or show conflict error |
42501 |
RLS policy violation (Postgres insufficient privilege) | Check auth state and RLS policies |
PGRST000 |
Connection error | Retry with exponential backoff |
42P01 |
Table does not exist | Verify table name and run migrations |
23503 |
Foreign key violation | Ensure referenced row exists first |
42703 |
Column does not exist | Check column name, regenerate types |
Examples
The recommended production shape is a typed service layer that wraps the
client so callers never touch raw queries, plus a pagination helper that
returns a page of rows and the total count in one round trip. Both full,
copy-ready implementations are in service patterns.
Resources
- Supabase JS Client Reference
- TypeScript Support & Type Generation
- Supabase Auth Reference
- Realtime Guide
- Storage Guide
- Python Client Reference
Next Steps
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.