# Error Handling ## Error classes The SDK has two error classes, both with `status` (HTTP code) and `code` (machine-readable string) properties. ### EnvError Thrown when a required environment variable is missing or malformed. Always `status: 500` — these are server configuration issues, not client errors. | Code | Meaning | | --------------------------------- | -------------------------------------------------------------- | | `MISSING_SUPABASE_URL` | `SUPABASE_URL` is not set | | `MISSING_PUBLISHABLE_KEY` | Named publishable key not found in `SUPABASE_PUBLISHABLE_KEYS` | | `MISSING_DEFAULT_PUBLISHABLE_KEY` | No default publishable key found | | `MISSING_SECRET_KEY` | Named secret key not found in `SUPABASE_SECRET_KEYS` | | `MISSING_DEFAULT_SECRET_KEY` | No default secret key found | | `ENV_ERROR` | Generic environment error | ### AuthError Thrown when authentication or authorization fails. Status is `401` for invalid credentials, `500` for server-side auth failures. | Code | Status | Meaning | | ------------------------------ | ------ | ----------------------------------------------------------------------------------------- | | `INVALID_CREDENTIALS` | 401 | No credential matched any allowed auth mode, or a JWT was present but failed verification | | `CREATE_SUPABASE_CLIENT_ERROR` | 500 | Auth succeeded but client creation failed | | `AUTH_ERROR` | 401 | Generic authentication error | ## How errors surface in each layer Different layers of the SDK handle errors differently. Understanding which pattern each function uses prevents surprises. | Function | Pattern | What happens on error | | ------------------------- | ------------- | ------------------------------------------------------------------------ | | `withSupabase()` | Auto-response | Returns `Response.json({ message, code }, { status })` with CORS headers | | `createSupabaseContext()` | Result tuple | Returns `{ data: null, error: AuthError }` | | `verifyAuth()` | Result tuple | Returns `{ data: null, error: AuthError }` | | `verifyCredentials()` | Result tuple | Returns `{ data: null, error: AuthError }` | | `resolveEnv()` | Result tuple | Returns `{ data: null, error: EnvError }` | | `createContextClient()` | **Throws** | Throws `EnvError` | | `createAdminClient()` | **Throws** | Throws `EnvError` | | Hono `withSupabase()` | HTTPException | Throws `HTTPException` with `cause: AuthError` | The two client factory functions (`createContextClient`, `createAdminClient`) are the only ones that throw. Everything else returns a result tuple `{ data, error }`. ## Handling errors in withSupabase `withSupabase` handles errors automatically. If auth fails, the caller receives a JSON response: ```json { "message": "Invalid credentials", "code": "INVALID_CREDENTIALS" } ``` with the appropriate HTTP status code and CORS headers. Your handler never runs. If you need custom error formatting, use `createSupabaseContext` instead: ```ts import { createSupabaseContext } from '@supabase/server' export default { fetch: async (req: Request) => { const { data: ctx, error } = await createSupabaseContext(req, { auth: 'user', }) if (error) { // Custom error format return Response.json( { success: false, error: { message: error.message, code: error.code }, }, { status: error.status }, ) } const { data } = await ctx!.supabase.from('todos').select() return Response.json({ success: true, data }) }, } ``` ## Handling errors in Hono The Hono adapter throws an `HTTPException` when auth fails. Access the original `AuthError` via `.cause`: ```ts import { Hono } from 'hono' import { HTTPException } from 'hono/http-exception' import { withSupabase } from '@supabase/server/adapters/hono' const app = new Hono() app.use('*', withSupabase({ auth: 'user' })) app.onError((err, c) => { if (err instanceof HTTPException && err.cause) { const authError = err.cause return c.json( { message: authError.message, code: authError.code }, err.status, ) } return c.json({ message: 'Internal error' }, 500) }) ``` ## Handling errors in core primitives Result-tuple functions: ```ts import { verifyAuth, resolveEnv } from '@supabase/server/core' // verifyAuth returns { data, error } const { data: auth, error } = await verifyAuth(request, { auth: 'user' }) if (error) { return Response.json({ message: error.message }, { status: error.status }) } // resolveEnv returns { data, error } const { data: env, error: envError } = resolveEnv() if (envError) { console.error(`Config issue [${envError.code}]: ${envError.message}`) } ``` Client factories throw — wrap them in try/catch: ```ts import { verifyAuth, createContextClient, createAdminClient, } from '@supabase/server/core' import { EnvError } from '@supabase/server' const { data: auth, error } = await verifyAuth(request, { auth: 'user' }) // ... handle error ... try { const supabase = createContextClient({ auth: { token: auth!.token } }) const supabaseAdmin = createAdminClient() } catch (e) { if (e instanceof EnvError) { console.error(`Config issue [${e.code}]: ${e.message}`) return Response.json({ message: e.message }, { status: 500 }) } throw e } ``` ## Using the Errors factory map The `Errors` object provides factory functions for creating error instances by code. Useful when building custom error handling or testing: ```ts import { Errors, MissingSupabaseURLError, InvalidCredentialsError, } from '@supabase/server' // Create specific errors const envError = Errors[MissingSupabaseURLError]() // → EnvError { message: "SUPABASE_URL is required but not set", code: "MISSING_SUPABASE_URL", status: 500 } const authError = Errors[InvalidCredentialsError]() // → AuthError { message: "Invalid credentials", code: "INVALID_CREDENTIALS", status: 401 } ``` ## Checking error types ```ts import { AuthError, EnvError } from '@supabase/server' try { // ... some operation } catch (e) { if (e instanceof AuthError) { // e.status is 401 or 500 // e.code is 'INVALID_CREDENTIALS', 'CREATE_SUPABASE_CLIENT_ERROR', or 'AUTH_ERROR' } if (e instanceof EnvError) { // e.status is always 500 // e.code is one of the MISSING_* constants or 'ENV_ERROR' } } ```