supabase-releaser[bot] a59a410cf3 chore(main): release server 0.1.1 (#12)
Co-authored-by: supabase-releaser[bot] <223506987+supabase-releaser[bot]@users.noreply.github.com>
2026-03-27 15:37:57 -05:00
2026-02-24 18:34:12 +02:00
2026-02-24 18:34:12 +02:00
2026-02-24 18:25:54 +02:00
2026-02-24 18:25:54 +02:00
2026-02-24 18:25:54 +02:00
2026-02-24 18:34:12 +02:00

@supabase/server

License Package pkg.pr.new

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

import { withSupabase } from '@supabase/server'

export default {
  fetch: 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

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

# npm
pnpm add @supabase/server

Quick Start

Authenticated endpoint

export default {
  fetch: 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)

export default {
  fetch: withSupabase({ allow: 'always' }, async (_req, _ctx) => {
    return Response.json({ status: 'ok' })
  }),
}

API key protected

export default {
  fetch: withSupabase({ allow: 'secret' }, async (_req, ctx) => {
    const { data } = await ctx.supabaseAdmin.from('config').select()
    return Response.json(data)
  }),
}

Dual auth (user or service)

export default {
  fetch: 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:

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

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. 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).

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

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' }))

export default { fetch: 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.

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

verifyAuth

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

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

verifyCredentials

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

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

createContextClient / createAdminClient

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.

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

resolveEnv

Resolves environment variables with optional overrides.

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

Example: custom multi-route handler

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

export default {
  fetch: 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(
          { message: 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":[...]} or [...] 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

pnpm install
pnpm dev

Contributing

See CONTRIBUTING.md for development workflow, commit conventions, and release process.

License

MIT

S
Description
Use when planning or writing server-side code that uses @supabase/server — Edge Functions, Hono apps, webhook handlers, or any backend that creates Supabase…
Readme MIT 1.7 MiB
Languages
TypeScript 97.3%
JavaScript 2.2%
Shell 0.5%