Loading...
Loading...
Use when writing server-side code with Supabase — Edge Functions, Hono apps, webhook handlers, or any backend that needs Supabase auth and client creation. Trigger whenever the user imports from `@supabase/server`, mentions `supabase/server`, Supabase Edge Functions, or needs server-side auth (JWT verification, API key validation, CORS handling) with Supabase. Also trigger when you see legacy patterns in existing code — `Deno.serve`, `createClient(Deno.env.get('SUPABASE_URL'))`, imports from `esm.sh/@supabase`, `deno.land/std` serve, or usage of `SUPABASE_ANON_KEY` / `SUPABASE_SERVICE_ROLE_KEY` — these indicate code that should be migrated to this package.
npx skill4agent add supabase/server supabase-serverBeta: This package is under active development. APIs and documentation may change. If you find a bug or have a feature request, please open an issue or submit a PR.
This is a brand new package. There is no information available online yet — no blog posts, no Stack Overflow answers, no tutorials. Do not search the web for usage examples. Rely exclusively on the documentation files listed below and the source code in this repository.
Do not use legacy Supabase keys. Thekey andanonkey (env varsservice_role,SUPABASE_ANON_KEY) are legacy and will be deprecated. Do not use them unless the user explicitly asks. Always use the new API keys:SUPABASE_SERVICE_ROLE_KEY
Legacy (avoid) New (use this) SUPABASE_ANON_KEY (SUPABASE_PUBLISHABLE_KEY(S))sb_publishable_...SUPABASE_SERVICE_ROLE_KEY (SUPABASE_SECRET_KEY(S))sb_secret_...Do not calldirectly — usecreateClient(url, anonKey)auth modes (@supabase/server,allow: 'user', etc.) which handle key resolution automatically. If migrating existing code, replaceallow: 'secret'usage withSUPABASE_ANON_KEYandallow: 'public'usage withSUPABASE_SERVICE_ROLE_KEY.allow: 'secret'
userpublicsecretalwaysallow: ['user', 'secret']InvalidCredentialsError| Import | Deno / Edge Functions | Provides |
|---|---|---|
| | |
| | |
| | |
Supabase Edge Functions: disablefor non-user auth. By default, Supabase Edge Functions require a valid JWT on every request. If your function usesverify_jwt,allow: 'public', orallow: 'secret', you must disable the platform-level JWT check inallow: 'always', otherwise the request will be rejected before it reaches your handler:supabase/config.tomltoml[functions.my-function] verify_jwt = falseFunctions usingcan leaveallow: 'user'enabled (the default) since callers already provide a valid JWT.verify_jwt
npm:// withSupabase — high-level wrapper
import { withSupabase } from 'npm:@supabase/server'
export default {
fetch: withSupabase({ allow: 'user' }, async (_req, ctx) => {
const { data } = await ctx.supabase.from('todos').select()
return Response.json(data)
}),
}// createSupabaseContext — returns { data, error } for custom response control
import { createSupabaseContext } from 'npm:@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)
},
}nodejs_compatwrangler.tomlenvdocs/environment-variables.mdimport { 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)
}),
}hono/corsdocs/hono-adapter.md// Node.js / Bun
import { Hono } from 'hono'
import { withSupabase } from '@supabase/server/adapters/hono'
const app = new Hono()
app.use('*', withSupabase({ allow: 'user' }))
app.get('/todos', async (c) => {
const { supabase } = c.var.supabaseContext
const { data } = await supabase.from('todos').select()
return c.json(data)
})
export default app// Deno / Supabase Edge Functions
import { Hono } from 'npm:hono'
import { withSupabase } from 'npm:@supabase/server/adapters/hono'
const app = new Hono()
app.use('*', withSupabase({ allow: 'user' }))
app.get('/todos', async (c) => {
const { supabase } = c.var.supabaseContext
const { data } = await supabase.from('todos').select()
return c.json(data)
})
export default { fetch: app.fetch }Authorization@supabase/server/coreverifyCredentialscreateContextClientdocs/ssr-frameworks.md// Key imports for building the adapter
import {
verifyCredentials,
createContextClient,
createAdminClient,
} from '@supabase/server/core'apikeydocs/auth-modes.mdimport { withSupabase } from 'npm:@supabase/server'
// Only accept the "automations" named secret key
export default {
fetch: withSupabase({ allow: 'secret:automations' }, async (req, ctx) => {
const body = await req.json()
const { data } = await ctx.supabaseAdmin
.from('scheduled_tasks')
.insert({ name: body.taskName, scheduled_at: body.scheduledAt })
return Response.json({ success: true, data })
}),
}await fetch('https://<project>.supabase.co/functions/v1/my-function', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
apikey: 'sb_secret_automations_...', // the named secret key
},
body: JSON.stringify({
taskName: 'cleanup',
scheduledAt: new Date().toISOString(),
}),
})allow: 'secret'allow: 'secret:name'allow: 'always'disables all authentication. The handler runs for every request with no credential checks. Only use it when auth is genuinely unnecessary — health checks, public status pages, or endpoints with no sensitive data and no side effects.allow: 'always'
allow: 'always'allow: 'secret'allow: 'secret:<name>'apikeyallow: 'secret'allow: 'always'allow: ['user', 'always']InvalidCredentialsErrorAuthorizationallow: 'always'allow: 'secret'ctx.supabaseAdmin.functions.invoke()supabase/config.toml[functions.process-order]
verify_jwt = false # called with secret key, not a user JWTsupabase/functions/process-order/index.tsimport { withSupabase } from 'npm:@supabase/server'
export default {
fetch: withSupabase({ allow: 'secret' }, async (req, ctx) => {
const { orderId } = await req.json()
const { data } = await ctx.supabaseAdmin
.from('orders')
.update({ status: 'processing' })
.eq('id', orderId)
.select()
.single()
return Response.json(data)
}),
}supabase/functions/checkout/index.tsimport { withSupabase } from 'npm:@supabase/server'
export default {
fetch: withSupabase({ allow: 'user' }, async (req, ctx) => {
const { orderId } = await req.json()
// Calls process-order with the secret key automatically
const { data, error } = await ctx.supabaseAdmin.functions.invoke(
'process-order',
{ body: { orderId } },
)
if (error) {
return Response.json({ error: error.message }, { status: 500 })
}
return Response.json(data)
}),
}pg_net-- 1. Enable the pg_net extension
create extension if not exists pg_net with schema extensions;
-- 2. Store your secret key in Vault
select vault.create_secret(
'sb_secret_...', -- your secret key value
'supabase_secret_key' -- a name to reference it by
);select net.http_post(
url := 'https://<project-ref>.supabase.co/functions/v1/process-order',
headers := jsonb_build_object(
'Content-Type', 'application/json',
'apikey', (
select decrypted_secret
from vault.decrypted_secrets
where name = 'supabase_secret_key'
)
),
body := jsonb_build_object('orderId', 'order_123')
);allow: 'secret'pg_netnet._http_responseallow: 'always'supabase/config.toml[functions.stripe-webhook]
verify_jwt = falsesupabase secrets set STRIPE_SECRET_KEY=sk_live_...
supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_...supabase/functions/stripe-webhook/index.tsimport { withSupabase } from 'npm:@supabase/server'
import Stripe from 'npm:stripe'
const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!)
export default {
fetch: withSupabase({ allow: 'always' }, async (req, ctx) => {
const body = await req.text()
const sig = req.headers.get('stripe-signature')!
let event: Stripe.Event
try {
event = await stripe.webhooks.constructEventAsync(
body,
sig,
Deno.env.get('STRIPE_WEBHOOK_SECRET')!,
)
} catch {
return Response.json({ error: 'Invalid signature' }, { status: 401 })
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session
await ctx.supabaseAdmin
.from('orders')
.update({ status: 'paid' })
.eq('stripe_session_id', session.id)
break
}
}
return Response.json({ received: true })
}),
}SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYDeno.serveesm.sh/@supabasedeno.land/std@supabase/serverimport { serve } from "https://deno.land/std/..."import { createClient } from "https://esm.sh/@supabase/supabase-js"Deno.serve(async (req) => { ... })createClient()Deno.env.get('SUPABASE_ANON_KEY')SUPABASE_SERVICE_ROLE_KEYimport { createClient } from 'npm:@supabase/supabase-js@2'
Deno.serve(async (req: Request) => {
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{
global: { headers: { Authorization: req.headers.get('Authorization')! } },
},
)
const { data } = await supabaseClient.from('orders').select('*')
return Response.json(data)
})import { withSupabase } from 'npm:@supabase/server'
export default {
fetch: withSupabase({ allow: 'user' }, async (_req, ctx) => {
const { data } = await ctx.supabase.from('orders').select('*')
return Response.json(data)
}),
}SUPABASE_ANON_KEYallow: 'user'SUPABASE_ANON_KEYallow: 'public'SUPABASE_SERVICE_ROLE_KEYreq.headers.get('apikey') === serviceRoleKeyallow: 'secret'ctx.supabaseAdmindocs/@supabase/serverdocs/node_modules/@supabase/server/docs/| Question | Doc file |
|---|---|
| How do I create a basic endpoint? | |
| What auth modes are available? Array syntax? Named keys? | |
| How do I use this with Hono? | |
| How do I use low-level primitives for custom flows? | |
| How do environment variables work across runtimes? | |
| How do I handle errors? What codes exist? | |
| How do I get typed database queries? | |
| How do I use this in Next.js, Nuxt, SvelteKit, or Remix? | |
| What's the complete API surface? | |
| What security decisions does this package make? | |