Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
56d9546
Update readme with proposed desired state
tomaspozo Mar 6, 2026
8ba5a05
feat: implement core primitives, wrappers, and adapters
tomaspozo Mar 6, 2026
5327920
chore: update pnpm-lock.yaml
tomaspozo Mar 6, 2026
bfb4e8d
feat: export all core primitives from main entry point
tomaspozo Mar 6, 2026
1926736
refactor: remove auth hooks and replace jsr: with npm: in README
tomaspozo Mar 6, 2026
5ff2a0f
feat: updaye hono adapter with createMiddleware pattern
tomaspozo Mar 6, 2026
c0483af
feat: throw HTTPException in Hono middleware instead of returning JSO…
tomaspozo Mar 6, 2026
8c5cd25
refactor: move createSupabaseContext and withSupabase to top-level src/
tomaspozo Mar 6, 2026
10725e3
fix: resolve type errors in Hono middleware test and tsconfig
tomaspozo Mar 8, 2026
d3c4445
refactor: rename Hono context variable from supabase to supabaseContext
tomaspozo Mar 8, 2026
a23e2d5
docs: update readme
tomaspozo Mar 9, 2026
04f9906
feat: use dict for keys
tomaspozo Mar 9, 2026
9a537c9
test: improve test cases for 'verify-credentials'
kallebysantos Mar 10, 2026
3d61a55
refactor: change key storage from NamedKey[] to Record<string, string>
tomaspozo Mar 20, 2026
bd26427
test: update test keys to use sb_ prefix
tomaspozo Mar 20, 2026
d4ad790
feat: rename hono adapter to withSupabase
tomaspozo Mar 20, 2026
79dd78c
chore: rename package to @supabase/server
tomaspozo Mar 20, 2026
0027895
feat: support singular env vars for publishable and secret keys
tomaspozo Mar 20, 2026
cf598de
style: run format
tomaspozo Mar 20, 2026
232c8c4
refactor: simplify CORS to use supabase-js defaults with pass-through…
tomaspozo Mar 20, 2026
6cb1186
fix: audit fixes for validation, error handling, and tests
tomaspozo Mar 20, 2026
c76a194
refactor: rename user to userClaims and UserIdentity to UserClaims
tomaspozo Mar 20, 2026
0a2403d
test: add edge case tests for credentials, allow modes, Hono skip, an…
tomaspozo Mar 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,18 @@

## [0.1.2](https://github.com/supabase/edge-functions/compare/edge-functions-v0.1.1...edge-functions-v0.1.2) (2026-02-24)


### Bug Fixes

