# Deno (import directly)
import { withSupabase } from 'npm:@supabase/server'
# npm
npm install @supabase/server
# pnpm
pnpm add @supabase/server
@supabase/server requires @supabase/supabase-js as a peer dependency:
# npm
npm install @supabase/supabase-js
# pnpm
pnpm add @supabase/supabase-js
The fastest way to get a working authenticated endpoint:
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)
}),
}
The
export default { fetch }pattern is the standard module worker interface supported by Deno (including Supabase Edge Functions), Bun, and Cloudflare Workers. For Node.js, use the Hono adapter or core primitives with your framework of choice.
This single wrapper does four things for every request:
OPTIONS preflight and adds CORS headers to all responses{ message, code }) if auth failsYour handler only runs when auth succeeds.
import { withSupabase } from '@supabase/server'
export default {
fetch: withSupabase({ allow: 'always' }, async (_req, _ctx) => {
return Response.json({ status: 'ok', time: new Date().toISOString() })
}),
}
Supabase Edge Functions: By default, the platform requires a valid JWT on every request. If your function uses
allow: 'public',allow: 'secret', orallow: 'always', disable the platform-level JWT check insupabase/config.toml:[functions.my-function] verify_jwt = false
Every handler receives a SupabaseContext with these fields:
| Field | Type | Description |
|---|---|---|
supabase |
SupabaseClient |
Client scoped to the caller. RLS policies apply. |
supabaseAdmin |
SupabaseClient |
Admin client. Bypasses RLS. |
userClaims |
UserClaims | null |
JWT-derived identity (id, email, role, appMetadata, userMetadata). null for non-user auth. |
claims |
JWTClaims | null |
Raw JWT payload (snake_case). null for non-user auth. |
authType |
Allow |
Which auth mode matched: 'user', 'public', 'secret', or 'always'. |
authKeyName |
string | null |
Which auth key name of the API key that was used. |
The supabase client respects Row-Level Security. When authType is 'user', the client is scoped to that user's permissions. For other auth modes, it's initialized as anonymous.
The supabaseAdmin client always bypasses RLS. Use it for operations that need full database access regardless of who's calling.
userClaims gives you a lightweight view of the user's identity from the JWT. For the full Supabase User object (email confirmation, providers, etc.), call ctx.supabase.auth.getUser().
When you need the context without the full wrapper — inside a framework route handler, custom middleware, or any situation where you want to control the response yourself:
import { createSupabaseContext } from '@supabase/server'
export default {
fetch: async (req: Request) => {
const { data: ctx, error } = await createSupabaseContext(req, {
allow: 'user',
})
if (error) {
return Response.json(
{ message: error.message, code: error.code },
{ status: error.status },
)
}
const { data } = await ctx!.supabase.from('todos').select()
return Response.json(data)
},
}
createSupabaseContext returns a result tuple { data, error } instead of producing a Response. This gives you full control over error formatting and response headers.
CORS is enabled by default with standard supabase-js headers. You can customize or disable it:
// Custom CORS headers
withSupabase(
{
allow: 'user',
cors: {
'Access-Control-Allow-Origin': 'https://myapp.com',
'Access-Control-Allow-Headers': 'authorization, content-type',
},
},
handler,
)
// Disable CORS (e.g., when a framework handles it)
withSupabase({ allow: 'user', cors: false }, handler)
withSupabase and createSupabaseContext work with any runtime that supports the Web API Request/Response standard. The core primitives go further — they work in any environment where you can extract headers, regardless of the request/response model (Express, Fastify, etc.).
.env files or your hosting platform. Use the Hono adapter or core primitives to integrate with any framework.nodejs_compat or pass env overrides via the env config option.For full details on environment setup per runtime, see environment-variables.md.