new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Supabase · Auth · all subjects

troubleshooting & edge cases

35 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

RLS is latency-sensitive with FDW

When using Foreign Data Wrappers in RLS policies, RLS becomes latency-sensitive due to extra database calls. Use the query plan analyzer to measure execution times and ensure queries are within expected ranges. For enterprise applications, contact enterprise@supabase.io for optimization guidance.

Built-in PostgREST retries enabled by default in supabase-js v2.102.0+

Starting with supabase-js v2.102.0, PostgREST queries using .from() and .rpc() include built-in automatic retries for transient errors. Retries are enabled by default and use exponential backoff with jitter.

Retryable HTTP status codes for PostgREST

PostgREST automatic retries trigger on HTTP status codes 408 (Request Timeout), 409 (Conflict), 503 (Service Unavailable), and 504 (Gateway Timeout), as well as network failures.

Only idempotent methods retried in PostgREST

Only idempotent HTTP methods (GET, HEAD, OPTIONS) and POST requests used by PostgREST are retried. Other HTTP methods are not automatically retried.

Disable PostgREST built-in retries

To disable built-in PostgREST retries, pass the option db.retry: false when creating the Supabase client. Use this if you prefer to handle retries yourself.

Disable PostgREST retries configuration example

The following example shows how to disable built-in retries: import { createClient } from '@supabase/supabase-js'; const supabase = createClient('https://your-project-id.supabase.co', 'your-publishable-key', { db: { retry: false, }, });

Use fetch-retry for non-PostgREST retries

The fetch-retry package allows you to add retries to non-PostgREST requests such as auth, storage, and functions. It wraps the native fetch function and applies retry logic to all requests made by the Supabase client.

Install fetch-retry and supabase-js

To use fetch-retry with Supabase, install both packages: npm install @supabase/supabase-js fetch-retry

Configure fetch-retry with Supabase client

To integrate fetch-retry with Supabase, wrap the fetch function and pass it to createClient: import { createClient } from '@supabase/supabase-js'; import fetchRetry from 'fetch-retry'; const fetchWithRetry = fetchRetry(fetch); const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...', { global: { fetch: fetchWithRetry, }, });

fetch-retry configuration options

fetch-retry accepts configuration options including: retries (number of retry attempts), retryDelay (function returning delay in milliseconds), and retryOn (array of HTTP status codes or custom function to determine whether to retry). Example: const fetchWithRetry = fetchRetry(fetch, { retries: 3, retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000), retryOn: [520], });

fetch-retry exponential backoff with cap

A common fetch-retry retryDelay strategy is exponential backoff with a maximum delay: (attempt) => Math.min(1000 * 2 ** attempt, 30000). This doubles the delay with each attempt, starting at 1 second on the first retry and capping at 30 seconds.

Custom retryOn function in fetch-retry

The retryOn option in fetch-retry can accept a custom function with signature (attempt, error, response) => boolean. This allows inspecting the attempt number, error, and response to decide whether to retry. Example: retryOn: (attempt, error, response) => { const shouldRetry = (attempt, error, response) => attempt < 3 && response && response.status == 520 && response.url.includes('rpc/your_database_function'); if (shouldRetry(attempt, error, response)) { console.log(`Retrying request... Attempt #${attempt}`, response); return true; } return false; }

Warning: High retry counts risk connection pool exhaustion

Enabling retries with a high number of attempts can exhaust the Data API connection pool, resulting in lower throughput and failed requests. Only enable retries for network errors like Cloudflare 520 status codes.

Use built-in retries for most PostgREST cases

For most use cases, the built-in PostgREST retry mechanism is sufficient. Use fetch-retry only when you need retries on non-PostgREST requests or need fine-grained control over retry behavior.

error.hint contains actionable fixes from Postgres

The error.hint field on a PostgrestError contains the exact fix Postgres provides. For example, permission-denied errors include the literal GRANT SQL statement to run. Always check error.hint first before reading other fields.

Log full error object, not just error.message

When logging errors from supabase-js calls, log the complete error object rather than only error.message. Logging only the message field hides important information like hints and details that are necessary for debugging.

supabase-js returns { data, error } pair, never throws

Every supabase-js call returns an object containing data and error fields as a { data, error } pair instead of throwing exceptions. Check the error field to handle failures.

PostgrestError fields by usefulness order

PostgrestError contains four fields in order of usefulness: hint (always check first for actionable fix), code (use for branching logic as it is stable across versions), details (contains offending value or key when hint and message are not enough), and message (human summary, less useful for debugging).

