--- name: supabase-sdk-patterns description: | 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: Read, Write, Edit, Grep version: 1.53.0 license: MIT author: Jeremy Longshore tags: - saas - supabase - typescript - python - sdk - patterns compatibility: Designed for Claude Code, also compatible with Cursor --- # 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-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 — a singleton preserves the auth session and connection pool. ```typescript // lib/supabase.ts import { createClient } from '@supabase/supabase-js' import type { Database } from './database.types' let supabase: ReturnType> export function getSupabase() { if (!supabase) { supabase = createClient( 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:** ```python 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: ```typescript 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 [queries and filters](references/queries-and-filters.md). ### 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](references/auth-realtime-storage.md). ## Output Applying these patterns yields: - Type-safe singleton client with `Database` generics - 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. ```typescript 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](references/service-patterns.md). ## Resources - [Supabase JS Client Reference](https://supabase.com/docs/reference/javascript/initializing) - [TypeScript Support & Type Generation](https://supabase.com/docs/reference/javascript/typescript-support) - [Supabase Auth Reference](https://supabase.com/docs/reference/javascript/auth-signup) - [Realtime Guide](https://supabase.com/docs/guides/realtime) - [Storage Guide](https://supabase.com/docs/guides/storage) - [Python Client Reference](https://supabase.com/docs/reference/python/initializing) ## 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`.