Skip to content
Telemetry

Debugging

Debug by evidence, not by guessing. A Supabase error almost always surfaces at one layer but originates at another, so the fastest path to a fix is finding where the problem is, not pattern-matching the symptom. Retrying a failed request rarely helps; isolating the layer does.

The debugging loop#

Work through these steps in order, skipping straight to a fix before you have evidence for the cause is the most common way to waste time on a bug.

  1. Reproduce the issue and read the error precisely. Capture the exact status code, the error code, and the full message, not a paraphrase. A 401 is not a 403; PGRST002 is not PGRST106; a Postgres SQLSTATE such as 42501, 42P01, or 23505 points at the exact failure. The precise error is your strongest clue. If you're using supabase-js, remember that errors are returned, not thrown, check the error field in the { data, error } response object. Make sure your code inspects error — a swallowed error is why many bugs look like "nothing happened".
  2. Locate the failing layer. Use the request stack below. The status code and error code usually name the layer for you.
  3. Gather evidence for that layer. Query its logs, run the security and performance advisors, and inspect the schema. Logs are the primary tool, and the layer you identified in the previous step tells you which log source to query. See Reading logs below.
  4. Isolate the cause using the troubleshooting guide for that layer (see Find the guide for your symptom below). Confirm your hypothesis against the evidence before you act. Most Supabase issues trace back to a small, known set of causes, and the guide explains how to tell them apart.
  5. Apply the fix, then verify. Re-run the exact operation that failed and confirm it now succeeds, and that the corresponding log line is clean. A fix you haven't re-run is still a guess. If a couple of attempts don't resolve it, stop and gather more evidence rather than repeating the same change.

The Supabase request stack#

A request from a client passes through several layers before it reaches your data. Errors propagate upward, so the layer that reports an error is often not the layer that caused it.

Knowing the shape of the stack is what makes isolating the layer possible.

1
Client (supabase-js / SSR)
2
→ Edge / API gateway → edge_logs (HTTP status, routing, rate limits)
3
The gateway routes each request to ONE of these services. They run in parallel,
4
not as a chain:
5
├→ PostgREST (Data API) → postgrest_logs (low-signal; PGRST* evidence lives in edge_logs and postgres_logs)
6
├→ GoTrue (Auth) → auth_logs (login, JWT, OAuth, email)
7
├→ Storage API → storage_logs (uploads, object access)
8
└→ Realtime → realtime_logs (channels, presence, broadcast)
9
PostgREST, GoTrue, and Storage each reach the database independently:
10
→ Supavisor (connection pooler) → supavisor_logs (pooling, timeouts)
11
→ Postgres (SQL, RLS, triggers) → postgres_logs (SQLSTATE, RLS, functions)

Edge Functions sit outside this stack and log separately: function_edge_logs for the HTTP request to the function, and function_logs for console output from inside it.

Reading logs#

Once you know the layer, query that layer's log source directly rather than scanning everything. Pick one source, bound the time window, and select only the fields you need.

When a query comes up empty, widen along an anchor, such as a timestamp, request ID, or error code, to follow the same request into the adjacent source (for example from edge_logs into postgres_logs) instead of broadening into an unfiltered scan.

A wide, unfiltered query across every source buries the one line you need and, on paid projects, costs more in scanned data.

The Logging guide covers the Logs Explorer, the available log sources, and how to write queries against them.

Find the guide for your symptom#

Match your symptom to a layer, confirm it against that layer's logs, then open the troubleshooting guide for the specific cause and fix. If a symptom could fit two layers (for example, an auth call failing with what looks like an RLS error), start with the layer closest to the database.

Supabase updates these troubleshooting guides continuously, so treat this table as a starting point rather than the final word: if your exact symptom isn't listed, search the troubleshooting index for the error string.

Symptom / errorLayer → log sourceTroubleshooting guides
Empty data array with rows present; wrong rows returned; UPDATE/DELETE affects 0 rows; 42501 permission denied; service_role still blocked; policy not matchingRLS & access → postgres_logsEmpty select array · service_role hits RLS · Database API 42501 · RLS Simplified · Deprecated RLS features
PGRST002/PGRST106; "schema cache"; "could not find table/relationship"; new column or table not recognized; 42P01; 520; API returns nothingData API (PostgREST) → edge_logs, postgres_logsRefresh schema cache · PGRST002 · New objects not recognized · 42P01 · 520 errors · API not returning
Login/logout/session broken; JWT "invalid claim"/"missing sub"; cookies not sent; OAuth redirect wrong; OTP/magic-link expired; MFA/TOTP fails; auth 500/503; emails not arrivingAuth → auth_logs, postgres_logs401 missing sub · 500 auth errors · 503 AuthRetryableFetchError · OTP expired · OAuth not redirecting · No auth emails · Next.js auth
statement timeout; duplicate key or sequence error; trigger errors; slow ALTER; blocked queries; disk/memory/swap pressure; index sizeDatabase (Postgres) → postgres_logsStatement timeout · Duplicate key / sequence · Blocked queries · Disk not shrinking · Autovacuum stalled · High CPU
"too many connections"; "remaining connection slots"; CONNECT_TIMEOUT; pooler vs. direct connection; read-only transaction; prepared statement already exists; no pg_hba.conf entry; IPv4/IPv6; SASL/SCRAMConnections & pooler → supavisor_logs, postgres_logsToo many connections · Remaining slots · Prepared statement exists · Read-only transaction · CONNECT_TIMEOUT · Supavisor terminology
Edge Function 401/404/500/503/504/546; CPU/memory/wall-clock limit hit; won't deploy; boot error; WebSocket drop; esm.sh import failsEdge Functions → function_edge_logs, function_logs401 · 500 · 503 boot · 504 · 546 resource limit · Shutdown reasons · Deploy fails · esm.sh import
Realtime TIMED_OUT; TooManyChannels; silent disconnect; missed database changes; broadcast-from-DB warning; heartbeatsRealtime → realtime_logsTIMED_OUT · TooManyChannels · Silent disconnects · Broadcast warning · Heartbeats · Logger
Upload or list fails; public bucket inaccessible; relation "objects" does not exist; file size limit; folder or RLS issueStorage → storage_logs, postgres_logsPublic bucket upload/list · 403 RLS on upload · relation objects does not exist · File size limits · Folder ops / hierarchical RLS
Webhook not firing; pg_cron job not running; pg_net queue stuck; 42501 ... http_request_queueDatabase jobs → postgres_logsWebhook debugging · pg_cron debugging · 42501 http_request_queue
Reading or querying logs; interpreting Postgres logs; finding API errors in logs; reading metricsDiagnostics → any log sourceLogging guide · Interpret Postgres logs · API errors in logs · Logging levels · View database metrics

For query performance and schema-design questions such as indexing, EXPLAIN, N+1 queries, or partitioning, see the Postgres guides.

Debugging is complete only once you've re-run the failing operation, confirmed it succeeds, and checked that the layer's logs show a clean result.