* remove provenance until repo is public ([2ebbc71](https://github.com/supabase/edge-functions/commit/2ebbc71e214c4bbae62c6af203a039801b5e3d4d))
- remove provenance until repo is public ([2ebbc71](https://github.com/supabase/edge-functions/commit/2ebbc71e214c4bbae62c6af203a039801b5e3d4d))

## [0.1.1](https://github.com/supabase/edge-functions/compare/edge-functions-v0.1.0...edge-functions-v0.1.1) (2026-02-24)


### Features

* set initial release version ([8352bda](https://github.com/supabase/edge-functions/commit/8352bda35c5967a6692f0a21744d30793e10709a))
- set initial release version ([8352bda](https://github.com/supabase/edge-functions/commit/8352bda35c5967a6692f0a21744d30793e10709a))

## 0.1.0 (2026-02-24)


### Features

* set initial release version ([8352bda](https://github.com/supabase/edge-functions/commit/8352bda35c5967a6692f0a21744d30793e10709a))
- set initial release version ([8352bda](https://github.com/supabase/edge-functions/commit/8352bda35c5967a6692f0a21744d30793e10709a))
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contributing to `@supabase/edge-functions`
# Contributing to `@supabase/server`

Thank you for your interest in contributing to `@supabase/edge-functions`! This document provides guidelines and instructions for contributing to the project.
Thank you for your interest in contributing to `@supabase/server`! This document provides guidelines and instructions for contributing to the project.

## Table of Contents

Expand Down Expand Up @@ -193,4 +193,4 @@ Publishing is fully automated via GitHub Actions:

## License

By contributing to `@supabase/edge-functions`, you agree that your contributions will be licensed under the MIT License.
By contributing to `@supabase/server`, you agree that your contributions will be licensed under the MIT License.
291 changes: 287 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,293 @@
# @supabase/edge-functions
# @supabase/server

[![License](https://img.shields.io/npm/l/nx.svg?style=flat-square)](./LICENSE)
[![Package](https://img.shields.io/npm/v/@supabase/edge-functions)](https://www.npmjs.com/package/@supabase/edge-functions)
[![pkg.pr.new](https://pkg.pr.new/badge/supabase/edge-functions)](https://pkg.pr.new/~/supabase/edge-functions)
[![Package](https://img.shields.io/npm/v/@supabase/server)](https://www.npmjs.com/package/@supabase/server)
[![pkg.pr.new](https://pkg.pr.new/badge/supabase/server)](https://pkg.pr.new/~/supabase/server)

> TBD
Server-side utilities for Supabase. Handles auth, client creation, and context injection so you write business logic, not boilerplate.

```ts
import { withSupabase } from '@supabase/server'

Deno.serve(
withSupabase({ allow: 'user' }, async (req, ctx) => {
const { data } = await ctx.supabase.from('todos').select()
return Response.json(data)
}),
)
```

One import. One line of config. Auth is validated, clients are scoped, CORS is handled. Your handler only runs on successful auth.

## Installation

```bash
# Deno
import { withSupabase } from "npm:@supabase/server";

# npm
pnpm add @supabase/server
```

## Quick Start

### Authenticated endpoint

```ts
import { withSupabase } from '@supabase/server'

Deno.serve(
withSupabase({ allow: 'user' }, async (req, ctx) => {
// ctx.supabase — RLS-scoped to the authenticated user
// ctx.supabaseAdmin — bypasses RLS (service role)
// ctx.userClaims — user identity from JWT (id, email, role)
// ctx.claims — JWT claims
// ctx.authType — which auth mode matched

const { data } = await ctx.supabase.from('todos').select()
return Response.json(data)
}),
)
```

### Public endpoint (no auth)

```ts
Deno.serve(
withSupabase({ allow: 'always' }, async (req, ctx) => {
return Response.json({ status: 'ok' })
}),
)
```

### API key protected

```ts
Deno.serve(
withSupabase({ allow: 'secret' }, async (req, ctx) => {
const { data } = await ctx.supabaseAdmin.from('config').select()
return Response.json(data)
}),
)
```

### Dual auth (user or service)

```ts
Deno.serve(
withSupabase({ allow: ['user', 'secret'] }, async (req, ctx) => {
const userId = ctx.userClaims?.id ?? (await req.json()).user_id
const { data } = await ctx.supabaseAdmin
.from('reports')
.select()
.eq('user_id', userId)
return Response.json(data)
}),
)
```

## Auth Modes

| Mode | Credential | Use case |
| ------------------ | --------------------- | --------------------------------------------------- |
| `"user"` (default) | Valid JWT | Authenticated user endpoints |
| `"public"` | Valid publishable key | Client-facing, key-validated endpoints |
| `"secret"` | Valid secret key | Server-to-server, internal calls |
| `"always"` | None | Open endpoints, wrappers that handle their own auth |

Array syntax (`allow: ["user", "secret"]`) accepts multiple auth methods — first match wins.

Named key validation: `allow: "public:web_app"` validates against a specific named key in `SUPABASE_PUBLISHABLE_KEYS`.

## Context

Every handler receives a `SupabaseContext`:

```ts
interface SupabaseContext {
supabase: SupabaseClient // RLS-scoped (user or anon depending on auth)
supabaseAdmin: SupabaseClient // Bypasses RLS
userClaims: UserClaims | null // JWT-derived identity (for full User, call supabase.auth.getUser())
claims: JWTClaims | null // Present when auth is JWT
authType: Allow // Which auth mode matched
}
```

`supabase` is always the safe client — it respects RLS. When `authType` is `"user"`, it's scoped to that user's permissions. Otherwise, it's initialized as anonymous.

`supabaseAdmin` always bypasses RLS. Use it for operations that need full database access.

## Config

```ts
withSupabase(
{
allow: 'user', // who can call this function
cors: false, // disable CORS (default: supabase-js CORS headers)
env: { url: '...' }, // env overrides (optional)
},
handler,
)
```

`cors` defaults to the standard [supabase-js CORS headers](https://supabase.com/docs/guides/functions/cors). Pass a `Record<string, string>` to set custom headers, or `false` to disable CORS handling (e.g. when using a framework that handles CORS separately).

```ts
withSupabase(
{
allow: 'user',
cors: {
'Access-Control-Allow-Origin': 'https://myapp.com',
'Access-Control-Allow-Headers': 'authorization, content-type',
},
},
handler,
)
```

`env` overrides environment variable resolution. Defaults to reading `SUPABASE_URL`, `SUPABASE_PUBLISHABLE_KEYS`, `SUPABASE_SECRET_KEYS`, and `SUPABASE_JWKS` from the runtime environment.

## Framework Adapters

### Hono

```ts
import { Hono } from 'hono'
import { withSupabase } from '@supabase/server/adapters/hono'

const app = new Hono()

app.get('/todos', withSupabase({ allow: 'user' }), async (c) => {
const { supabase: sb } = c.var.supabaseContext
const { data } = await sb.from('todos').select()
return c.json(data)
})

app.get('/health', (c) => c.json({ status: 'ok' }))

Deno.serve(app.fetch)
```

The adapter does not handle CORS — use `hono/cors` for that. Per-route auth works naturally by applying the middleware to specific routes.

## Primitives

For when you need more control than `withSupabase` provides — multiple routes with different auth, custom response headers, or building your own wrapper.

All primitives are available from `@supabase/server/core`.

```ts
import {
verifyAuth,
createContextClient,
createAdminClient,
} from '@supabase/server/core'
```

### verifyAuth

Extracts credentials from a Request and validates against the allow config.

```ts
const { data: auth, error } = await verifyAuth(req, { allow: 'user' })
if (error) {
return Response.json({ error: error.message }, { status: error.status })
}
```

### verifyCredentials

Low-level — works with raw credentials instead of a Request. Used by SSR adapters and custom auth flows.

```ts
const credentials = { token: myToken, apikey: null }
const { data: auth, error } = await verifyCredentials(credentials, {
allow: 'user',
})
```

### createContextClient / createAdminClient

```ts
const supabase = createContextClient(auth.token) // user-scoped, RLS applies
const supabase = createContextClient() // anonymous, RLS as anon
const supabaseAdmin = createAdminClient() // bypasses RLS
```

### createSupabaseContext

Full context assembly from a Request — `verifyAuth` + client creation in one call.

```ts
const { data: ctx, error } = await createSupabaseContext(req, { allow: 'user' })
```

### resolveEnv

Resolves environment variables with optional overrides.

```ts
const { data: env, error } = resolveEnv({
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
})
```

### Example: custom multi-route handler

```ts
import { verifyAuth, createContextClient } from '@supabase/server/core'

Deno.serve(async (req) => {
const url = new URL(req.url)

if (url.pathname === '/health') {
return Response.json({ status: 'ok' })
}

if (url.pathname === '/todos') {
const { data: auth, error } = await verifyAuth(req, { allow: 'user' })
if (error)
return Response.json({ error: error.message }, { status: error.status })

const supabase = createContextClient(auth.token)
const { data } = await supabase.from('todos').select()
return Response.json(data)
}

return new Response('Not found', { status: 404 })
})
```

## Environment Variables

Automatically available in Supabase Edge Functions:

| Variable | Format | Description |
| --------------------------- | ------------------------------------------------------------- | ------------------------------------- |
| `SUPABASE_URL` | `https://<ref>.supabase.co` | Your project URL |
| `SUPABASE_PUBLISHABLE_KEYS` | `{"default":"sb_publishable_...","web":"sb_publishable_..."}` | Publishable API keys (named) |
| `SUPABASE_SECRET_KEYS` | `{"default":"sb_secret_...","web":"sb_secret_..."}` | Secret API keys (named) |
| `SUPABASE_JWKS` | `{"keys":[...]}` | JSON Web Key Set for JWT verification |

Also supported (for local dev, self-hosted, or other runtimes):

| Variable | Format | Description |
| -------------------------- | -------------------- | ---------------------- |
| `SUPABASE_PUBLISHABLE_KEY` | `sb_publishable_...` | Single publishable key |
| `SUPABASE_SECRET_KEY` | `sb_secret_...` | Single secret key |

When both singular and plural forms are set, plural takes priority.

For other environments, pass overrides via the `env` config option or `resolveEnv()`.

## Exports

| Export | What's in it |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `@supabase/server` | `withSupabase`, `createSupabaseContext` |
| `@supabase/server/core` | `verifyAuth`, `verifyCredentials`, `extractCredentials`, `createContextClient`, `createAdminClient`, `resolveEnv` |
| `@supabase/server/wrappers` | `verifyWebhookSignature` |
| `@supabase/server/adapters/hono` | `withSupabase` (Hono middleware) |

## Development

Expand Down
Loading
Loading