supabase-common-errors

'Diagnose and fix Supabase errors across PostgREST, PostgreSQL, Auth,

Allowed Tools

ReadGrepBash(curl:*)Bash(supabase:*)Bash(npx:*)

Provided by Plugin

supabase-pack

Claude Code skill pack for Supabase (30 skills)

saas packs v1.53.0
View Plugin

Installation

This skill is included in the supabase-pack plugin:

/plugin install supabase-pack@claude-code-plugins-plus

Click to copy

Instructions

Supabase Common Errors

Overview

Diagnostic guide for Supabase errors across PostgREST (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.

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.

Prerequisites

  • @supabase/supabase-js installed (npm install @supabase/supabase-js)
  • SUPABASEURL and SUPABASEANONKEY (or SUPABASESERVICEROLEKEY) configured
  • Access to Supabase Dashboard (for log inspection and SQL Editor)
  • Supabase CLI installed for local development (npx supabase --version)

Instructions

Step 1 — Capture the Error Object

Every Supabase SDK call returns a { 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`)

If 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

Match the code prefix to its subsystem, then look it up in the Error Handling tables below:

  • *PGRST** → PostgREST (API gateway: JWT, query parsing, schema)
  • 5-digit numeric (e.g. 42501, 23505) → PostgreSQL engine (RLS, constraints, migrations)
  • AuthApiError → Auth service (credentials, confirmation, token expiry)
  • StorageApiError → Storage service (bucket, RLS on storage.objects, size limits)

A missing code usually means the HTTP status is the signal: 401 → bad/missing SUPABASEANONKEY; 500 → an unhandled exception in a database function. A paste-ready diagnoseSupabaseError() classifier is in the walkthrough.

Step 3 — Apply the Fix and Verify

Apply the fix from the matching Error Handling table, then re-run the original operation to confirm. Common recoveries:

  • Refresh an expired JWT (PGRST301) with supabase.auth.refreshSession().
  • Confirm an RLS block (42501) by re-querying with the service-role client before correcting the policy.
  • After a migration, reload the PostgREST schema cache (Dashboard → Settings → API → "Reload schema cache", or NOTIFY pgrst, 'reload schema').

Full before/after fix code for both cases is in the walkthrough.

Output

Deliverables after applying this skill:

  • Error identified by code and layer (PostgREST, PostgreSQL, Auth, Storage, Realtime)
  • Root cause isolated using the diagnostic helper or manual code inspection
  • Fix applied from the Error Handling table and verified against the original failing operation
  • Guard code in place (if (error) checks) preventing silent null-data bugs

Error Handling

The two most-cited layers are inline below. Auth, Storage, and Realtime tables are in the full error reference.

PostgREST API Errors (PGRST*)

Code HTTP Meaning Root Cause Fix
PGRST301 401 JWT expired or invalid SUPABASEANONKEY is wrong, or the user session expired Verify SUPABASEANONKEY matches the project; call supabase.auth.refreshSession()
PGRST302 401 Missing Authorization header Client created without a key, or middleware stripped the header Pass SUPABASEANONKEY to createClient(); check proxy/CDN config
PGRST116 406 No rows returned for .single() Query matched 0 rows but .single() expects exactly 1 Use .maybeSingle() for optional lookups, or check filters
PGRST200 400 Invalid query parameters Malformed filter, bad operator, or invalid column reference Check filter syntax: .eq('col', val) not .eq('col = val')
PGRST204 400 Column not found Column name doesn't exist in the table or view Verify column exists with supabase gen types typescript; check for typos
PGRST000 503 Connection pool exhausted Too many concurrent connections from serverless functions Enable pgBouncer (Supavisor) in project settings; reduce connection count

PostgreSQL Database Errors (5-digit codes)

Code Meaning Root Cause Fix
42501 RLS policy violation Row-level security is blocking the operation for this user Add or fix the RLS policy; test with service role to confirm
23505 Unique constraint violation INSERT/UPDATE conflicts with an existing row Use .upsert({ onConflict: 'column' }) or check existence first
23503 Foreign key violation Referenced row doesn't exist in the parent table Insert the parent row first, or check the foreign key value
42P01 Table or relation doesn't exist Migration not applied, or wrong schema Run supabase db push; verify schema with \dt in SQL Editor
42703 Column doesn't exist Schema out of sync with code Regenerate types: supabase gen types typescript --local > types/supabase.ts
57014 Query cancelled (statement timeout) Query took longer than statement_timeout Add indexes; simplify the query; increase timeout in postgresql.conf

Examples

The most common failure — calling .single() on optional data — is inline below. Three more worked examples (upsert to dodge 23505, Realtime subscription error handling, and serverless pool exhaustion) are in the examples reference.

Handling .single() on optional data (PGRST116)


// BAD — throws PGRST116 when the user has no profile row
const { data: profile } = await supabase
  .from('profiles').select('*').eq('user_id', userId).single()

// GOOD — returns null instead of erroring
const { data: profile, error } = await supabase
  .from('profiles').select('*').eq('user_id', userId).maybeSingle()

if (!profile) {
  await supabase.from('profiles').insert({ user_id: userId, display_name: 'New User' })
}

Resources

Next Steps

  • Use supabase-debug-bundle to generate a full diagnostic snapshot when errors persist after applying these fixes.
  • Use supabase-security-basics to audit your RLS policies and prevent 42501 errors proactively.
  • Use supabase-known-pitfalls for edge cases and SDK behavior that can cause subtle bugs.
  • Use supabase-observability to set up logging and alerting so you catch errors before users report them.

Ready to use supabase-pack?