Files
supabase__server/docs/api-reference.md
2026-09-11 18:38:07 +03:00

859 lines
32 KiB
Markdown

# API Reference
Complete reference for every export, organized by entry point.
---
## @supabase/server
### withSupabase
```ts
function withSupabase<Database = UntypedDatabase>(
config: WithSupabaseConfig,
handler: (req: Request, ctx: SupabaseContext<Database>) => Promise<Response>,
): (req: Request) => Promise<Response>
```
Wraps a fetch handler with auth, CORS, and client creation. Returns a `(req: Request) => Promise<Response>` function suitable for `export default { fetch }`.
- Handles `OPTIONS` preflight when CORS is enabled
- Verifies credentials per `config.auth`
- Returns JSON error response on auth failure
- Adds CORS headers to all responses
- Buffers the request body at the entry point, so composed middleware and the handler can each read it
- Reading the raw `req.body` stream bypasses the buffer, so a handler that forwards the request with `fetch()` after another layer has read the body rebuilds it from `await req.arrayBuffer()`.
```ts
function withSupabase<Database = UntypedDatabase>(
config: WithSupabaseConfig,
): Entry<SupabaseContext<Database>>
```
Called with config only, `withSupabase` is an entry for `pipeline` from `@supabase/middleware`. Position decides what runs before and after the auth gate. A config carrying a `middleware` key is refused when the stack is built; entries compose through `pipeline` or nesting:
```ts
import { pipeline } from '@supabase/middleware'
import { withOAuthProtectedResource, withSupabase } from '@supabase/server'
import { withPostgresClient } from '@supabase/server/middleware/postgres'
pipeline(
[
withOAuthProtectedResource(),
withSupabase({ auth: 'user' }),
withPostgresClient(),
],
handler,
)
```
Entries before `withSupabase` see every request, including unauthenticated ones, and observe its `401` responses on the way out. Entries after it receive the full `SupabaseContext` and may declare prerequisites on its keys; an entry contributing one of those keys is a compile-time conflict. Nesting works the same way: `withOAuthProtectedResource(withSupabase(config, handler))` places the OAuth middleware ahead of the gate, `withSupabase(config, withPostgresClient(handler))` places Postgres behind it. Placing `withOAuthProtectedResource` directly after `withSupabase` with an auth mode that requires credentials is refused when the stack is built; a pre-auth middleware separated from `withSupabase` by another entry is not detected and must be ordered by hand.
> **Alpha.** The entry form and the `@supabase/server/middleware/*` subpaths
> track `@supabase/middleware` 0.x — entry shapes and context keys may change
> between 0.x releases. Everything else in `@supabase/server` is stable.
### createSupabaseContext
```ts
function createSupabaseContext<Database = UntypedDatabase>(
request: Request,
options?: WithSupabaseConfig,
): Promise<
| { data: SupabaseContext<Database>; error: null }
| { data: null; error: AuthError }
>
```
Creates a `SupabaseContext` from a request. Returns a result tuple. The `cors` option is ignored.
Defaults to `auth: 'user'` when `options` is omitted.
---
## @supabase/server/core
### verifyAuth
```ts
function verifyAuth(
request: Request,
options: {
auth?: AuthModeWithKey | AuthModeWithKey[]
env?: Partial<SupabaseEnv>
},
): Promise<{ data: AuthResult; error: null } | { data: null; error: AuthError }>
```
Extracts credentials from a request and verifies them. Convenience wrapper over `extractCredentials` + `verifyCredentials`.
### verifyCredentials
```ts
function verifyCredentials(
credentials: Credentials,
options: {
auth?: AuthModeWithKey | AuthModeWithKey[]
env?: Partial<SupabaseEnv>
},
): Promise<{ data: AuthResult; error: null } | { data: null; error: AuthError }>
```
Verifies pre-extracted credentials against allowed auth modes. Tries each mode in order — first match wins.
### extractCredentials
```ts
function extractCredentials(request: Request): Credentials
```
Reads `Authorization: Bearer <token>` and `apikey` headers from a request. Pure extraction, no validation. Synchronous.
### resolveEnv
```ts
function resolveEnv(
overrides?: Partial<SupabaseEnv>,
): { data: SupabaseEnv; error: null } | { data: null; error: EnvError }
```
Resolves Supabase environment configuration from runtime variables. `SUPABASE_URL` is the only hard requirement.
### createContextClient
```ts
function createContextClient<Database = UntypedDatabase>(
options?: CreateContextClientOptions,
): SupabaseClient<Database>
```
Creates a user-scoped Supabase client. RLS applies. **Throws `EnvError`** if URL or publishable key is missing.
Configured with:
- Publishable key (named or default) as `apikey` header
- User's JWT as `Authorization: Bearer` header (when `auth.token` is provided)
- `persistSession: false`, `autoRefreshToken: false`, `detectSessionInUrl: false`
### createAdminClient
```ts
function createAdminClient<Database = UntypedDatabase>(
options?: CreateAdminClientOptions,
): SupabaseClient<Database>
```
Creates an admin Supabase client that bypasses RLS. **Throws `EnvError`** if URL or secret key is missing.
---
## @supabase/server/adapters/hono
### withSupabase (Hono)
```ts
function withSupabase(
config?: Omit<WithSupabaseConfig, 'cors'>,
): MiddlewareHandler
```
Hono middleware. Sets `c.var.supabaseContext` on the Hono context. Throws `HTTPException` on auth failure with `cause: AuthError`.
Skips if `c.var.supabaseContext` is already set (enables route-level overrides).
Defaults to `auth: 'user'` when config is omitted.
---
## @supabase/server/adapters/h3
### withSupabase (H3)
```ts
function withSupabase(config?: Omit<WithSupabaseConfig, 'cors'>): Middleware
```
H3 middleware. Sets `event.context.supabaseContext` on the H3 event. Throws `HTTPError` on auth failure with `cause: AuthError`.
Skips if `event.context.supabaseContext` is already set (enables chained middleware).
Defaults to `auth: 'user'` when config is omitted.
---
## @supabase/server/adapters/elysia
### withSupabase (Elysia)
```ts
function withSupabase(config?: Omit<WithSupabaseConfig, 'cors'>): Elysia
```
Elysia plugin that resolves `supabaseContext` into the request context. Throws an error on auth failure with `cause: AuthError`.
Skips if `supabaseContext` is already resolved by a prior plugin.
Defaults to `auth: 'user'` when config is omitted.
---
## @supabase/server/middleware/claims
> **Alpha.** Composing `withSupabase` as a `pipeline` entry and the
> `@supabase/server/middleware/*` subpaths track `@supabase/middleware` 0.x —
> entry shapes and context keys may change between 0.x releases. The
> `withSupabase(config, handler)` form is stable.
### withClaims
```ts
const withClaims: Middleware<
'jwtClaims',
WithClaimsConfig | void,
Record<never, never>,
JWTClaims | null
>
```
Contributes `ctx.jwtClaims` by verifying the caller's Bearer token against the project JWKS. This is the same verification core `withSupabase` uses for its `user` auth mode.
Behavior:
- No `Authorization: Bearer` token, or an `sb_*` API key in that position: contributes `null` and the request proceeds as anonymous.
- Token present but invalid: short-circuits with a 401 and code `INVALID_JWT`, naming the specific reason (expired, bad signature, unknown `kid`, malformed, no `sub`).
- Token present but no JWKS configured: short-circuits with a 500 and code `JWKS_NOT_CONFIGURED` — the same code `withSupabase`'s `user` mode reports, with a `hint` naming this middleware's `jwks` option. Verification is required; the middleware has no decode-only mode.
- Remote JWKS unreachable: short-circuits with a 500 and code `JWKS_FETCH_FAILED`.
Responses use the standard [error payload](error-handling.md#what-a-failure-looks-like).
`withClaims` is not an auth gate. It never rejects a request that has no token, so `[withClaims(), withSupabaseClient()]` is not the composable form of `withSupabase({ auth: 'user' })` and accepts anonymous callers. To require an authenticated caller, compose `withRequiredClaims` (`@supabase/server/middleware/required-claims`) instead. The two entries share the `jwtClaims` key, so a pipeline picks "claims if present" or "claims required"; composing both is a compile-time conflict.
### WithClaimsConfig
```ts
interface WithClaimsConfig {
jwks?: JSONWebKeySet | URL
}
```
Defaults to `SUPABASE_JWKS` (inline JSON) or `SUPABASE_JWKS_URL` (https endpoint) from the environment.
---
## @supabase/server/middleware/required-claims
> **Alpha.** Composing `withSupabase` as a `pipeline` entry and the
> `@supabase/server/middleware/*` subpaths track `@supabase/middleware` 0.x —
> entry shapes and context keys may change between 0.x releases. The
> `withSupabase(config, handler)` form is stable.
### withRequiredClaims
```ts
const withRequiredClaims: Middleware<
'jwtClaims',
WithRequiredClaimsConfig | void,
Record<never, never>,
JWTClaims
>
```
The user-mode auth gate. Verifies the caller's Bearer token against the project JWKS and contributes **non-null** `ctx.jwtClaims`. This is the same verification core `withSupabase` uses for its `user` auth mode.
Behavior:
- No `Authorization` header: short-circuits with a 401 and code `MISSING_CREDENTIALS`. The handler never runs.
- An `sb_*` API key in the `Authorization` header: a 401 with code `UNUSABLE_CREDENTIAL` — a credential arrived, just not a user JWT.
- Token present but invalid: a 401 with code `INVALID_JWT`, naming the specific reason.
- Token present but no JWKS configured: short-circuits with a 500 and code `JWKS_NOT_CONFIGURED` — the same code `withSupabase`'s `user` mode reports, with a `hint` naming this middleware's `jwks` option. Verification is required; the middleware has no decode-only mode.
- Remote JWKS unreachable: short-circuits with a 500 and code `JWKS_FETCH_FAILED`.
Responses use the standard [error payload](error-handling.md#what-a-failure-looks-like).
`withRequiredClaims` is the required-caller counterpart to `withClaims`: "claims required" rather than "claims if present". The two share the `jwtClaims` key, so composing both in one pipeline is a compile-time conflict.
Because the contribution is non-null, gated handlers read `ctx.jwtClaims` directly, and entries declaring a `jwtClaims` prerequisite, such as `withPostgresClient`, compose with no further verification:
```ts
pipeline([withRequiredClaims(), withPostgresClient()], async (req, ctx) => {
const rows = await ctx.postgres.query`select id, title from posts`
return Response.json({ rows, caller: ctx.jwtClaims.sub })
})
```
The gate's 401 and 500 short-circuits carry no CORS headers, and a bare pipeline answers no `OPTIONS` preflight. For browser callers, compose `withCors` (`@supabase/middleware/cors`) ahead of the gate: it answers preflight before the gate runs and stamps `Access-Control-*` headers on the gate's short-circuit responses.
After `withSupabase` in a `pipeline` the context already carries verified `jwtClaims`, so placing the gate there is a compile-time conflict. Use `withSupabase({ auth: 'user' })` to gate that path.
The gate contributes `jwtClaims` and nothing else. A handler that needs the full `SupabaseContext` behind an auth gate (for example `ctx.userClaims` or `ctx.authMode`, which no composable entry contributes) uses `withSupabase({ auth: 'user' })` directly. A host that takes an entries array can wrap it as the sole entry. `cors: 'disabled'` leaves CORS handling to the host:
```ts
const entry = (h: (req: Request, ctx: object) => Promise<Response>) =>
withSupabase({ auth: 'user', cors: 'disabled' }, h)
```
### WithRequiredClaimsConfig
```ts
interface WithRequiredClaimsConfig {
jwks?: JSONWebKeySet | URL
}
```
Defaults to `SUPABASE_JWKS` (inline JSON) or `SUPABASE_JWKS_URL` (https endpoint) from the environment.
---
## @supabase/server/middleware/postgres
> **Alpha.** Composing `withSupabase` as a `pipeline` entry and the
> `@supabase/server/middleware/*` subpaths track `@supabase/middleware` 0.x —
> entry shapes and context keys may change between 0.x releases. The
> `withSupabase(config, handler)` form is stable.
### withPostgresClient
```ts
const withPostgresClient: Middleware<
'postgres',
WithPostgresClientConfig | void,
{ jwtClaims: RequestClaims | null },
PostgresApi
>
```
Contributes `ctx.postgres` — a `pg` client scoped to the caller by RLS. Each query runs in its own transaction that sets `request.jwt.claims` and drops to the caller's role before the statement, so `auth.uid()` resolves and policies enforce.
Only `authenticated` and `anon` are assumed. A verified token naming any other role — `service_role` or a custom role — short-circuits with a 500 and `{ message, code: 'UNSUPPORTED_ROLE' }` naming the role, rather than being downgraded to `anon`. A missing or absent `role` claim is `anon`.
Requires `ctx.jwtClaims` upstream — supplied by `withSupabase` or by `withClaims` in a standalone `pipeline`. Composing it without one is a compile-time error.
Short-circuits with a 500 and code `MISSING_CONNECTION_STRING` when no connection string is available.
Needs raw TCP: Node, Deno, Bun, and the Supabase Edge runtime, not Workers-style isolates. `pg` is an optional peer dependency.
See [`docs/postgres.md`](postgres.md).
### PostgresApi
```ts
interface PostgresApi {
query<T = Record<string, unknown>>(
strings: TemplateStringsArray,
...values: unknown[]
): Promise<T[]>
queryRaw<T = Record<string, unknown>>(
text: string,
params?: unknown[],
): Promise<T[]>
}
```
The value at `ctx.postgres`. Both methods return the result rows directly (not a `pg` `Result`).
`query` is a **tagged template**, so every interpolation becomes a bind parameter and can never alter the statement:
```ts
const rows = await ctx.postgres
.query`select id, body from notes where id = ${id}`
// -> select id, body from notes where id = $1 with values [id]
```
Tagged templates cannot carry type arguments, so annotate the binding instead of writing `query<NoteRow>`:
```ts
const rows: NoteRow[] = await ctx.postgres.query`select id, body from notes`
```
Passing a plain string to `query` throws — the two calls differ only in their brackets, so it refuses rather than silently reinterpreting.
`queryRaw` takes SQL text plus `params`, for text that cannot be a literal: a query builder emitting `{ sql, parameters }`, or SQL that must interpolate an identifier. Identifiers can never be bind parameters, so check them against a set you control and quote them with `ident`:
```ts
import { ident } from '@supabase/server/middleware/postgres'
const SORTABLE = new Set(['created_at', 'title'])
if (!SORTABLE.has(column)) throw new Error('unsupported sort column')
const rows = await ctx.postgres.queryRaw(
`select id, title from posts order by ${ident(column)} desc`,
)
```
`ident` quotes and escapes, but does not authorize — it stops injection, not a caller reading a column they should not see. The allowlist is what does that.
### WithPostgresClientConfig
```ts
interface WithPostgresClientConfig {
connectionString?: string
}
```
Defaults to the `SUPABASE_DB_URL` environment variable. Pools are created lazily, one per connection string per process.
### RequestClaims
```ts
interface RequestClaims {
role?: string
[key: string]: unknown
}
```
The minimal claims shape `withPostgresClient` requires upstream at `ctx.jwtClaims`. Satisfied by `withSupabase`'s JWKS-verified claims and by `withClaims`. Only `role` is read; the whole object is serialized into `request.jwt.claims`.
---
## @supabase/server/middleware/postgres-admin
> **Alpha.** Composing `withSupabase` as a `pipeline` entry and the
> `@supabase/server/middleware/*` subpaths track `@supabase/middleware` 0.x —
> entry shapes and context keys may change between 0.x releases. The
> `withSupabase(config, handler)` form is stable.
### withPostgresAdminClient
```ts
const withPostgresAdminClient: Middleware<
'postgresAdmin',
WithPostgresAdminClientConfig | void,
Record<never, never>,
PostgresApi
>
```
Contributes `ctx.postgresAdmin` — a `pg` client that **bypasses RLS**. Queries run as-is, as the role in the connection string: no claim injection, no role switching, no wrapping transaction.
Declares no upstream prerequisite, so it composes in any auth mode including `'secret'` and `'none'`. Shares the pool cache with `withPostgresClient` — same connection string, one pool.
Short-circuits with a 500 and code `MISSING_CONNECTION_STRING` when no connection string is available.
Authorization is the caller's responsibility: RLS is not consulted, so per-user scoping must be an explicit `where` clause.
### WithPostgresAdminClientConfig
```ts
interface WithPostgresAdminClientConfig {
connectionString?: string
}
```
Defaults to the `SUPABASE_DB_URL` environment variable.
---
## @supabase/server/oauth-protected-resource
> **Alpha.** The config shape, the contributed context key, and the metadata
> route may change in a minor release.
Also re-exported from `@supabase/server`. See [`docs/mcp.md`](mcp.md) for the MCP server walkthrough.
### withOAuthProtectedResource
```ts
function withOAuthProtectedResource(
config?: OAuthProtectedResourceConfig,
): Entry<{ oauthProtectedResource: OAuthProtectedResourceContribution }>
function withOAuthProtectedResource(handler: FetchHandler): FetchHandler
function withOAuthProtectedResource(
config: OAuthProtectedResourceConfig,
handler: FetchHandler,
): FetchHandler
```
OAuth 2.1 Protected Resource behavior (RFC 9728) for the wrapped handler. Answers `GET` and `OPTIONS` on any path ending in `/oauth-protected-resource` with the metadata document and a permissive CORS preflight; adds `WWW-Authenticate: Bearer resource_metadata="…"` to a `401` from below unless the handler already set that header; passes everything else through. Runs before the `withSupabase` gate; placing it directly after `withSupabase` with a credentialed auth mode is refused when the stack is built.
Contributes `ctx.oauthProtectedResource.resourceMetadataUrl`, the resolved absolute URL of the metadata document.
### OAuthProtectedResourceConfig
| Option | Type | Default on Supabase Edge Functions | Default elsewhere |
| --------------------- | ----------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resourceServer` | `UrlOption` | Public origin from `X-Forwarded-*` (or `SUPABASE_PUBLIC_URL`) + `/functions/v1/{SUPABASE_FUNCTION_SLUG}` | None. Throws `MissingResourceServerError` (`MISSING_RESOURCE_SERVER`). |
| `authorizationServer` | `UrlOption` | Public origin + `/auth/v1` | `SUPABASE_PUBLIC_URL`, then `SUPABASE_URL`, each + `/auth/v1`. Throws `MissingAuthorizationServerError` (`MISSING_AUTHORIZATION_SERVER`) if neither is set. |
`UrlOption` is `string | ((req: Request) => string)`. Without `SUPABASE_FUNCTION_SLUG` the resource path is reconstructed from the request path with `/functions/v1` restored; a request at the root path with no slug throws `MissingResourceServerError`.
### fromSupabaseUrl
```ts
function fromSupabaseUrl(supabaseUrl: string): string
```
Turns a project URL (`https://<ref>.supabase.co`) into its Auth issuer (`…/auth/v1`) for `authorizationServer`. Tolerates a value that already carries the `/auth/v1` path.
### resourceMetadataResponse / unauthorizedResponse
```ts
function resourceMetadataResponse(
req: Request,
options?: { resource?: string; authorizationServers?: string[] },
): Response
function unauthorizedResponse(
req: Request,
options?: { resourceMetadataUrl?: string },
): Response
```
The building blocks behind the middleware, for custom routing. Defaults derive from the request as above.
## Types
### AuthMode
```ts
type AuthMode = 'none' | 'publishable' | 'secret' | 'user'
```
### AuthModeWithKey
```ts
type AuthModeWithKey = AuthMode | `publishable:${string}` | `secret:${string}`
```
Extended auth mode with named key support. Examples: `'publishable:web'`, `'secret:*'`, `'secret:internal'`. The bare form (`'publishable'` / `'secret'`) matches only the `default` key; `:*` accepts any key in the set.
### CredentialedAuthMode
```ts
type CredentialedAuthMode = Exclude<AuthModeWithKey, 'none'>
```
Every `AuthModeWithKey` except `'none'`, keyed forms included.
### AuthConfig
```ts
type AuthConfig =
| 'none'
| CredentialedAuthMode
| [CredentialedAuthMode, ...CredentialedAuthMode[]]
| [CredentialedAuthMode, ...CredentialedAuthMode[], 'none']
```
The accepted shape of the `auth` option. `'none'` matches every request, so the type allows it on its own or as the last entry of a list, and nowhere else — `['none']` says nothing that a bare `'none'` doesn't, and a mode placed after `'none'` can never be reached. A single mode needs no wrapping array: `'user'` and `['user']` are the same configuration.
```ts
withSupabase({ auth: 'user' }, handler) // one mode
withSupabase({ auth: ['secret', 'user'] }, handler) // first match wins
withSupabase({ auth: ['user', 'none'] }, handler) // optional user
withSupabase({ auth: 'none' }, handler) // no credentials required
```
### Allow / AllowWithKey (deprecated aliases)
`Allow` and `AllowWithKey` are kept as deprecated aliases for `AuthMode` and `AuthModeWithKey`. Prefer the `Auth*` names — the legacy ones will be removed in a future major release.
### SupabaseContext\<Database\>
```ts
interface SupabaseContext<Database = UntypedDatabase> {
supabase: SupabaseClient<Database>
supabaseAdmin: SupabaseClient<Database>
userClaims: UserClaims | null
jwtClaims: JWTClaims | null
authMode: AuthMode
authKeyName?: string
}
```
### WithSupabaseConfig
```ts
interface WithSupabaseConfig {
auth?: AuthConfig // default: 'user'
/** @deprecated use `auth` instead — will be removed in a future major release */
allow?: AuthModeWithKey | AuthModeWithKey[]
env?: Partial<SupabaseEnv>
cors?: boolean | Record<string, string> // default: true
supabaseOptions?: SupabaseClientOptions<string>
errors?: ErrorResponseConfig
}
```
### ErrorResponseConfig
```ts
interface ErrorResponseConfig {
detailed?: boolean // default: true
}
```
`detailed: false` reduces the error response body to `code` and `message` alone, dropping `source`, `hint`, `docs`, and `details`. The status and `x-supabase-server-error` header are unaffected, and the error object itself keeps everything. See [`error-handling.md`](error-handling.md#trimming-the-response-body).
### SupabaseEnv
```ts
interface SupabaseEnv {
url: string
publishableKeys: Record<string, string>
secretKeys: Record<string, string>
jwks: JsonWebKeySet | null
}
```
### Credentials
```ts
interface Credentials {
token: string | null
apikey: string | null
}
```
### AuthResult
```ts
interface AuthResult {
authMode: AuthMode
token: string | null
userClaims: UserClaims | null
jwtClaims: JWTClaims | null
keyName?: string | null
}
```
### JWTClaims
```ts
interface JWTClaims {
sub: string
iss?: string
aud?: string | string[]
exp?: number
iat?: number
role?: string
email?: string
app_metadata?: Record<string, unknown>
user_metadata?: Record<string, unknown>
[key: string]: unknown
}
```
### UserClaims
```ts
interface UserClaims {
id: string
role?: string
email?: string
appMetadata?: Record<string, unknown>
userMetadata?: Record<string, unknown>
}
```
### ClientAuth
```ts
interface ClientAuth {
token?: string | null
keyName?: string | null
}
```
### CreateContextClientOptions
```ts
interface CreateContextClientOptions {
auth?: ClientAuth
env?: Partial<SupabaseEnv>
supabaseOptions?: SupabaseClientOptions<string>
}
```
### CreateAdminClientOptions
```ts
interface CreateAdminClientOptions {
auth?: Pick<ClientAuth, 'keyName'>
env?: Partial<SupabaseEnv>
supabaseOptions?: SupabaseClientOptions<string>
}
```
### JsonWebKeySet
```ts
interface JsonWebKeySet {
keys: JsonWebKey[]
}
```
### Peer Dependencies
Some peer dependencies types are available from `@supabase/server/peer/*` export
#### supabase-js
Only a curated set of types are available to import — It means that may be missing types from the original lib.
```ts
import type {
SupabaseClient,
PostgrestError,
AuthError as SupabaseAuthError, // Avoid clashing with this SDK's own `AuthError` class.
// ...
} from '@supabase/server/peer/supabase-js'
```
---
## Error Classes
### SupabaseServerError
Base class for every error the library produces — catch this to handle anything from `@supabase/server`.
```ts
abstract class SupabaseServerError extends Error {
readonly source: '@supabase/server'
abstract readonly status: number
readonly code: string
readonly hint?: string // actionable next step
readonly docs: string // link to docs/error-handling.md#<code>
readonly details?: Record<string, unknown> // non-sensitive diagnostics
toJSON(): ErrorPayload
}
```
`message` is always prefixed `[@supabase/server]`. `details` never contains key values or token payloads. `toJSON()` is picked up by `JSON.stringify`, so logging the error yields the full diagnostics.
### EnvError
```ts
class EnvError extends SupabaseServerError {
readonly status: 500
constructor(
message: string,
code?: string,
options?: SupabaseServerErrorOptions,
)
}
```
### AuthError
```ts
class AuthError extends SupabaseServerError {
readonly status: number // 401 = bad credentials, 500 = server misconfigured
constructor(
message: string,
code?: string,
status?: number,
options?: SupabaseServerErrorOptions,
)
}
```
### ErrorPayload
The JSON body every auto-responding layer returns, and the return type of `toJSON()`.
```ts
interface ErrorPayload {
source: '@supabase/server'
code: string
message: string
hint?: string
docs: string
details?: Record<string, unknown>
}
```
### SupabaseServerErrorOptions
```ts
interface SupabaseServerErrorOptions {
hint?: string
details?: Record<string, unknown>
docs?: string // overrides the generated URL
cause?: unknown
}
```
---
## Error Code Constants
| Constant | Value | Class | Meaning |
| ----------------------------------- | ----------------------------------- | ----------- | -------------------------------------------------------------------- |
| `EnvGenericError` | `'ENV_ERROR'` | `EnvError` | Generic environment error |
| `MissingSupabaseURLError` | `'MISSING_SUPABASE_URL'` | `EnvError` | `SUPABASE_URL` not set |
| `MissingPublishableKeyError` | `'MISSING_PUBLISHABLE_KEY'` | `EnvError` | Named publishable key not found |
| `MissingDefaultPublishableKeyError` | `'MISSING_DEFAULT_PUBLISHABLE_KEY'` | `EnvError` | No default publishable key |
| `MissingSecretKeyError` | `'MISSING_SECRET_KEY'` | `EnvError` | Named secret key not found |
| `MissingDefaultSecretKeyError` | `'MISSING_DEFAULT_SECRET_KEY'` | `EnvError` | No default secret key |
| `MissingResourceServerError` | `'MISSING_RESOURCE_SERVER'` | `EnvError` | `withOAuthProtectedResource` cannot derive a `resourceServer` |
| `MissingAuthorizationServerError` | `'MISSING_AUTHORIZATION_SERVER'` | `EnvError` | `withOAuthProtectedResource` cannot derive an authorization server |
| `MissingConnectionStringError` | `'MISSING_CONNECTION_STRING'` | `EnvError` | No Postgres connection string configured |
| `AuthGenericError` | `'AUTH_ERROR'` | `AuthError` | Generic auth error (401) |
| `MissingCredentialsError` | `'MISSING_CREDENTIALS'` | `AuthError` | Request carried no credentials at all (401) |
| `UnusableCredentialError` | `'UNUSABLE_CREDENTIAL'` | `AuthError` | A credential arrived but cannot be used (401) |
| `InvalidApiKeyError` | `'INVALID_API_KEY'` | `AuthError` | `apikey` matched no configured key (401) |
| `InvalidJwtError` | `'INVALID_JWT'` | `AuthError` | JWT failed verification (401) |
| `InvalidCredentialsError` | `'INVALID_CREDENTIALS'` | `AuthError` | Fallback credential failure (401) |
| `JwksNotConfiguredError` | `'JWKS_NOT_CONFIGURED'` | `AuthError` | JWT sent but no JWKS configured (500) |
| `JwksFetchFailedError` | `'JWKS_FETCH_FAILED'` | `AuthError` | Remote JWKS unreachable or unusable (500) |
| `NoKeysConfiguredError` | `'NO_KEYS_CONFIGURED'` | `AuthError` | Auth mode no configured key can match (500) |
| `UnsupportedRoleError` | `'UNSUPPORTED_ROLE'` | `AuthError` | `withPostgresClient` will not assume the caller's `role` claim (500) |
| `CreateSupabaseClientError` | `'CREATE_SUPABASE_CLIENT_ERROR'` | `AuthError` | Client creation failed after auth (500) |
Also exported: `ErrorSource` (`'@supabase/server'`) and `ErrorCodeHeader` (`'x-supabase-server-error'`).
See [`error-handling.md`](error-handling.md) for the meaning, `hint`, and `details` of each code.
---
## Errors Factory Map
```ts
const Errors: {
[MissingSupabaseURLError]: () => EnvError
[MissingPublishableKeyError]: (name, configuredKeyNames?) => EnvError
[MissingDefaultPublishableKeyError]: (configuredKeyNames?) => EnvError
[MissingSecretKeyError]: (name, configuredKeyNames?) => EnvError
[MissingDefaultSecretKeyError]: (configuredKeyNames?) => EnvError
[MissingResourceServerError]: () => EnvError
[MissingAuthorizationServerError]: () => EnvError
[MissingConnectionStringError]: (middleware: string) => EnvError
[MissingCredentialsError]: (context: AuthFailureContext) => AuthError
[UnusableCredentialError]: (
context: PartialContext & { reason; hint },
) => AuthError
[InvalidApiKeyError]: (context: AuthFailureContext) => AuthError
[InvalidJwtError]: (context: PartialContext & JwtFailure) => AuthError
[InvalidCredentialsError]: (context?: AuthFailureContext) => AuthError
[JwksNotConfiguredError]: (
context?: PartialContext & { middleware? },
) => AuthError
[JwksFetchFailedError]: (context: PartialContext & { reason }) => AuthError
[NoKeysConfiguredError]: (
context: AuthFailureContext & { mode; keyKind },
) => AuthError
[UnsupportedRoleError]: (context: {
requestedRole
supportedRoles
}) => AuthError
[CreateSupabaseClientError]: (options?: { cause?: unknown }) => AuthError
}
```
Keyed by error code constant. Each entry returns an error pre-configured with `hint`, `docs`, and non-sensitive `details`. The named-key factories accept the configured key names so they can be reported in the message without exposing key values.
### AuthFailureContext
Non-sensitive diagnostics the auth pipeline passes to the factories.
```ts
interface AuthFailureContext {
authModes: readonly string[]
received: {
authorization: 'bearer' | 'api-key' | 'non-bearer-scheme' | 'absent'
apikey: 'absent' | 'publishable' | 'secret' | 'legacy-jwt' | 'unrecognized'
}
configuredKeyNames?: Record<string, readonly string[]>
}
```