supabase-pack
Complete Supabase integration skill pack with 30 skills covering authentication, database, storage, realtime, edge functions, and production operations. Flagship+ tier vendor pack.
Installation
Open Claude Code and run this command:
/plugin install supabase-pack@claude-code-plugins-plus
Use --global to install for all projects, or --project for current project only.
What It Does
> 30 production-grade Claude Code skills for Supabase: real @supabase/supabase-js v2 client methods, real PostgreSQL RLS policies, real Edge Functions, real Supabase CLI workflows.
Skills (30) plugin-local skills
Deep Supabase diagnostics: pg_stat_statements for slow queries, lock debugging with pg_locks, connection leak detection, RLS policy conflicts, Edge Function cold starts, and Realtime connection drop analysis.
Supabase Advanced Troubleshooting
Overview
When basic debugging does not reveal the root cause, you need deep PostgreSQL diagnostics: pg_stat_statements to find the slowest queries by cumulative execution time, pg_locks to detect lock contention and deadlocks, pg_stat_activity to find connection leaks, RLS policy conflict analysis to diagnose silent data filtering, Edge Function cold start profiling, and Realtime channel drop investigation. This skill covers every advanced diagnostic technique with real SQL queries and createClient from @supabase/supabase-js.
When to use: Slow query investigation, lock contention causing timeouts, connection pool exhaustion from leaks, RLS policies that silently filter or conflict, Edge Functions with unpredictable latency, or Realtime subscriptions that disconnect intermittently.
Prerequisites
- Supabase project with
pg_stat_statementsextension enabled - Direct database access via SQL Editor or
psql @supabase/supabase-jsv2+ installed in your project- Supabase CLI for Edge Function logs
- Familiarity with PostgreSQL system catalogs
Authentication
Every technique here runs against your own project's Postgres, so authenticate with project-scoped credentials, never a shared key:
psql/ SQL Editor — connect with the project's direct connection string or pooled Supavisor URI from Dashboard → Settings → Database. Keep the database password in an env var (PGPASSWORD/.pgpass), never inline in a command.- SDK (
createClient) — pass the service-role key (SUPABASE_SERVICE_ROLE_KEY) for the diagnostic RPCs below, since they readpg_stat_activityand system catalogs that the anon key cannot. Load it from the environment; never commit it. supabaseCLI — runsupabase loginonce (stores a personal access token), thensupabase link --project-ref <ref>for Edge Function logs.
Instructions
Work the three diagnostic tracks in the order the symptom points to. Each track's full query set and SDK helper lives in a reference file — SKILL.md carries the skeleton so you can start immediately, then drill in for the complete toolkit.
Step 1: pg_stat_statements and slow query analysis
Enable pg_stat_statements, then rank queries by total execution time, call frequency, and cache-hit ratio to find the real cost centers. Follow up with EXPLAIN (ANALYZE, BUFFERS) on the worst offenders and add targeted indexes with CREATE INDEX CONCURRENTLY. The starter query:
CREATE EXTENSION IF NOT EXUse when choosing how to integrate Supabase into a specific stack — setting up Next.
Supabase Architecture Variants
Overview
Every Supabase createClient configuration turns on two questions: where the client runs (browser vs server) and which key it uses (anon respects RLS; service_role bypasses it). This skill supplies production-ready patterns for five architectures — Next.js SSR, SPA, Mobile, Serverless Edge Functions, and Multi-tenant isolation.
Prerequisites
@supabase/supabase-jsv2+ installed@supabase/ssrpackage for Next.js SSR (v0.5+)- Supabase project with URL,
anonkey, andservice_rolekey - TypeScript project with generated database types (
supabase gen types typescript) - For mobile: React Native with Expo or bare workflow
Instructions
Pick the architecture that matches the target stack, then follow the linked walkthrough for the full, copy-ready client setup.
| Architecture | Client(s) | Key | Session storage |
|---|---|---|---|
| Next.js SSR | Server (cookies) + browser + admin | anon in-request, service_role server-only |
HTTP cookies |
| SPA (React/Vue) | Single browser client | anon only |
localStorage |
| Mobile (React Native) | Single native client | anon only |
AsyncStorage |
| Serverless (Edge Functions) | Per-request client | anon (forwarded JWT) or service_role |
none (stateless) |
| Multi-tenant | Any of the above | anon + RLS, or schema-per-tenant |
per host pattern |
Step 1 — Next.js SSR (App Router)
Next.js App Router needs two separate clients: a server client that reads/writes auth cookies via @supabase/ssr, and a browser client for client components. A third service_role admin client is used only in Server Actions/Route Handlers and must never reach the browser. The browser client is the minimal skeleton:
// lib/supabase/client.ts
'use client'
import { createBrowserClient } from '@supabase/ssr'
import type { Database } from '../database.types'
export function createSupabaseBrowser() {
return createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! // anon key only — respects RLS
)
}
Full walkthrough — server client, admin client, middleware session refresh, server component usage, Server Actions, and the OAuth callback route: Next.js SSR patterns<
Implement Supabase Auth (signUp, signIn, OAuth, session management), Storage (upload, download, signed URLs, bucket policies), and Realtime (Postgres changes, broadcast, presence).
Supabase Auth + Storage + Realtime Core
Overview
Implement the three pillars that turn a Supabase database into a full application backend: user authentication (email/password, OAuth, magic links, session lifecycle), file storage (uploads, downloads, signed URLs, bucket-level RLS policies), and real-time subscriptions (Postgres change events, client-to-client broadcast, presence tracking). Every operation integrates with Row-Level Security through auth.uid().
Each pillar below carries a lean skeleton in this file; the full, copy-paste walkthroughs live in references/ so this file stays scannable.
Prerequisites
- Supabase project created at supabase.com/dashboard
@supabase/supabase-jsv2 installed (npm install @supabase/supabase-js)SUPABASE_URLandSUPABASE_ANON_KEYavailable from project Settings > API- For Python:
pip install supabase(wrapspostgrest-py,gotrue-py,storage3,realtime-py)
Instructions
Read the file (Read), edit or create the client and route/component code (Write, Edit), and grep the project (Grep) to reuse an existing Supabase client before creating a new one. Use Bash(npm:*) to install the SDK and Bash(supabase:*) to run migrations/policies.
Step 1: Auth — registration, login, OAuth
Initialize the client once, then wire the flows your app needs. The skeleton:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!)
// Email/password
await supabase.auth.signUp({ email, password })
await supabase.auth.signInWithPassword({ email, password })
// OAuth — redirect the user to data.url
const { data } = await supabase.auth.signInWithOAuth({ provider: 'google' })
// React to session changes (SIGNED_IN / SIGNED_OUT / TOKEN_REFRESHED)
supabase.auth.onAuthStateChange((event, session) => { /* update UI */ })
Full auth walkthrough — OAuth callback, magic link, session lifecycle, password reset: references/auth.md. Python: references/python-examples.md.
Step 2: Storage — upload, download, secure with bucket policies
Public buckets serve via CDN URLs; private buckets require signed URLs. The skeleton:
// Upload to the signed-in user's own folder (RLS enforces ownership)
await supabase.storage.from('avatars').upload(`${userId}/avatar.png`, file, { upsert: true })
// Public URL (public bucket) vs. time-limited signed URL (private bucket)
supabase.storage.from('avatars').getPublicUrl(`${userId}/avatar.png`)
awConfigure Supabase continuous-integration and deployment pipelines with GitHub Actions: link projects, push migrations, deploy Edge Functions, generate types, and run tests against local Supabase instances.
Supabase CI Integration
Overview
Build GitHub Actions workflows that automate the full Supabase lifecycle: link projects in CI, push migrations on merge, deploy Edge Functions, generate TypeScript types, run tests against a local Supabase instance, and create preview branches for pull requests. Every database change gets validated before it reaches production.
The pull-request CI pipeline runs these stages, all detailed in ci-workflows.md:
- Start a local Supabase instance (unused services disabled for speed).
- Apply every migration from scratch with
npx supabase db reset. - Regenerate TypeScript types and fail the build on drift from the committed version.
- Run pgTAP database tests and the application test suite against the local instance.
- Stop Supabase (always, even on failure).
Prerequisites
- GitHub repository with Actions enabled
- Supabase project created at supabase.com/dashboard
- Supabase CLI initialized locally (
npx supabase init) - Node.js 18+ in your project
@supabase/supabase-jsinstalled:
npm install @supabase/supabase-js
Instructions
Step 1: Configure GitHub Secrets and Link in CI
Store credentials as GitHub repository secrets. The CI pipeline uses these to authenticate with your Supabase project without exposing tokens in code.
# Set secrets via GitHub CLI
gh secret set SUPABASE_ACCESS_TOKEN --body "<your-access-token>"
gh secret set SUPABASE_DB_PASSWORD --body "<your-database-password>"
gh secret set SUPABASE_PROJECT_REF --body "<your-project-ref>"
Generate your access token at supabase.com/dashboard/account/tokens. Find your project ref in Project Settings > General.
Link the project in any CI job that needs remote access:
- name: Install Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest
- name: Link Supabase project
run: npx supabase link --project-ref ${{ secrets.SUPABASE_PROJECT_REF }}
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
Step 2: CI Workflow — Test, Validate Migrations, and Generate Types
Add Diagnose and fix Supabase errors across PostgREST, PostgreSQL, Auth, Storage, and Realtime. Diagnostic guide for Supabase errors across PostgREST ( The workflow is three steps: capture the error object, classify it by layer and code, then apply and verify the fix. Full step-by-step code lives in the diagnostic walkthrough; complete lookup tables are in the error reference. Every Supabase SDK call returns a If Match the code prefix to its subsystem, then look it up in the Error Handling tables below: A missing code usually means the HTTP status is the signal: Apply the fix from the matching Error Handling table, then .github/workflows/supabase-ci.yml that runs on every pull request. It starts a local Supabase instance, runs db reset to apply all migrations from scratch, regenerates types and fails on drift (git diff --exit-code), then runs pgTAP and application tests. The default local dev keys are safe to commit — they only work against the local instance. Copy the complete workflow from
Supabase Common Errors
Overview
PGRST*), PostgreSQL (numeric codes), Auth, Storage, and Realtime. Identify the error layer, trace the root cause, and apply the correct fix — every SDK call returns { data, error } where data is null when error exists.Prerequisites
@supabase/supabase-js installed (npm install @supabase/supabase-js)SUPABASE_URL and SUPABASE_ANON_KEY (or SUPABASE_SERVICE_ROLE_KEY) configurednpx supabase --version)Instructions
Step 1 — Capture the Error Object
{ data, error } tuple. Never assume data exists — always destructure and check error first, because data is null whenever error is set.
const { data, error } = await supabase.from('todos').select('*')
if (error) {
console.error(`[${error.code}] ${error.message}`)
return // data is null here — do not touch it
}
console.log(`Found ${data.length} rows`)
error is undefined rather than null, upgrade to @supabase/supabase-js@2.x. See the walkthrough for the full guard pattern.Step 2 — Identify the Error Layer and Code
PGRST* → PostgREST (API gateway: JWT, query parsing, schema)42501, 23505) → PostgreSQL engine (RLS, constraints, migrations)AuthApiError → Auth service (credentials, confirmation, token expiry)StorageApiError → Storage service (bucket, RLS on storage.objects, size limits)401 → bad/missing SUPABASE_ANON_KEY; 500 → an unhandled exception in a database function. A paste-ready diagnoseSupabaseError() classifier is in the walkthrough.Step 3 — Apply the Fix and Verify
Optimize Supabase costs through plan selection, database tuning, storage cleanup, connection pooling, and Edge Function optimization.
Supabase Cost Tuning
Overview
Reduce Supabase spend by auditing usage against plan limits, eliminating database and storage waste, and right-sizing compute resources. The three biggest levers: database optimization (vacuum, index cleanup, archival), storage lifecycle management (compress before upload, orphan cleanup), and connection pooling to reduce compute add-on requirements.
Work the three steps below in order — audit first to find where the money goes, then optimize the biggest offenders, then right-size compute. Each step keeps a representative snippet inline; the full query and script sets live in references/ so this file stays scannable.
Prerequisites
- Supabase project with Dashboard access (Settings > Billing)
@supabase/supabase-jsinstalled:npm install @supabase/supabase-js- Service role key for admin operations (storage audit, cleanup scripts)
- SQL editor access (Dashboard > SQL Editor or
psqlconnection)
Pricing Reference
| Resource | Free Tier | Pro ($25/mo) | Team ($599/mo) |
|---|---|---|---|
| Database | 500 MB | 8 GB included, $0.125/GB extra | 8 GB included |
| Storage | 1 GB | 100 GB included, $0.021/GB extra | 100 GB included |
| Bandwidth | 5 GB | 250 GB included, $0.09/GB extra | 250 GB included |
| Edge Functions | 500K invocations | 2M invocations, $2/million extra | 2M invocations |
| Realtime | 200 concurrent | 500 concurrent | 500 concurrent |
| Auth MAU | 50,000 | 100,000 | 100,000 |
Compute add-ons (Pro and above):
| Instance | vCPUs | RAM | Price |
|---|---|---|---|
| Micro | 2 | 1 GB | Included with Pro |
| Small | 2 | 2 GB | $25/mo |
| Medium | 2 | 4 GB | $50/mo |
| Large | 4 | 8 GB | $100/mo |
| XL | 8 | 16 GB | $200/mo |
| 2XL | 16 | 32 GB | $400/mo |
Decision framework: Read replicas ($25/mo each) beat scaling up when reads dominate and geographic distribution is needed. Connection pooling (Supavisor, free) reduces compute pressure from idle connections.
Instructions
Step 1: Audit current usage and identify cost drivers
Find where the database budget is going before changing anything. Run the audit queries in the SQL Editor to surface the biggest tables, unused indexes, dead-tuple bloat, and connection count. Start with total size
Implement GDPR/CCPA compliance with Supabase: RLS for data isolation, user deletion via auth.
Supabase Data Handling
Overview
GDPR and CCPA compliance with Supabase uses a layered approach: Row Level Security (RLS) for tenant data isolation, supabase.auth.admin.deleteUser() for right-to-deletion, SQL-based exports for subject access requests, PII detection across columns, automated retention with pg_cron, and point-in-time recovery for backup/restore. Every requirement maps to a real Supabase SDK method or PostgreSQL feature.
When to use: Implement GDPR right-to-deletion, respond to data subject access requests (DSARs), audit PII in the database, configure automated retention, set up tenant isolation with RLS, or plan backup/restore procedures.
Prerequisites
@supabase/supabase-jsv2+ with service role key for admin operations- Supabase project on Pro plan (for
pg_cronand point-in-time recovery) - Understanding of GDPR Articles 15-17 (access, rectification, erasure)
- Database access via SQL Editor or
psqlfor schema changes
Instructions
Step 1: RLS for Data Isolation and PII Column Management
Enable RLS on every table holding user data, then classify PII columns so later deletion and export steps know what to touch. The RLS skeleton is one USING (auth.uid() = ...) policy per access pattern:
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users_read_own_profile" ON public.profiles
FOR SELECT USING (auth.uid() = id);
Pair the policies with a PII audit — an information_schema.columns scan by naming pattern, COMMENT ON COLUMN tags, a pii_registry view, and an SDK-side regex scanner for emails, phones, SSNs, and IPs.
See RLS and PII reference for the full multi-tenant policy set, the PII audit SQL, the pii_registry view, and the scanTableForPII() SDK scanner.
Step 2: User Deletion and Data Export
Implement GDPR Article 17 (erasure) and Article 15 (access). Deletion runs in cascade order — application tables, then storage files, then the auth user, then an immutable audit-log entry that must survive the erasure:
// Cascade order matters: children before parents, auth user last
const tablesToPurge = ['comments', 'orders', 'documents', 'profiles'];
// ...delete rows, remove storage files, then:
await supabase.auth.admin.deleteUser(userId);
await supabase.from('gdpr_audit_log').insert({ action: 'USER_DELETION', subject_id: userId });
Export is the inverse: read every user-scoped table plus storage listings into one JSON payload and log a DATA_EXPORT audit row.
See deletion and e
Collect Supabase diagnostic info for troubleshooting and support tickets.
Supabase Debug Bundle
Overview
Collect a comprehensive, redacted diagnostic bundle from a Supabase project. Tests connectivity, auth, Realtime, Storage, RLS policy behavior, and database health — then packages everything into a single archive safe for sharing with Supabase support.
Current State
!node --version 2>/dev/null || echo 'Node.js not found' !npx supabase --version 2>/dev/null || echo 'Supabase CLI not found' !npm list @supabase/supabase-js 2>/dev/null | grep supabase || echo '@supabase/supabase-js not installed'
Prerequisites
- Node.js 18+ with
@supabase/supabase-jsv2 installed in the project - Supabase CLI installed (
npm i -g supabaseornpx supabase) - Environment variables set:
SUPABASE_URLandSUPABASE_ANON_KEY(minimum);SUPABASE_SERVICE_ROLE_KEYfor full diagnostics - Project linked via
supabase link --project-ref <ref>(for CLI commands)
Instructions
Step 1: Gather Environment and Connectivity
Collect SDK version, project URL, key type, and test basic connectivity against the REST and Auth endpoints.
import { createClient } from '@supabase/supabase-js'
const url = process.env.SUPABASE_URL!
const anonKey = process.env.SUPABASE_ANON_KEY!
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
// Identify which key is in use
const keyType = serviceKey ? 'service_role' : 'anon'
const supabase = createClient(url, serviceKey ?? anonKey, {
auth: { autoRefreshToken: false, persistSession: false }
})
const diagnostics: Record<string, unknown> = {}
// 1a — SDK + environment
diagnostics.environment = {
supabase_js_version: require('@supabase/supabase-js/package.json').version,
node_version: process.version,
project_url: url.replace(/https:\/\/([^.]+)\..*/, 'https://$1.***'),
key_type: keyType,
timestamp: new Date().toISOString(),
}
// 1b — REST API connectivity
const restStart = Date.now()
const restRes = await fetch(`${url}/rest/v1/`, {
headers: { apikey: anonKey, Authorization: `Bearer ${anonKey}` },
})
diagnostics.rest_api = {
status: restRes.status,
latency_ms: Date.now() - restStart,
ok: restRes.ok,
}
// 1c — Auth health
const authStart = Date.now()
const { data: sessionData, error: sessionErr } = await supabase.auth.getSession()
diagnostics.auth = {
status: sessionErr ? `error: ${sessionErr.message}` : 'ok',
has_session: !!sessionData?.session,
latency_ms: Date.now() - authStart,
}
// 1d — Database connectivity probe
const dbStart = Date.now()
const { error: dbErr } = await supabase.from('_test_ping').select('*').limit(1)
diagnostics.database = {
// 42P01 = table doesDeploy and manage Supabase projects in production.
Supabase Deploy Integration
Overview
Deploy and manage Supabase projects in production with confidence. This skill covers the full deployment lifecycle: pushing database migrations, deploying Edge Functions, managing secrets, executing zero-downtime rollouts with blue/green database branching, rolling back failed migrations, and verifying deployment health. All commands use the Supabase CLI with --project-ref for explicit project targeting.
SDK: @supabase/supabase-js — supabase.com/docs
Prerequisites
- Supabase CLI installed (
npm install -g supabaseornpx supabase) - Supabase project linked (
npx supabase link --project-ref <your-ref>) - Database migrations in
supabase/migrations/directory - Edge Functions in
supabase/functions/directory (if deploying functions) SUPABASE_ACCESS_TOKENset for CI/non-interactive environments
Instructions
Step 1 — Push Database Migrations and Deploy Edge Functions
Apply pending database migrations to your production project, then deploy Edge Functions with their required secrets.
Database migrations:
# Apply all pending migrations to production
npx supabase db push --project-ref $PROJECT_REF
# Preview what will run without applying (dry run)
npx supabase db push --project-ref $PROJECT_REF --dry-run
# Check current migration status
npx supabase migration list --project-ref $PROJECT_REF
Each migration file in supabase/migrations/ is applied in timestamp order. The CLI tracks which migrations have already been applied and only runs new ones.
Edge Functions deployment:
# Deploy a single Edge Function
npx supabase functions deploy process-webhook --project-ref $PROJECT_REF
# Deploy all Edge Functions at once
npx supabase functions deploy --project-ref $PROJECT_REF
Secrets management — set environment variables for Edge Functions:
# Set individual secrets
npx supabase secrets set STRIPE_KEY=sk_live_xxx --project-ref $PROJECT_REF
npx supabase secrets set WEBHOOK_SECRET=whsec_xxx --project-ref $PROJECT_REF
# Set multiple secrets at once
npx supabase secrets set API_KEY=value1 SIGNING_KEY=value2 --project-ref $PROJECT_REF
# List current secrets (names only, values hidden)
npx supabase secrets list --project-ref $PROJECT_REF
# Remove a secret
npx supabase secrets unset OLD_KEY --project-ref $PROJECT_REF
Step 2 — Zero-Downtime Deployments and Blue/Green Branching
Use Supabase database branching to test migrations against a production-like environment before cutting over.
Blue/green deployment via dat
Implement custom role-based access control via JWT claims in Supabase: app_metadata.
Supabase Enterprise RBAC
Overview
Supabase supports custom role-based access control (RBAC) by storing role information in app_metadata on the user's JWT, then reading those claims in RLS policies via auth.jwt() ->> 'role'. This skill implements a complete RBAC system: roles in app_metadata, RLS policies that enforce role hierarchies, organization-scoped access, role management through the Admin API, and API endpoints protected with role checks — all using real createClient from @supabase/supabase-js.
When to use: Building multi-role applications (admin/editor/viewer), implementing organization-scoped access, creating custom permission systems beyond Supabase's built-in anon/authenticated roles, or scoping API operations by user role.
Prerequisites
@supabase/supabase-jsv2+ with service role key for admin operations- Understanding of JWT claims and Supabase's
auth.jwt()SQL function - Database access via SQL Editor or
psqlfor RLS policy creation - Supabase project with authentication configured
Instructions
The workflow has three steps: assign roles into the JWT, enforce them in the database with RLS, then enforce them again in application code.
Step 1: Define Roles via app_metadata and JWT Claims
Store custom roles in the user's app_metadata using the Admin API. These claims appear in every JWT the user receives and are readable in RLS policies. Assign roles with the service-role client:
// Define the role hierarchy
type AppRole = 'admin' | 'editor' | 'viewer' | 'member';
// Assign a role to a user (admin operation, service role key required)
async function setUserRole(userId: string, role: AppRole, orgId: string) {
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
app_metadata: { role, org_id: orgId },
});
if (error) throw new Error(`Failed to set role: ${error.message}`);
return data.user;
}
Read the role back in app code with supabase.auth.getUser() and compare it against a numeric hierarchy so hasRole('editor', 'viewer') is true. See role assignment and JWT extraction for the full service-role client setup, granular permissions, bulk assignTeamRoles(), getCurrentUserRole(), getCurrentOrg(), hasRole(), and the requireRole() guard.
Step 2: RLS Policies with JWT Role Claims
Write Row Level Security policies that read auth.jwt() -> 'app_metadata' ->> 'role' and ... ->> 'org_id'. Wrap the JWT extrac
Run your first Supabase query — insert a row and read it back.
Supabase Hello World — First Query
Overview
Execute your first real Supabase query: create a todos table in the dashboard, insert a row with the JS client, and read it back. This validates that your project URL, anon key, and Row Level Security are configured correctly before you build anything else.
Prerequisites
- Completed
supabase-install-authsetup (project URL + anon key in.env) @supabase/supabase-jsv2+ installed (npm install @supabase/supabase-js)- A Supabase project at supabase.com/dashboard
Instructions
The workflow is three steps: create the table (dashboard SQL), insert a row with the JS client, and read it back to prove the round-trip. The skeleton below is enough to follow along; the fully annotated code for every step — including the error-branch comments and expected console output — lives in the full implementation walkthrough.
Step 1: Create the todos Table
In the Supabase dashboard SQL Editor, create the table, enable Row Level Security (required for anon-key access), and add permissive read + insert policies for the hello-world exercise:
create table public.todos (
id bigint generated always as identity primary key,
task text not null,
is_complete boolean default false,
inserted_at timestamptz default now()
);
alter table public.todos enable row level security;
create policy "Allow public read" on public.todos
for select using (true);
create policy "Allow public insert" on public.todos
for insert with check (true);
Lock these policies down before production. Verify the table appears under Table Editor before continuing.
Step 2: Insert a Row
Create the client from your env vars, then insert. Chain .select() to get the inserted row back — .insert() alone returns { data: null }:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
)
const { data, error } = await supabase
.from('todos')
.insert({ task: 'Hello from Supabase!' })
.select()
Step 3: Read It Back
Select all rows and confirm the row you inserted is present:
const { data: todos } = await supabase.from('todos').select('*')
// [{ id: 1, task: "Hello from Supabase!", is_complete: false, ... }]
Open the Table Editor in the dashboard to visually confirm the row is there. See implementation.md for the fully error-handled v
Execute Supabase incident response: dashboard health checks, connection pool status, pg_stat_activity queries, RLS debugging, Edge Function logs, storage health, and escalation.
Supabase Incident Runbook
Overview
A structured response for Supabase-backed application failures. Work three layers in order: triage platform vs. application, run pg_stat_activity database diagnostics, then debug RLS, Edge Functions, and storage — ending with an evidence bundle for support escalation.
When to use: Production errors involving Supabase, degraded API response times, connection pool exhaustion, silent data filtering from RLS, Edge Function cold start failures, or storage upload/download errors.
Each step below gives the workflow plus a first command. The complete copy-paste blocks for every step live in references/diagnostics.md.
Prerequisites
- Supabase project with dashboard access at supabase.com/dashboard
@supabase/supabase-jsv2+ installed in your project- Supabase CLI installed for Edge Function log access
- Direct database connection string (for
psqldiagnostics) - Access to status.supabase.com for platform health
Instructions
Step 1: Triage — Platform vs. Application
Determine whether the issue is a Supabase platform incident or an application-level bug. Check the official status page first, then verify SDK client connectivity. Use Read to inspect the app's Supabase env config and Grep to scan application logs for HTTP error codes (401=auth, 429=rate limit, 500=server).
# Check official status page — is this a platform-wide incident?
curl -sf https://status.supabase.com/api/v2/status.json | jq '.status'
# Expected: { "indicator": "none", "description": "All Systems Operational" }
If the status page is green, run the SDK healthCheck() (a select 1 against a small _health_check table) to measure latency and confirm connectivity. A green platform plus a failing health check points at your queries, RLS, or Edge Functions — the SDK block, incident-check query, and full decision tree are in references/diagnostics.md.
Step 2: Database Diagnostics with pg_stat_activity
Connect directly via psql (or the Supabase SQL Editor) to inspect connections, find stuck queries, and detect leaks.
-- Current connections grouped by state — the first thing to run
SELECT state, count(*) AS connections,
max(extract(epoch FROM age(now(), state_change)))::int AS max_idle_seconds
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY state ORDER BY connections DESC;
-- WARNING: If idle > 20 or idle_in_transaction > 0, you have a leak
From there, drill into
Install and configure Supabase SDK, CLI, and project authentication.
Supabase Install & Auth
Overview
Install the Supabase SDK, CLI, and project credentials from scratch — covering package install, environment configuration, client initialization, and connection verification for both TypeScript (@supabase/supabase-js) and Python (supabase).
Key facts:
- npm package:
@supabase/supabase-js - Python package:
supabase(via pip) - Client factory:
createClient()— nevernew SupabaseClient() - Dashboard: https://supabase.com/dashboard (Settings > API for keys)
- Docs: https://supabase.com/docs
Prerequisites
- Node.js 18+ (for JS/TS) or Python 3.8+ (for Python)
- Package manager: npm, pnpm, or yarn (JS) / pip (Python)
- A Supabase project created at https://supabase.com/dashboard
- Docker Desktop (only if using local development via
supabase start)
Instructions
Step 1 — Install the SDK and CLI
Install the SDK and the Supabase CLI:
JavaScript / TypeScript:
# Install the SDK
npm install @supabase/supabase-js
# For SSR frameworks (Next.js, SvelteKit, Nuxt), also install:
npm install @supabase/ssr
# Install the Supabase CLI (for types, migrations, local dev)
npm install -D supabase
Python:
# Install the SDK
pip install supabase
# Install the CLI (alternative: brew install supabase/tap/supabase)
npm install -g supabase
Verify the CLI is available:
npx supabase --version
Step 2 — Configure Environment Variables
Retrieve project credentials from the Supabase Dashboard (Settings > API) and create the env file:
# .env.local (or .env)
SUPABASE_URL=https://<project-ref>.supabase.co
SUPABASE_KEY=eyJhbGciOiJIUzI1NiIs... # anon key — safe for client-side
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1... # admin key — server-side ONLY
Add env files to .gitignore immediately:
.env
.env.local
.env.*.local
Security rules:
- The anon key (
SUPABASE_KEY) is safe for client-side code. It respects Row Level Security (RLS) policies. - The service role key (
SUPABASE_SERVICE_ROLE_KEY) bypasses RLS entirely. Use only on the server. Never bundle into client code or expose in browser.
Step 3 — Initialize the Client and Verify
Create a client singleton and verify connectivity:
TypeScript — client-side (anon key):
// lib/supabUse when reviewing Supabase code, onboarding developers, auditing an existing project, or debugging unexpected behavior — catches the twelve most common Supabase mistakes: exposing the service_role key in client bundles, forgetting to enable RLS, skipping connection pooling in serverless, .
Supabase Known Pitfalls
Overview
The twelve most common Supabase mistakes, ranked by severity: security (service_role exposure, missing RLS, permissive policies, no connection pooling), data integrity (ignoring { data, error }, missing .select() after mutations, .single() on optional results), and performance / maintainability (select('*'), N+1 queries, missing FK indexes, multiple client instances, no generated types). Each pitfall shows the broken code, why it fails, and the correct pattern using createClient from @supabase/supabase-js.
This SKILL.md carries the full pitfall table plus one representative fix per category. The verbatim broken-vs-correct code and detection queries for all twelve live in references/pitfalls.md — drill in there for depth.
Prerequisites
- Access to a Supabase project codebase for review
@supabase/supabase-jsv2+ installed- Basic understanding of Row Level Security (RLS)
Instructions
Work the pitfalls top-down by severity. Fix every Critical finding before moving on — a single security miss can expose the whole database.
| # | Pitfall | Severity | Fix | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | service_role key in client bundle | Critical | anon key on client; service_role server-only, no NEXT_PUBLIC_ |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2 | Table without RLS | Critical | ALTER TABLE … ENABLE ROW LEVEL SECURITY right after CREATE TABLE |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3 | Overly permissive RLS policy | Critical | scope USING (…) to auth.uid(), never USING (true) for writes |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4 | No connection pooling in serverless | Critical | pooled string (Supavisor, port 6543), not the direct 5432 URL | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5 | Ignoring { data, error } |
High | destructure both; check error before touching data |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6 | Missing .select() after mutation |
High | chain .select('cols') — mutations return null otherwise |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 7 | .single() on optional result |
High | use .maybeSingle() for 0-or-1; .single() only for guaranteed 1 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 8 | select('*') everywhere |
Medium | name the columns — smaller payload, typed, no leakage | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 9 | N+1 query loop | Medium | PostgREST embedded join, or batch with .in() |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 10 |
| Metric | Free | Pro | Enterprise |
|---|---|---|---|
| Requests per minute (RPM) | 500 | 5,000 | Unlimited (custom) |
| Requests per day (RPD) | 50,000 | 1,000,000 | Unlimited (custom) |
Auth, Storage, Realtime, Edge Functions, and Database connections each carry their own quotas. See the full per-surface breakdown in rate-limit-tiers.md before you architect.
Step 2 — Pool connections with Supavisor
Supavisor is Supabase's built-in connection pooler (replaced PgBouncer). Pick the mode by workload:
| Use case | Mode | Port |
|---|---|---|
| Serverless / Edge Functions | Transaction | 6543 |
| Next.js API routes | Transaction | 6543 |
| Long-running workers | Session | 5432 |
| Realtime subscriptions | Direct (no pooler) | 5432 |
| Prisma / Drizzle ORM | Transaction + ?pgbouncer=true |
6543 |
Transaction mode (port 6543) returns a connection to the pool after each transaction — the right default for serverless. Session mode (port 5432) holds a dedicated connection for LISTEN/NOTIFY and prepared statements. Full client setup and connection-string formats are in implementation.md.
Step 3 — Retry, paginate, and batch
Wrap queries in an exponential-backoff retry that recognizes 429s and pool exhaustion:
// Retryable when the error is a rate limit, "too many requests",
// code 429, or PGRST000 (connection pool exhausted). Delay doubles
// per attempt with jitter, capped at maxDelayMs, honoring Retry-After.
const users = await withRetry(() =>
supabase.from('users').select('id, email, created_at').eq('active', true)
)
Then cut request volume two ways: paginate large reads with .range(f
Implement enterprise Supabase reference architectures — monorepo layout, multi-tenant RLS, microservices with cross-project access, framework integration, edge functions, caching, queue patterns, and audit logging.
Supabase Reference Architecture
Overview
Production Supabase applications need more than a flat lib/supabase.ts file. This skill covers five enterprise architecture patterns: monorepo with shared types, multi-tenant RLS isolation, microservices with separate Supabase projects, framework integration (Next.js / SvelteKit), and operational patterns (edge functions, caching, queues, audit trails). Each pattern stands alone — pick the ones that match your scale.
For the full monorepo directory layout and microservices cross-project access, see Project Structure. For edge functions, caching, queue, and audit trail patterns, see Operational Patterns.
Prerequisites
@supabase/supabase-jsv2+ installed (npm install @supabase/supabase-js)- Supabase CLI installed (
npm install -g supabase) - A Supabase project at supabase.com/dashboard
- Familiarity with
supabase-install-auth(project URL, anon key, service role key) - PostgreSQL basics (RLS policies, triggers, functions)
Instructions
Step 1: Client Singleton — The Foundation
Every app in the monorepo imports from a shared package instead of creating its own client. This guarantees a single source of truth for the URL, keys, and type definitions.
// packages/supabase/src/client.ts
import { createClient, SupabaseClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
let client: SupabaseClient<Database> | null = null
export function getSupabaseClient(): SupabaseClient<Database> {
if (!client) {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL ?? process.env.SUPABASE_URL
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? process.env.SUPABASE_ANON_KEY
if (!url || !key) {
throw new Error('Missing SUPABASE_URL or SUPABASE_ANON_KEY environment variables')
}
client = createClient<Database>(url, key)
}
return client
}
// Reset for testing
export function resetClient(): void {
client = null
}
// packages/supabase/src/admin.ts — Server-side only, never bundle in client code
import { createClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
export function getSupabaseAdmin() {
const url = process.env.SUPABASE_URL
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
if (!url || !serviceKey) {
throw new Error('Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY — server-only')
}
return createClient<Database>(url, serviceKey, {
auth: { autoRefreshToken: false, persistSession: false }
})
}
Key detail: The admin clien
Build resilient Supabase integrations: circuit breakers wrapping createClient calls, offline queue with IndexedDB, graceful degradation with cached fallbacks, health check endpoints, retry with exponential backoff and jitter, and dual-write patterns for critical data.
Supabase Reliability Patterns
Overview
Production Supabase apps need six reliability layers: circuit breakers (stop calling Supabase when it's down to prevent cascading failures), offline queue (buffer writes when the network is unavailable and replay when reconnected), graceful degradation (serve cached or fallback data during outages), health checks (detect Supabase availability before routing traffic), retry with exponential backoff (handle transient errors without overwhelming the service), and dual-write (write critical data to both Supabase and a backup store). All patterns use real createClient from @supabase/supabase-js.
Prerequisites
@supabase/supabase-jsv2+ installed- TypeScript project with Supabase client configured
- For offline queue: browser environment with IndexedDB or server with Redis
- For dual-write: secondary data store (Redis, DynamoDB, or local SQLite)
Instructions
Step 1 — Circuit Breaker and Retry with Exponential Backoff
A circuit breaker tracks failures per Supabase service (database, auth, storage) and stops making calls when a threshold is exceeded. Combined with retry logic, it prevents both cascading failures and unnecessary retries during extended outages.
Circuit Breaker Implementation
// lib/circuit-breaker.ts
import { createClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN'
interface CircuitBreakerOptions {
failureThreshold: number // failures before opening
resetTimeoutMs: number // ms before trying again (half-open)
halfOpenSuccesses: number // successes in half-open to close
name: string // for logging
}
class CircuitBreaker {
private state: CircuitState = 'CLOSED'
private failures = 0
private lastFailureTime = 0
private halfOpenSuccesses = 0
constructor(private opts: CircuitBreakerOptions) {}
async call<T>(fn: () => Promise<T>, fallback?: () => T): Promise<T> {
// Check if circuit should transition from OPEN to HALF_OPEN
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > this.opts.resetTimeoutMs) {
this.state = 'HALF_OPEN'
this.halfOpenSuccesses = 0
console.log(`[CircuitBreaker:${this.opts.name}] OPEN → HALF_OPEN`)
} else {
if (fallback) return fallback()
throw new Error(`Circuit breaker ${this.opts.name} is OPEN`)
}
}
try {
const result = await fn()
// Success in HALF_OPEN: count toward recovery
if (this.state === 'HALF_OPEN') {
this.halfOpenSuccesses++
if (this.Design Supabase Postgres schema from business requirements with migrations, RLS, and types.
Supabase Schema from Requirements
Overview
Translate business requirements into a production-ready Postgres schema inside Supabase, covering the full path from specification to applied migration: entity extraction, tables with proper types and constraints, Row Level Security policies, performance indexes, timestamp triggers, and TypeScript type generation. Getting this right early matters because every downstream feature — auth, storage, realtime, edge functions — depends on well-designed tables.
Prerequisites
- Supabase CLI installed (
npm install -g supabase) and project linked (supabase link) @supabase/supabase-jsv2+ installed in the project- Business requirements, PRD, or specification document identifying entities and access rules
- Local Supabase running (
supabase start) or a linked remote project
Instructions
Step 1: Parse Requirements and Create Migration
Read the requirements document and extract entities, attributes, relationships, and access control rules. Map each entity to a Postgres table.
Entity extraction example (project management app):
| Entity | Key Columns | Relationships |
|---|---|---|
| Organization | name, slug, plan | has many Projects, has many Members |
| Project | name, description, status | belongs to Organization, has many Tasks |
| Task | title, priority, status, due_date | belongs to Project, assigned to User |
| Member | role (owner/admin/member) | junction linking User to Organization |
Create the migration file:
npx supabase migration new create_tables
# Creates: supabase/migrations/TIMESTAMP_create_tables.sql
Write the migration SQL using standard Postgres data types (uuid, text, integer, boolean, timestamptz, jsonb): Full worked migration (project-management app — tables, FKs, indexes, moddatetime triggers): references/worked-schema-examples.md.
Data type selection guide:
| Use case | Type | Notes | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Primary keys | uuid |
Always with uuid_generate_v4() default |
|||||||||||||||||||||||||||
| Names, titles | text |
Prefer over varchar in Postgres |
|||||||||||||||||||||||||||
| Counts, ranks | integer |
Use bigint for sequences |
|||||||||||||||||||||||||||
| Flags | boolean |
Default explicitly to true or false |
|||||||||||||||||||||||||||
| Timesta
supabase-sdk-patterns
View full skill →
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.
ReadWriteEditGrep
Supabase SDK PatternsOverviewProduction patterns for Prerequisites
InstructionsStep 1: Initialize a typed singleton clientCreate one client instance and reuse it. Never call
Python equivalent:
Step 2: Query, filter, and mutate dataDestructure
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. Step 3: Auth, realtime, and storageAuth, realtime channels, and storage all follow the same
supabase-security-basics
View full skill →
Apply Supabase security best practices: anon vs service_role key separation, RLS enforcement, policy patterns, JWT verification, and API hardening.
ReadWriteEditGrepBash(supabase:*)Bash(npx supabase:*)
Supabase Security BasicsOverviewSupabase exposes a Postgres database directly to the internet via PostgREST. Every table without Row Level Security enabled is fully readable and writable by anyone with your project URL and anon key — both of which are public. This skill covers the three pillars of Supabase security: key separation (anon vs service_role), RLS policy enforcement, and API surface hardening. Prerequisites
InstructionsStep 1 — Understand the Two API KeysSupabase issues two keys per project. Confusing them is the most common security mistake:
The anon key is a JWT that PostgREST uses to determine which RLS policies apply. It is safe to include in client-side bundles — it can only access data that RLS policies explicitly allow. The service role key bypasses every RLS policy and should only ever exist in server-side code (API routes, Edge Functions, cron jobs, migration scripts).
Key rotation: Regenerate keys in Dashboard > Settings > API. After rotation, update every environment variable and redeploy all services. Old keys are invalidated immediately — there is no grace period. Step 2 — Enforce Row Level Security on Every TableWithout RLS,
supabase-upgrade-migration
View full skill →
Upgrade Supabase SDK and CLI versions with breaking-change detection and automated code migration.
ReadWriteEditBash(npm:*)Bash(npx:*)Bash(pip:*)Bash(supabase:*)Bash(git:*)GrepGlob
Supabase Upgrade MigrationOverviewUpgrade Current State! Prerequisites
InstructionsStep 1: Audit Versions, Scan Usage, and Review Breaking ChangesCheck every installed Supabase package and find all import sites in the codebase.
supabase-js v1 → v2 breaking changes:
|