Files

860 lines
32 KiB
Markdown
Raw Permalink Normal View History

docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
# API Reference
Complete reference for every export, organized by entry point.
---
## @supabase/server
### withSupabase
```ts
function withSupabase<Database = unknown>(
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
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
- Verifies credentials per `config.auth`
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
- 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()`.
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
```ts
function withSupabase<Database = unknown>(
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.
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
### createSupabaseContext
```ts
function createSupabaseContext<Database = unknown>(
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.
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
Defaults to `auth: 'user'` when `options` is omitted.
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
---
## @supabase/server/core
### verifyAuth
```ts
function verifyAuth(
request: Request,
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
options: {
auth?: AuthModeWithKey | AuthModeWithKey[]
env?: Partial<SupabaseEnv>
},
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
): 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,
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
options: {
auth?: AuthModeWithKey | AuthModeWithKey[]
env?: Partial<SupabaseEnv>
},
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
): 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 = unknown>(
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 = unknown>(
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.
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
Defaults to `auth: 'user'` when config is omitted.
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
---
## @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.
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
- 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:
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
- 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.
---
feat(middleware): ship withPostgresClient and withPostgresAdminClient (#115) * refactor(middleware): rename withPostgres to withPostgresClient and harden it Renames the export to sit alongside withSupabaseClient / withSupabaseAdminClient, and extracts the pool into a shared core module so the service-role companion can reuse it. Safe to rename now: the old name exists only on 1.5.0-rc.* / beta, never on a stable release. Three correctness fixes alongside it: - The pool cache was keyed on nothing, so a second connectionString in the same process silently queried the first database. Now keyed per string. - The missing-connection-string 500 returned { error }, not the package's standard { message, code }. - An unguarded rollback in the catch could replace the caller's real error with a connection error. Adds unit coverage for each, plus a type-level check that composing without an upstream jwtClaims stays a compile-time error. * feat(middleware): add withPostgresAdminClient Contributes ctx.postgresAdmin — a pg client that bypasses RLS, exported from ./middleware/postgres-admin. Queries run as-is under the connection-string role: no claim injection, no role switch, no wrapping transaction. Declares no upstream prerequisite, so unlike withPostgresClient it composes under auth: 'secret' and auth: 'none'. Shares the pool cache with the scoped half — same connection string, one pool. That is safe because everything the scoped half sets is transaction-local, so a connection always returns clean. Kept as a second middleware rather than a property on ctx.postgres: defineMiddleware contributes exactly one ctx key, and the split keeps the RLS bypass visible at the composition site. * test(e2e): cover both postgres middleware against a real database Adds /my-notes-pg and /all-notes-pg to the core Node app and the Deno edge function, both running the identical unfiltered SELECT — one through ctx.postgres, one through ctx.postgresAdmin. user2 sees none of user1's rows through the scoped client and sees them through the admin one, which proves claim injection, the role drop, and the bypass in a single contrast. The edge function passes connectionString explicitly from E2E_DB_URL: the CLI injects a SUPABASE_DB_URL addressing the database by container name, and Deno's DNS resolver rejects the underscores in it. The Node app still covers the SUPABASE_DB_URL default path. * docs: document the postgres middleware pair Adds docs/postgres.md covering both halves, the SQL each query runs, the two composition paths, table grants, the RLS bypass and why it is a separate middleware, and guidance to write policies with the auth.* helpers rather than reading request.jwt.claim.* directly. Wires both subpaths into typedoc entryPoints — without which neither export reached api-docs/ — and adds README sections, Exports and env-var rows, and api-reference entries. * fix(middleware): discard the connection when a rollback fails pg-pool only removes a client when release() is given a truthy argument, so the previous release() returned a connection whose transaction could not be unwound straight back to the pool — potentially still inside the caller's transaction with their role set. That was survivable while the pool served one middleware. It is not now that withPostgresAdminClient shares it: that middleware begins no transaction and sets up no session state, so it would silently inherit the leftover role on the next checkout. * fix(middleware): refuse unsupported roles instead of downgrading to anon withPostgresClient silently mapped every role that was not 'authenticated' to 'anon'. For a forged service_role that was the intent, but Supabase also supports custom roles via the role claim, and RLS applies to those normally — so a legitimate `role: manager` token was being answered with zero rows and no indication that the role was the reason. Now only 'authenticated' and 'anon' are assumed, and anything else short-circuits with a 500 and code UNSUPPORTED_ROLE before the handler runs or a connection is checked out. service_role gets a message pointing at withPostgresAdminClient; other roles are named in the error. Custom roles remain unsupported — the reason is that PostgREST connects as the unprivileged authenticator, where `grant <role> to authenticator` is itself the authorization, while we connect as postgres and have no such boundary to lean on. Documented, and tracked separately. Also hoists the per-request claims serialization out of the per-query path. * docs: list every subpath in the README exports table The table covered 8 of 13 entry points. Adding the postgres pair made the omission look deliberate rather than incidental — a reader could reasonably conclude withClaims has no subpath, which matters because it is the documented prerequisite for composing withPostgresClient standalone. * feat(middleware): make query a tagged template, add queryRaw and ident `query` now takes a tagged template only, so every interpolation becomes a bind parameter and can never alter the shape of the statement. `queryRaw(text, params)` keeps the string form — it is fully safe with params, and it is the only path that works for query builders and codegen emitting `{ sql, parameters }`, or for SQL that has to interpolate an identifier. Passing a plain string to `query` throws, naming `queryRaw`. The two calls differ only in their brackets, so refusing beats reinterpreting: the string's first character would otherwise be read as the whole template and a one-character query would be sent. `ident()` quotes identifiers, which can never be bind parameters — `select $1 from notes` selects a literal, not a column. It is implemented directly rather than wrapping `pg.escapeIdentifier`: that top-level export only exists from pg 8.11, while the peer range is `^8.0.0`, so a wrapper would be a runtime TypeError on 8.0-8.5. It also rejects empty names and NUL bytes, which pg passes straight through to a confusing server-side error. `set local role` now quotes the role via `ident()`. The role is already constrained to the SUPPORTED_ROLES allowlist, so this changes nothing today — it keeps the interpolation safe if that list widens to the custom roles the docstring promises. Follows the prior art: Prisma shipped the dual overload and reversed it, Slonik refuses plain strings outright, and postgres.js requires the tag with `sql.unsafe` as the named escape hatch. The e2e edge function built its query by interpolating a column list. As a `query` tag that would have compiled to `select $1 from notes` and returned the literal string for every row — valid SQL, wrong rows, no error. It now uses `queryRaw`, with a comment explaining why. * fix: refuse non-string role claims instead of downgrading to anon * chore: keep prettier off the release-please changelog --------- Co-authored-by: Katerina Skroumpelou <sk.katherine@gmail.com>
2026-08-24 09:52:01 -05:00
## @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.
feat(middleware): ship withPostgresClient and withPostgresAdminClient (#115) * refactor(middleware): rename withPostgres to withPostgresClient and harden it Renames the export to sit alongside withSupabaseClient / withSupabaseAdminClient, and extracts the pool into a shared core module so the service-role companion can reuse it. Safe to rename now: the old name exists only on 1.5.0-rc.* / beta, never on a stable release. Three correctness fixes alongside it: - The pool cache was keyed on nothing, so a second connectionString in the same process silently queried the first database. Now keyed per string. - The missing-connection-string 500 returned { error }, not the package's standard { message, code }. - An unguarded rollback in the catch could replace the caller's real error with a connection error. Adds unit coverage for each, plus a type-level check that composing without an upstream jwtClaims stays a compile-time error. * feat(middleware): add withPostgresAdminClient Contributes ctx.postgresAdmin — a pg client that bypasses RLS, exported from ./middleware/postgres-admin. Queries run as-is under the connection-string role: no claim injection, no role switch, no wrapping transaction. Declares no upstream prerequisite, so unlike withPostgresClient it composes under auth: 'secret' and auth: 'none'. Shares the pool cache with the scoped half — same connection string, one pool. That is safe because everything the scoped half sets is transaction-local, so a connection always returns clean. Kept as a second middleware rather than a property on ctx.postgres: defineMiddleware contributes exactly one ctx key, and the split keeps the RLS bypass visible at the composition site. * test(e2e): cover both postgres middleware against a real database Adds /my-notes-pg and /all-notes-pg to the core Node app and the Deno edge function, both running the identical unfiltered SELECT — one through ctx.postgres, one through ctx.postgresAdmin. user2 sees none of user1's rows through the scoped client and sees them through the admin one, which proves claim injection, the role drop, and the bypass in a single contrast. The edge function passes connectionString explicitly from E2E_DB_URL: the CLI injects a SUPABASE_DB_URL addressing the database by container name, and Deno's DNS resolver rejects the underscores in it. The Node app still covers the SUPABASE_DB_URL default path. * docs: document the postgres middleware pair Adds docs/postgres.md covering both halves, the SQL each query runs, the two composition paths, table grants, the RLS bypass and why it is a separate middleware, and guidance to write policies with the auth.* helpers rather than reading request.jwt.claim.* directly. Wires both subpaths into typedoc entryPoints — without which neither export reached api-docs/ — and adds README sections, Exports and env-var rows, and api-reference entries. * fix(middleware): discard the connection when a rollback fails pg-pool only removes a client when release() is given a truthy argument, so the previous release() returned a connection whose transaction could not be unwound straight back to the pool — potentially still inside the caller's transaction with their role set. That was survivable while the pool served one middleware. It is not now that withPostgresAdminClient shares it: that middleware begins no transaction and sets up no session state, so it would silently inherit the leftover role on the next checkout. * fix(middleware): refuse unsupported roles instead of downgrading to anon withPostgresClient silently mapped every role that was not 'authenticated' to 'anon'. For a forged service_role that was the intent, but Supabase also supports custom roles via the role claim, and RLS applies to those normally — so a legitimate `role: manager` token was being answered with zero rows and no indication that the role was the reason. Now only 'authenticated' and 'anon' are assumed, and anything else short-circuits with a 500 and code UNSUPPORTED_ROLE before the handler runs or a connection is checked out. service_role gets a message pointing at withPostgresAdminClient; other roles are named in the error. Custom roles remain unsupported — the reason is that PostgREST connects as the unprivileged authenticator, where `grant <role> to authenticator` is itself the authorization, while we connect as postgres and have no such boundary to lean on. Documented, and tracked separately. Also hoists the per-request claims serialization out of the per-query path. * docs: list every subpath in the README exports table The table covered 8 of 13 entry points. Adding the postgres pair made the omission look deliberate rather than incidental — a reader could reasonably conclude withClaims has no subpath, which matters because it is the documented prerequisite for composing withPostgresClient standalone. * feat(middleware): make query a tagged template, add queryRaw and ident `query` now takes a tagged template only, so every interpolation becomes a bind parameter and can never alter the shape of the statement. `queryRaw(text, params)` keeps the string form — it is fully safe with params, and it is the only path that works for query builders and codegen emitting `{ sql, parameters }`, or for SQL that has to interpolate an identifier. Passing a plain string to `query` throws, naming `queryRaw`. The two calls differ only in their brackets, so refusing beats reinterpreting: the string's first character would otherwise be read as the whole template and a one-character query would be sent. `ident()` quotes identifiers, which can never be bind parameters — `select $1 from notes` selects a literal, not a column. It is implemented directly rather than wrapping `pg.escapeIdentifier`: that top-level export only exists from pg 8.11, while the peer range is `^8.0.0`, so a wrapper would be a runtime TypeError on 8.0-8.5. It also rejects empty names and NUL bytes, which pg passes straight through to a confusing server-side error. `set local role` now quotes the role via `ident()`. The role is already constrained to the SUPPORTED_ROLES allowlist, so this changes nothing today — it keeps the interpolation safe if that list widens to the custom roles the docstring promises. Follows the prior art: Prisma shipped the dual overload and reversed it, Slonik refuses plain strings outright, and postgres.js requires the tag with `sql.unsafe` as the named escape hatch. The e2e edge function built its query by interpolating a column list. As a `query` tag that would have compiled to `select $1 from notes` and returned the literal string for every row — valid SQL, wrong rows, no error. It now uses `queryRaw`, with a comment explaining why. * fix: refuse non-string role claims instead of downgrading to anon * chore: keep prettier off the release-please changelog --------- Co-authored-by: Katerina Skroumpelou <sk.katherine@gmail.com>
2026-08-24 09:52:01 -05:00
### 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.
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
Short-circuits with a 500 and code `MISSING_CONNECTION_STRING` when no connection string is available.
feat(middleware): ship withPostgresClient and withPostgresAdminClient (#115) * refactor(middleware): rename withPostgres to withPostgresClient and harden it Renames the export to sit alongside withSupabaseClient / withSupabaseAdminClient, and extracts the pool into a shared core module so the service-role companion can reuse it. Safe to rename now: the old name exists only on 1.5.0-rc.* / beta, never on a stable release. Three correctness fixes alongside it: - The pool cache was keyed on nothing, so a second connectionString in the same process silently queried the first database. Now keyed per string. - The missing-connection-string 500 returned { error }, not the package's standard { message, code }. - An unguarded rollback in the catch could replace the caller's real error with a connection error. Adds unit coverage for each, plus a type-level check that composing without an upstream jwtClaims stays a compile-time error. * feat(middleware): add withPostgresAdminClient Contributes ctx.postgresAdmin — a pg client that bypasses RLS, exported from ./middleware/postgres-admin. Queries run as-is under the connection-string role: no claim injection, no role switch, no wrapping transaction. Declares no upstream prerequisite, so unlike withPostgresClient it composes under auth: 'secret' and auth: 'none'. Shares the pool cache with the scoped half — same connection string, one pool. That is safe because everything the scoped half sets is transaction-local, so a connection always returns clean. Kept as a second middleware rather than a property on ctx.postgres: defineMiddleware contributes exactly one ctx key, and the split keeps the RLS bypass visible at the composition site. * test(e2e): cover both postgres middleware against a real database Adds /my-notes-pg and /all-notes-pg to the core Node app and the Deno edge function, both running the identical unfiltered SELECT — one through ctx.postgres, one through ctx.postgresAdmin. user2 sees none of user1's rows through the scoped client and sees them through the admin one, which proves claim injection, the role drop, and the bypass in a single contrast. The edge function passes connectionString explicitly from E2E_DB_URL: the CLI injects a SUPABASE_DB_URL addressing the database by container name, and Deno's DNS resolver rejects the underscores in it. The Node app still covers the SUPABASE_DB_URL default path. * docs: document the postgres middleware pair Adds docs/postgres.md covering both halves, the SQL each query runs, the two composition paths, table grants, the RLS bypass and why it is a separate middleware, and guidance to write policies with the auth.* helpers rather than reading request.jwt.claim.* directly. Wires both subpaths into typedoc entryPoints — without which neither export reached api-docs/ — and adds README sections, Exports and env-var rows, and api-reference entries. * fix(middleware): discard the connection when a rollback fails pg-pool only removes a client when release() is given a truthy argument, so the previous release() returned a connection whose transaction could not be unwound straight back to the pool — potentially still inside the caller's transaction with their role set. That was survivable while the pool served one middleware. It is not now that withPostgresAdminClient shares it: that middleware begins no transaction and sets up no session state, so it would silently inherit the leftover role on the next checkout. * fix(middleware): refuse unsupported roles instead of downgrading to anon withPostgresClient silently mapped every role that was not 'authenticated' to 'anon'. For a forged service_role that was the intent, but Supabase also supports custom roles via the role claim, and RLS applies to those normally — so a legitimate `role: manager` token was being answered with zero rows and no indication that the role was the reason. Now only 'authenticated' and 'anon' are assumed, and anything else short-circuits with a 500 and code UNSUPPORTED_ROLE before the handler runs or a connection is checked out. service_role gets a message pointing at withPostgresAdminClient; other roles are named in the error. Custom roles remain unsupported — the reason is that PostgREST connects as the unprivileged authenticator, where `grant <role> to authenticator` is itself the authorization, while we connect as postgres and have no such boundary to lean on. Documented, and tracked separately. Also hoists the per-request claims serialization out of the per-query path. * docs: list every subpath in the README exports table The table covered 8 of 13 entry points. Adding the postgres pair made the omission look deliberate rather than incidental — a reader could reasonably conclude withClaims has no subpath, which matters because it is the documented prerequisite for composing withPostgresClient standalone. * feat(middleware): make query a tagged template, add queryRaw and ident `query` now takes a tagged template only, so every interpolation becomes a bind parameter and can never alter the shape of the statement. `queryRaw(text, params)` keeps the string form — it is fully safe with params, and it is the only path that works for query builders and codegen emitting `{ sql, parameters }`, or for SQL that has to interpolate an identifier. Passing a plain string to `query` throws, naming `queryRaw`. The two calls differ only in their brackets, so refusing beats reinterpreting: the string's first character would otherwise be read as the whole template and a one-character query would be sent. `ident()` quotes identifiers, which can never be bind parameters — `select $1 from notes` selects a literal, not a column. It is implemented directly rather than wrapping `pg.escapeIdentifier`: that top-level export only exists from pg 8.11, while the peer range is `^8.0.0`, so a wrapper would be a runtime TypeError on 8.0-8.5. It also rejects empty names and NUL bytes, which pg passes straight through to a confusing server-side error. `set local role` now quotes the role via `ident()`. The role is already constrained to the SUPPORTED_ROLES allowlist, so this changes nothing today — it keeps the interpolation safe if that list widens to the custom roles the docstring promises. Follows the prior art: Prisma shipped the dual overload and reversed it, Slonik refuses plain strings outright, and postgres.js requires the tag with `sql.unsafe` as the named escape hatch. The e2e edge function built its query by interpolating a column list. As a `query` tag that would have compiled to `select $1 from notes` and returned the literal string for every row — valid SQL, wrong rows, no error. It now uses `queryRaw`, with a comment explaining why. * fix: refuse non-string role claims instead of downgrading to anon * chore: keep prettier off the release-please changelog --------- Co-authored-by: Katerina Skroumpelou <sk.katherine@gmail.com>
2026-08-24 09:52:01 -05:00
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.
feat(middleware): ship withPostgresClient and withPostgresAdminClient (#115) * refactor(middleware): rename withPostgres to withPostgresClient and harden it Renames the export to sit alongside withSupabaseClient / withSupabaseAdminClient, and extracts the pool into a shared core module so the service-role companion can reuse it. Safe to rename now: the old name exists only on 1.5.0-rc.* / beta, never on a stable release. Three correctness fixes alongside it: - The pool cache was keyed on nothing, so a second connectionString in the same process silently queried the first database. Now keyed per string. - The missing-connection-string 500 returned { error }, not the package's standard { message, code }. - An unguarded rollback in the catch could replace the caller's real error with a connection error. Adds unit coverage for each, plus a type-level check that composing without an upstream jwtClaims stays a compile-time error. * feat(middleware): add withPostgresAdminClient Contributes ctx.postgresAdmin — a pg client that bypasses RLS, exported from ./middleware/postgres-admin. Queries run as-is under the connection-string role: no claim injection, no role switch, no wrapping transaction. Declares no upstream prerequisite, so unlike withPostgresClient it composes under auth: 'secret' and auth: 'none'. Shares the pool cache with the scoped half — same connection string, one pool. That is safe because everything the scoped half sets is transaction-local, so a connection always returns clean. Kept as a second middleware rather than a property on ctx.postgres: defineMiddleware contributes exactly one ctx key, and the split keeps the RLS bypass visible at the composition site. * test(e2e): cover both postgres middleware against a real database Adds /my-notes-pg and /all-notes-pg to the core Node app and the Deno edge function, both running the identical unfiltered SELECT — one through ctx.postgres, one through ctx.postgresAdmin. user2 sees none of user1's rows through the scoped client and sees them through the admin one, which proves claim injection, the role drop, and the bypass in a single contrast. The edge function passes connectionString explicitly from E2E_DB_URL: the CLI injects a SUPABASE_DB_URL addressing the database by container name, and Deno's DNS resolver rejects the underscores in it. The Node app still covers the SUPABASE_DB_URL default path. * docs: document the postgres middleware pair Adds docs/postgres.md covering both halves, the SQL each query runs, the two composition paths, table grants, the RLS bypass and why it is a separate middleware, and guidance to write policies with the auth.* helpers rather than reading request.jwt.claim.* directly. Wires both subpaths into typedoc entryPoints — without which neither export reached api-docs/ — and adds README sections, Exports and env-var rows, and api-reference entries. * fix(middleware): discard the connection when a rollback fails pg-pool only removes a client when release() is given a truthy argument, so the previous release() returned a connection whose transaction could not be unwound straight back to the pool — potentially still inside the caller's transaction with their role set. That was survivable while the pool served one middleware. It is not now that withPostgresAdminClient shares it: that middleware begins no transaction and sets up no session state, so it would silently inherit the leftover role on the next checkout. * fix(middleware): refuse unsupported roles instead of downgrading to anon withPostgresClient silently mapped every role that was not 'authenticated' to 'anon'. For a forged service_role that was the intent, but Supabase also supports custom roles via the role claim, and RLS applies to those normally — so a legitimate `role: manager` token was being answered with zero rows and no indication that the role was the reason. Now only 'authenticated' and 'anon' are assumed, and anything else short-circuits with a 500 and code UNSUPPORTED_ROLE before the handler runs or a connection is checked out. service_role gets a message pointing at withPostgresAdminClient; other roles are named in the error. Custom roles remain unsupported — the reason is that PostgREST connects as the unprivileged authenticator, where `grant <role> to authenticator` is itself the authorization, while we connect as postgres and have no such boundary to lean on. Documented, and tracked separately. Also hoists the per-request claims serialization out of the per-query path. * docs: list every subpath in the README exports table The table covered 8 of 13 entry points. Adding the postgres pair made the omission look deliberate rather than incidental — a reader could reasonably conclude withClaims has no subpath, which matters because it is the documented prerequisite for composing withPostgresClient standalone. * feat(middleware): make query a tagged template, add queryRaw and ident `query` now takes a tagged template only, so every interpolation becomes a bind parameter and can never alter the shape of the statement. `queryRaw(text, params)` keeps the string form — it is fully safe with params, and it is the only path that works for query builders and codegen emitting `{ sql, parameters }`, or for SQL that has to interpolate an identifier. Passing a plain string to `query` throws, naming `queryRaw`. The two calls differ only in their brackets, so refusing beats reinterpreting: the string's first character would otherwise be read as the whole template and a one-character query would be sent. `ident()` quotes identifiers, which can never be bind parameters — `select $1 from notes` selects a literal, not a column. It is implemented directly rather than wrapping `pg.escapeIdentifier`: that top-level export only exists from pg 8.11, while the peer range is `^8.0.0`, so a wrapper would be a runtime TypeError on 8.0-8.5. It also rejects empty names and NUL bytes, which pg passes straight through to a confusing server-side error. `set local role` now quotes the role via `ident()`. The role is already constrained to the SUPPORTED_ROLES allowlist, so this changes nothing today — it keeps the interpolation safe if that list widens to the custom roles the docstring promises. Follows the prior art: Prisma shipped the dual overload and reversed it, Slonik refuses plain strings outright, and postgres.js requires the tag with `sql.unsafe` as the named escape hatch. The e2e edge function built its query by interpolating a column list. As a `query` tag that would have compiled to `select $1 from notes` and returned the literal string for every row — valid SQL, wrong rows, no error. It now uses `queryRaw`, with a comment explaining why. * fix: refuse non-string role claims instead of downgrading to anon * chore: keep prettier off the release-please changelog --------- Co-authored-by: Katerina Skroumpelou <sk.katherine@gmail.com>
2026-08-24 09:52:01 -05:00
### 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.
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
Short-circuits with a 500 and code `MISSING_CONNECTION_STRING` when no connection string is available.
feat(middleware): ship withPostgresClient and withPostgresAdminClient (#115) * refactor(middleware): rename withPostgres to withPostgresClient and harden it Renames the export to sit alongside withSupabaseClient / withSupabaseAdminClient, and extracts the pool into a shared core module so the service-role companion can reuse it. Safe to rename now: the old name exists only on 1.5.0-rc.* / beta, never on a stable release. Three correctness fixes alongside it: - The pool cache was keyed on nothing, so a second connectionString in the same process silently queried the first database. Now keyed per string. - The missing-connection-string 500 returned { error }, not the package's standard { message, code }. - An unguarded rollback in the catch could replace the caller's real error with a connection error. Adds unit coverage for each, plus a type-level check that composing without an upstream jwtClaims stays a compile-time error. * feat(middleware): add withPostgresAdminClient Contributes ctx.postgresAdmin — a pg client that bypasses RLS, exported from ./middleware/postgres-admin. Queries run as-is under the connection-string role: no claim injection, no role switch, no wrapping transaction. Declares no upstream prerequisite, so unlike withPostgresClient it composes under auth: 'secret' and auth: 'none'. Shares the pool cache with the scoped half — same connection string, one pool. That is safe because everything the scoped half sets is transaction-local, so a connection always returns clean. Kept as a second middleware rather than a property on ctx.postgres: defineMiddleware contributes exactly one ctx key, and the split keeps the RLS bypass visible at the composition site. * test(e2e): cover both postgres middleware against a real database Adds /my-notes-pg and /all-notes-pg to the core Node app and the Deno edge function, both running the identical unfiltered SELECT — one through ctx.postgres, one through ctx.postgresAdmin. user2 sees none of user1's rows through the scoped client and sees them through the admin one, which proves claim injection, the role drop, and the bypass in a single contrast. The edge function passes connectionString explicitly from E2E_DB_URL: the CLI injects a SUPABASE_DB_URL addressing the database by container name, and Deno's DNS resolver rejects the underscores in it. The Node app still covers the SUPABASE_DB_URL default path. * docs: document the postgres middleware pair Adds docs/postgres.md covering both halves, the SQL each query runs, the two composition paths, table grants, the RLS bypass and why it is a separate middleware, and guidance to write policies with the auth.* helpers rather than reading request.jwt.claim.* directly. Wires both subpaths into typedoc entryPoints — without which neither export reached api-docs/ — and adds README sections, Exports and env-var rows, and api-reference entries. * fix(middleware): discard the connection when a rollback fails pg-pool only removes a client when release() is given a truthy argument, so the previous release() returned a connection whose transaction could not be unwound straight back to the pool — potentially still inside the caller's transaction with their role set. That was survivable while the pool served one middleware. It is not now that withPostgresAdminClient shares it: that middleware begins no transaction and sets up no session state, so it would silently inherit the leftover role on the next checkout. * fix(middleware): refuse unsupported roles instead of downgrading to anon withPostgresClient silently mapped every role that was not 'authenticated' to 'anon'. For a forged service_role that was the intent, but Supabase also supports custom roles via the role claim, and RLS applies to those normally — so a legitimate `role: manager` token was being answered with zero rows and no indication that the role was the reason. Now only 'authenticated' and 'anon' are assumed, and anything else short-circuits with a 500 and code UNSUPPORTED_ROLE before the handler runs or a connection is checked out. service_role gets a message pointing at withPostgresAdminClient; other roles are named in the error. Custom roles remain unsupported — the reason is that PostgREST connects as the unprivileged authenticator, where `grant <role> to authenticator` is itself the authorization, while we connect as postgres and have no such boundary to lean on. Documented, and tracked separately. Also hoists the per-request claims serialization out of the per-query path. * docs: list every subpath in the README exports table The table covered 8 of 13 entry points. Adding the postgres pair made the omission look deliberate rather than incidental — a reader could reasonably conclude withClaims has no subpath, which matters because it is the documented prerequisite for composing withPostgresClient standalone. * feat(middleware): make query a tagged template, add queryRaw and ident `query` now takes a tagged template only, so every interpolation becomes a bind parameter and can never alter the shape of the statement. `queryRaw(text, params)` keeps the string form — it is fully safe with params, and it is the only path that works for query builders and codegen emitting `{ sql, parameters }`, or for SQL that has to interpolate an identifier. Passing a plain string to `query` throws, naming `queryRaw`. The two calls differ only in their brackets, so refusing beats reinterpreting: the string's first character would otherwise be read as the whole template and a one-character query would be sent. `ident()` quotes identifiers, which can never be bind parameters — `select $1 from notes` selects a literal, not a column. It is implemented directly rather than wrapping `pg.escapeIdentifier`: that top-level export only exists from pg 8.11, while the peer range is `^8.0.0`, so a wrapper would be a runtime TypeError on 8.0-8.5. It also rejects empty names and NUL bytes, which pg passes straight through to a confusing server-side error. `set local role` now quotes the role via `ident()`. The role is already constrained to the SUPPORTED_ROLES allowlist, so this changes nothing today — it keeps the interpolation safe if that list widens to the custom roles the docstring promises. Follows the prior art: Prisma shipped the dual overload and reversed it, Slonik refuses plain strings outright, and postgres.js requires the tag with `sql.unsafe` as the named escape hatch. The e2e edge function built its query by interpolating a column list. As a `query` tag that would have compiled to `select $1 from notes` and returned the literal string for every row — valid SQL, wrong rows, no error. It now uses `queryRaw`, with a comment explaining why. * fix: refuse non-string role claims instead of downgrading to anon * chore: keep prettier off the release-please changelog --------- Co-authored-by: Katerina Skroumpelou <sk.katherine@gmail.com>
2026-08-24 09:52:01 -05:00
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.
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
## Types
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
### AuthMode
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
```ts
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
type AuthMode = 'none' | 'publishable' | 'secret' | 'user'
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
```
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
### AuthModeWithKey
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
```ts
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
type AuthModeWithKey = AuthMode | `publishable:${string}` | `secret:${string}`
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
```
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.
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
### 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
```
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
### 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.
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
### SupabaseContext\<Database\>
```ts
interface SupabaseContext<Database = unknown> {
supabase: SupabaseClient<Database>
supabaseAdmin: SupabaseClient<Database>
userClaims: UserClaims | null
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
jwtClaims: JWTClaims | null
authMode: AuthMode
authKeyName?: string
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
}
```
### WithSupabaseConfig
```ts
interface WithSupabaseConfig {
auth?: AuthConfig // default: 'user'
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
/** @deprecated use `auth` instead — will be removed in a future major release */
allow?: AuthModeWithKey | AuthModeWithKey[]
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
env?: Partial<SupabaseEnv>
cors?: boolean | Record<string, string> // default: true
supabaseOptions?: SupabaseClientOptions<string>
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
errors?: ErrorResponseConfig
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
}
```
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
### 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).
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
### 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 {
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
authMode: AuthMode
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
token: string | null
userClaims: UserClaims | null
Pre-v1 API cleanup: rename allow/authType/claims, narrow authKeyName, refresh adapter docs (#48) * feat: rename `allow` config option to `auth` Aligns the SDK with Supabase CLI terminology — `auth: 'user'` reads more naturally than `allow: 'user'`. The legacy `allow` key still works (with a one-time `console.warn` per process) and will be removed in a future major release; when both `auth` and `allow` are provided, `auth` wins. Also exports new `AuthMode` / `AuthModeWithKey` types alongside deprecated `Allow` / `AllowWithKey` aliases. * feat!: rename auth mode values `'always'` → `'none'` and `'public'` → `'publishable'` Aligns auth-mode values with Supabase CLI terminology. `'none'` reads more directly than `'always'` for "no authentication required", and `'publishable'` matches the `SUPABASE_PUBLISHABLE_KEY(S)` env var names. `'secret'` and `'user'` are unchanged. BREAKING CHANGE: the `'always'` and `'public'` mode values no longer work. Replace `auth: 'always'` with `auth: 'none'`, `auth: 'public'` with `auth: 'publishable'`, and `auth: 'public:<name>'` with `auth: 'publishable:<name>'`. Runtime checks like `ctx.authType === 'public'` must be updated to `ctx.authType === 'publishable'`. * feat!: rename `authType` field to `authMode` on `AuthResult` and `SupabaseContext` Lines the field name up with its type — `authMode: AuthMode`. Reads more naturally for both humans and AI agents working with the API. BREAKING CHANGE: the `authType` field was renamed to `authMode` on `AuthResult` (returned by `verifyAuth` / `verifyCredentials`) and on `SupabaseContext` (passed to handlers). Find-and-replace `ctx.authType` → `ctx.authMode` and `auth.authType` → `auth.authMode` across your codebase. * feat!: rename `claims` field to `jwtClaims` on `AuthResult` and `SupabaseContext` Pairs naturally with `userClaims` and makes the snake_case JWT payload distinct from the normalized identity view at a glance. * refactor!: narrow `SupabaseContext.authKeyName` to `string | undefined` The field used to be `string | null | undefined` (optional + explicitly nullable), forcing consumers to handle two absence values. Collapse to a single representation by dropping `null`: the property is simply omitted for `'user'` and `'none'` modes, which don't match a named key. `AuthResult.keyName` keeps its `string | null` shape — it's the low-level type where the field is always present and `null` actively signals "no named key for this mode." * docs: add publishable-key example to README quick start The auth-modes table documented the publishable mode but the quick start only showed user, none, secret, dual, and server-to-server examples, leaving readers without a concrete shape for publishable. Slot it between the "no auth" and "secret" examples so the progression reads no key → publishable (anon, key-gated) → secret (admin, key-gated). The example clarifies the resulting client behavior — `supabase` is anonymous, RLS still applies, and the publishable key is a client gate rather than a user identity — which is the most common point of confusion vs. `auth: 'secret'`. * docs: update skill description * docs: update ssr references accross docs and skill * docs(adapters): add ecosystem index + community contribution guide Adds src/adapters/README.md (index, maintenance model, contribution checklist) and docs/adapters/h3.md. Moves docs/hono-adapter.md into docs/adapters/. Slims the top-level README Framework Adapters section to a canonical adapter table + brief examples. Updates CONTRIBUTING.md, docs/getting-started.md, and the supabase-server skill to reference the new paths. * docs: sweep adapter and SSR docs to use renamed API The cherry-picked docs commits were authored before the API renames in this branch, so the new content arrived using `allow:`, `'always'`, `'public'`, `claims`, `authType`, and `AllowWithKey`. Update the newly-arrived files in line with the renamed API: - docs/adapters/h3.md — `allow:` → `auth:`, `claims, authType` → `jwtClaims, authMode` throughout - docs/ssr-frameworks.md — composed Next.js adapter example now uses `auth:` / `AuthModeWithKey` / `jwtClaims` / `authMode` - src/adapters/README.md — adapter-test checklist mentions the four current modes (`'user'`, `'publishable'`, `'secret'`, `'none'`) - CONTRIBUTING.md — same wording fix in the adapter-PR section - skills/supabase-server/SKILL.md — top-level skill description points at `auth:` and the new mode values; legacy patterns folded into the existing migration trigger - src/adapters/hono/middleware.ts — inline comment example uses `auth:` in both halves rather than mixing legacy and current option names - README.md — collapsed Hono and H3 quick-start snippets use `auth:` Migration prose (`README.md` callout, `docs/auth-modes.md` callout, `docs/api-reference.md` deprecated-aliases section, `SKILL.md` migration callouts) intentionally still references the old names; they document the migration itself. * docs: reframe Beta disclaimer for v1 launch + extract MIGRATION.md The Beta callout ("APIs and documentation may change") directly contradicts the SemVer commitment that v1 makes. For launch material that pins to v1, the contradiction undermines the stability message the version number is meant to carry. Replace it with a v1.0 callout that leads with stability under SemVer and follows with honest "active development continues" framing — new adapters and ergonomic improvements in minor releases, breaking changes only ever in a major bump. Move the v0 → v1 rename map out of the README and into a dedicated MIGRATION.md. The README quick start was buried under 20+ lines of migration tables that only matter to upgraders, not first-time readers — exactly the wrong tradeoff at launch. New short callout points upgraders at MIGRATION.md. SKILL.md gets the same Beta → v1.0 swap. The agent-operational migration rules (lines 12-14: "always emit `auth:` in new code", "the new mode values are `'none'` / `'publishable'`") are kept inline — they're rules the agent applies every time it writes code, not user-facing migration steps, so they don't belong in MIGRATION.md. * docs: reframe v1.0 callout as "Public Beta" to match Supabase house style The previous "Stable under SemVer; active development continues" framing mixed two distinct axes — code stability (SemVer) and product lifecycle stage (Public Beta / GA) — into the SemVer line. Several Supabase docs run those independently: a release can be v1+ in SemVer terms and still labeled Public Beta in lifecycle terms. Lead with both signals in the headline: "v1.0 — Public Beta." Keep the SemVer commitment ("breaking changes only ship as a major bump") so launch copy can pin to v1, and pair it with the Public Beta lifecycle stage so readers know the product line is still early. Same swap in the SKILL.md mirror.
2026-05-06 04:16:14 -05:00
jwtClaims: JWTClaims | null
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
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'
```
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
---
## Error Classes
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
### 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.
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
### EnvError
```ts
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
class EnvError extends SupabaseServerError {
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
readonly status: 500
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
constructor(
message: string,
code?: string,
options?: SupabaseServerErrorOptions,
)
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
}
```
### AuthError
```ts
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
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
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
}
```
---
## Error Code Constants
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
| 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.
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
---
## Errors Factory Map
```ts
const Errors: {
[MissingSupabaseURLError]: () => EnvError
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
[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
docs: add SDK documentation and SKILL.md (#20) * docs: add initial documentation and skills.md * docs: apply formatting * docs: update SKILL.md to resolve docs from package location and ship docs with npm SKILL.md now instructs agents to find documentation in the installed @supabase/server package (node_modules or repo root) instead of using relative paths. Added docs/ and SKILL.md to package.json files array so they ship with npm installs. * docs: add missing HTTPException import in error-handling example * docs: fix strictNullChecks issues, duplicate variables, and missing context in examples - Add non-null assertions (!) after error guards where TS can't narrow destructured result tuples - Split duplicate variable declarations into separate code blocks - Add missing imports and show where variables like `auth` come from - Keep { data, error } destructuring pattern consistent with SDK convention * docs: reframe as runtime-agnostic and add env auto-injection details - getting-started: replace Edge Function framing with runtime-neutral language, explain module worker pattern works across Deno/Bun/Workers, add Runtimes section covering all supported environments - webhooks: replace Deno.env with process.env for portable examples - environment-variables: add "Auto-injected in" column distinguishing Platform vs Local CLI, reframe section headers - auth-modes: clean up example key values - core-primitives: clarify "Integration with frameworks" wording - types: simplify TSDoc for publishable/secret key descriptions * docs: add SSR frameworks guide and update references Add docs/ssr-frameworks.md covering the pattern for using core primitives in Next.js, SvelteKit, Nuxt, and Remix — cookie extraction, env bridging, JWKS caching, and a complete Next.js adapter example. Replace the basic SSR example in core-primitives.md with a pointer to the new dedicated doc. Add SSR row to SKILL.md routing table. * docs: add disclaimer of new package * docs: extend explanation on keys env vars * docs: add platform-specific quick starts to SKILL.md Split the single generic example into per-platform sections (Edge Functions, Cloudflare Workers, Hono, SSR Frameworks) so AI agents pick the correct import specifier for each runtime. Adds npm: prefix to all Deno examples and a Deno column to the entry points table. Also adds createSupabaseContext examples. * docs: add server-to-server quick starts and allow:always guardrails Add secret key auth and webhook signature verification quick starts to SKILL.md. Add explicit decision tree for allow:'always' so AI agents confirm with the user before leaving endpoints unprotected. * docs: add legacy keys warning, skills install, remove webhook docs - Add legacy keys warning to SKILL.md (avoid anon/service_role keys) - Add AI coding skills install section to README - Add server-to-server quick start with caller code to README - Add runtimes, documentation table, and named secret keys to README - Remove verifyWebhookSignature references from all docs - Delete docs/webhooks.md (code removal in separate PR) * docs: add verify_jwt = false note for non-user auth modes Edge Functions require verify_jwt = false in config.toml when using allow: public, secret, or always — otherwise the platform rejects requests before the handler runs. * docs: add edge function recipes and refactor env vars doc Add recipes for function-to-function calls, pg_net from database, Stripe webhooks, and generic webhook signature verification. Document the @supabase/server/wrappers entry point. Refactor environment-variables.md into Supabase vs non-Supabase sections. * docs: add security doc covering timing-safe comparison, auth model, CORS * docs: link auth-modes timing-safe mentions to security.md * docs: adding 'local cli' to secrets table This envs will be injected from cli too * docs: setting Deno as first installation choice * docs: adding 'verify_jwt=false' disclaimer for non-user auth * docs: split Deno/Supabase runtime section, merge Deno/Node/Bun * docs(skills): adding legacy code migration example * docs(skills): explaining why legacy code should be migrated * docs: rewrite migration section, improve skill description triggers --------- Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
2026-03-31 16:14:55 -05:00
}
```
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
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[]>
matchedKey?: { kind: 'publishable' | 'secret'; name: string; mode: string }
feat(errors): add self-identifying errors with hints and diagnostics (#130) * feat: specific, self-identifying errors with hints and diagnostics Nearly every failure returned `{ message: "Invalid credentials", code: "INVALID_CREDENTIALS" }` — naming neither the cause nor the library it came from. Provenance. All errors now share a `SupabaseServerError` base carrying `source: "@supabase/server"`, a `[@supabase/server]` message prefix (the convention `deprecation.ts` already used for warnings), a `docs` link to the matching `docs/error-handling.md` section, an optional `hint`, and non-sensitive `details`. `toJSON()` renders the wire payload and is picked up by `JSON.stringify`, so logging no longer yields `{}`. One `errorResponse()` helper renders it everywhere, repeating the code in an `x-supabase-server-error` header and adding that to `Access-Control-Expose-Headers` so cross-origin callers can read it. Top-level `message` and `code` are unchanged, so existing consumers and the adapters keep working. Diagnosis. `verifyUserJwt` now returns *why* a token failed instead of `null`, and the mode chain records why each mode fell through, so the final error names the real cause: `MISSING_CREDENTIALS`, `INVALID_API_KEY`, `INVALID_JWT`, plus `JWKS_NOT_CONFIGURED`, `JWKS_FETCH_FAILED` and `NO_KEYS_CONFIGURED` for states where no request could ever have succeeded. `INVALID_CREDENTIALS` stays exported as the fallback. Hints cover the mistakes people actually make — a secret key sent to a publishable-only endpoint, a legacy anon/service_role key, an `Authorization` header without the `Bearer` scheme, a JWT with no `kid`, an expired token, a JWKS from the wrong project. The middleware that answer directly get the same treatment rather than their own hand-rolled bodies: `withClaims` / `withRequiredClaims` report `MISSING_JWKS`, `MISSING_CREDENTIALS`, `INVALID_JWT`; `withPostgresClient` / `withPostgresAdminClient` report `MISSING_CONNECTION_STRING` and a catalogued `UNSUPPORTED_ROLE`. `details` never carries key values or token payloads: API keys are reported by prefix format, named keys by name, JWTs by `alg`/`kid` only. Note: server misconfiguration now surfaces as 500 rather than 401. A missing or unreachable JWKS, or an auth mode no configured key can match, are not the caller's fault. * feat: add `errors: { detailed: false }` to trim the error response body `hint`, `docs`, and `details` are written for whoever is building against the endpoint, and not everyone wants them on the wire. `errors.detailed` (default `true`) reduces the body to `code` and `message` alone. Provenance survives the trim: `message` keeps its `[@supabase/server]` prefix, and the code is still sent as the `x-supabase-server-error` header — so the error stays identifiable without the `source` field. Response-only. The HTTP status is unaffected and the error object keeps `hint`, `docs`, and `details` in full, so `createSupabaseContext` callers and the framework adapters see everything. Documented as a verbosity control rather than a security boundary — `code` and `message` still name the failure specifically. Formatting the response by hand via `createSupabaseContext` remains the way to disclose nothing. * feat: distinguish UNUSABLE_CREDENTIAL from MISSING_CREDENTIALS Review feedback on #130: the top-level code was `MISSING_CREDENTIALS` even when a credential had arrived, just the wrong kind. `received. authorization: 'api-key'` and the hint carried the diagnosis, but `errors: { detailed: false }` strips both — leaving a caller who is demonstrably sending a key staring at a bare `MISSING_CREDENTIALS`. That mode makes the code the only thing a caller can rely on, so it has to be true standing alone. `UNUSABLE_CREDENTIAL` (401) now covers "a credential arrived that no accepted mode can use", partitioning the space exactly against `MISSING_CREDENTIALS` ("nothing arrived"). It has two shapes, named in the `message` so the diagnosis survives the trim: - wrong kind: an `sb_*` API key in the Authorization header - unreadable: wrong scheme, wrong casing, bare value, empty token The unreadable shapes had the same defect and are fixed with it — a `Basic` or lowercase-`bearer` header is not a missing credential either. Classification moves into one shared `diagnoseAuthorizationHeader`, since only the raw header separates "sent nothing" from "sent something unreadable" and both `verifyAuth` and the `withRequiredClaims` gate need that distinction. Previously the gate could not make it at all, so the two disagreed on every scheme case. A parity matrix over all six header shapes now pins gate and `withSupabase({ auth: 'user' })` to the same status and code. * fix: preserve error cause when client creation fails * fix: report API keys as UNUSABLE_CREDENTIAL on user-only endpoints Review feedback on #130: `supabase-js` sends the publishable key in both the `apikey` and `Authorization` headers, so an unauthenticated browser call to an `auth: 'user'` endpoint arrives with a key in each slot. The `apikey !== 'absent'` branch in `explainFallthrough` was read first, so the caller got `INVALID_API_KEY` — "check you are pointing at the right Supabase project" — for a key that was never going to be looked up. With `errors: { detailed: false }` the code is all they get, and it sent them hunting for a key mismatch that does not exist. `INVALID_API_KEY` means "matched none of the configured keys", which only says something when a mode was doing that lookup. It is now gated on an attempted `publishable` / `secret` mode; where no mode reads keys, a key in either header is `UNUSABLE_CREDENTIAL` — not wrong, just the wrong kind of credential. The apikey-header-only case had the same defect and is fixed with it: "matched no key configured for auth mode(s): "user"" described a lookup that never happened. The new diagnosis is shared as `apiKeyOnUserOnlyEndpoint`, so the `withRequiredClaims` gate stops answering with "API keys belong in the `apikey` header" for callers who already sent it there — that gate only ever accepts a user JWT, so moving the key would not help. It keeps the parity the gate is built for: an identical request, worded identically from both paths. `ApiKeyInAuthorizationHeader` still covers the case where the advice is right — a mixed `['user', 'publishable']` endpoint with a key in `Authorization` alone. * docs: add MissingConnectionStringError documentation and clarify credential error handling
2026-08-31 11:16:38 -03:00
}
```