Branch on error.code, not error.message

For programmatic branching, use error.code instead of error.message because codes are stable across Postgres and PostgREST versions, while message text changes between versions.

PostgrestError example: permission denied with GRANT hint

A permission-denied error on a table with revoked GRANT from anon role produces: code 42501, message 'permission denied for table users', and hint containing the exact GRANT statement needed to fix it.

Recommended error handling pattern for database calls

The recommended pattern is: destructure { data, error } from the response, check if error exists, log the whole error object, and return early. This applies to database calls like select, insert, update, upsert, delete, and rpc.

AuthError fields: code and status

AuthError exposes error.code (e.g. 'invalid_credentials', 'email_not_confirmed') and error.status. Branch on code and log the whole error object.

StorageError fields: statusCode and name

StorageError exposes error.statusCode as an HTTP status string and a structured error name field (e.g. 'Duplicate', 'NotFound').

Edge Functions errors have three subclasses

Edge Functions errors are one of three subclasses: FunctionsHttpError, FunctionsFetchError, or FunctionsRelayError. Narrow with instanceof. For FunctionsHttpError, parse error.context.json() to get the function's own error payload.

Realtime subscribe callback receives status and err

The supabase.channel().subscribe() callback receives a status parameter and an err argument on failure. Log the whole err object as its cause field often holds the underlying reason.

Database call error handling example

Example: const { data, error } = await supabase.from('users').select(); if (error) { console.error(error); return; }

Auth error handling example

Example: const { data, error } = await supabase.auth.signInWithPassword({ email: 'example@email.com', password: 'exa••••••rd' }); if (error) { console.error(error); return; }

Storage error handling example

Example: const { data, error } = await supabase.storage.from('avatars').upload('public/avatar1.png', avatarFile); if (error) { console.error(error); return; }

Edge Functions error handling example with instanceof

Example showing how to handle Edge Functions errors: import { FunctionsFetchError, FunctionsHttpError, FunctionsRelayError } from '@supabase/supabase-js'; const { data, error } = await supabase.functions.invoke('hello'); if (error instanceof FunctionsHttpError) { console.error('Function error', await error.context.json()); } else if (error) { console.error(error); }

Realtime error handling example

Example: supabase.channel('room1').subscribe((status, err) => { if (status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') { console.error(status, err); } });

Audit logs stored in external log storage and optional Postgres database

Audit logs are stored in external log storage by default, which is cost-efficient and accessible through the dashboard. Optionally, logs can be stored in the Postgres database in the auth.audit_log_entries table, which is searchable via SQL but uses additional database storage. You can enable or disable Postgres database storage to optimize costs.

How to configure audit log storage in Supabase dashboard

To configure audit log storage: 1) Navigate to your project's dashboard. 2) Go to Authentication. 3) Find the Audit Logs under the Configuration section. 4) Toggle 'Write audit logs to the database' on to enable or off to disable database storage.

Audit log entry format and fields

Audit log entries contain a timestamp (ISO 8601 format), user_id (UUID), action (string), ip_address, user_agent, and metadata object. Example: {"timestamp": "2025-08-01T10:30:00Z", "user_id": "uuid", "action": "user_signedup", "ip_address": "192.168.1.1", "user_agent": "Mozilla/5.0...", "metadata": {"provider": "email"}}

Complete reference of audit log actions

Supabase auth audit logs track the following actions: login (user login attempt), logout (user logout), invite_accepted (team invitation accepted), user_signedup (new user registration), user_invited (user invitation sent), user_deleted (user account deleted), user_modified (user profile updated), user_recovery_requested (password reset request), user_reauthenticate_requested (user reauthentication required), user_confirmation_requested (email/phone confirmation requested), user_repeated_signup (duplicate signup attempt), user_updated_password (password change completed), token_revoked (refresh token revoked), token_refreshed (refresh token used to obtain new tokens), generate_recovery_codes (MFA recovery codes generated), factor_in_progress (MFA factor enrollment started), factor_unenrolled (MFA factor removed), challenge_created (MFA challenge initiated), verification_attempted (MFA verification attempt), factor_deleted (MFA factor deleted), recovery_codes_deleted (MFA recovery codes deleted), factor_updated (MFA factor settings updated), mfa_code_login (login with MFA code), and identity_unlinked (an identity unlinked from account).

Audit logs limitations

Auth audit logs have the following limitations: there may be a short delay before logs appear, and query capabilities are limited to the dashboard interface.

Give your agent this brain