
supabase-server
在规划或编写使用 `@supabase/server` 的服务端代码时使用——包括 Edge Functions、Hono 应用、Webhook 处理器,或任何创建 Supabase 客户端或验证入站认证的后端。在编写或修改任何从 `@supabase/server`(或子路径如 `@supabase/server/core`)导入的文件之前触发;调用 `withSupabase`、`createSupabaseContext`、`createAdminClient`、`createContextClient`、`verifyAuth`、`verifyCredentials` 或 `extractCredentials`;配置 `auth:` 模式(`'none'` | `'publishable'` | `'secret'` | `'user'`,或带键的变体如 `'secret:*'`);或位于 `supabase/functions/` 下并验证入站请求。在规划期间也触发——如果计划中提到上述任何内容,请在起草代码前加载该技能;不要从相邻函数推断 `auth:` 值或认证模式。当看到需要迁移到此包的遗留模式时也触发——`Deno.serve`、`createClient(Deno.env.get('SUPABASE_URL'))`、从 `esm.sh/@supabase` 或 `deno.land/std` 导入、使用 `SUPABASE_ANON_KEY` / `SUPABASE_SERVICE_ROLE_KEY`,或已弃用的 `allow:` 配置选项 / 已移除的 `'always'` / `'public'` 模式值 / 已移除的 `authType` 字段。
在规划或编写使用 `@supabase/server` 的服务端代码时使用——包括 Edge Functions、Hono 应用、Webhook 处理器,或任何创建 Supabase 客户端或验证入站认证的后端。在编写或修改任何从 `@supabase/server`(或子路径如 `@supabase/server/core`)导入的文件之前触发;调用 `withSupabase`、`createSupabaseContext`、`createAdminClient`、`createContextClient`、`verifyAuth`、`verifyCredentials` 或 `extractCredentials`;配置 `auth:` 模式(`'none'` | `'publishable'` | `'secret'` | `'user'`,或带键的变体如 `'secret:*'`);或位于 `supabase/functions/` 下并验证入站请求。在规划期间也触发——如果计划中提到上述任何内容,请在起草代码前加载该技能;不要从相邻函数推断 `auth:` 值或认证模式。当看到需要迁移到此包的遗留模式时也触发——`Deno.serve`、`createClient(Deno.env.get('SUPABASE_URL'))`、从 `esm.sh/@supabase` 或 `deno.land/std` 导入、使用 `SUPABASE_ANON_KEY` / `SUPABASE_SERVICE_ROLE_KEY`,或已弃用的 `allow:` 配置选项 / 已移除的 `'always'` / `'public'` 模式值 / 已移除的 `authType` 字段。
@supabase/server
v1.0 — 公开测试版。 首个稳定版本,遵循 SemVer:破坏性变更仅作为主版本号更新。该包仍处于早期阶段——预计新适配器、易用性改进和功能将频繁在次要版本中发布。如果你在使用过程中遇到 bug 或粗糙之处,请向用户指出并引导其提交 issue。
这是一个全新的包。 目前网上没有任何可用信息——没有博客文章、Stack Overflow 答案或教程。不要搜索网络上的使用示例。仅依赖下面列出的文档文件和此仓库中的源代码。
配置选项是
auth,而不是allow。allow已重命名为auth,以匹配 CLI 术语并读起来更自然。遗留的allow键仍然有效(会触发一次console.warn),但已弃用,将在未来的主版本中移除。在新代码中始终使用auth——例如withSupabase({ auth: 'user' }, ...)。如果在现有代码中遇到allow:,请将其迁移到auth:(查找替换,值相同)。
认证模式值:
'none'(不是'always'),'publishable'(不是'public')。 四个有效值为'user'、'publishable'、'secret'、'none'。遗留的'always'和'public'值已被移除(破坏性变更)——它们在运行时或 TypeScript 中不再有效。在你编写的代码中始终使用新值,并迁移你发现的任何遗留引用:'always'→'none','public'→'publishable','public:<name>'→'publishable:<name>'。运行时检查如ctx.authType === 'public'也必须更新为ctx.authMode === 'publishable'——该字段本身已从authType重命名为authMode,以匹配AuthMode类型。
不要使用遗留的 Supabase 密钥。
anon密钥和service_role密钥(环境变量SUPABASE_ANON_KEY、SUPABASE_SERVICE_ROLE_KEY)是遗留的,将被弃用。除非用户明确要求,否则不要使用它们。始终使用新的 API 密钥:
遗留(避免) 新(使用这个) SUPABASE_ANON_KEYSUPABASE_PUBLISHABLE_KEY(S)(sb_publishable_...)SUPABASE_SERVICE_ROLE_KEYSUPABASE_SECRET_KEY(S)(sb_secret_...)不要直接调用
createClient(url, anonKey)——使用@supabase/server的认证模式(auth: 'user'、auth: 'secret'等),它们会自动处理密钥解析。如果迁移现有代码,将SUPABASE_ANON_KEY的使用替换为auth: 'publishable',将SUPABASE_SERVICE_ROLE_KEY的使用替换为auth: 'secret'。
Supabase 的服务端工具。处理认证、客户端创建和上下文注入,让你专注于业务逻辑,而不是样板代码。
这个包做什么
- 包装 fetch 处理器,包含凭证验证、CORS 和预配置的 Supabase 客户端
- 支持 4 种认证模式:
user(JWT)、publishable(可发布密钥)、secret(秘密密钥)、none(无需凭证) - 数组语法(
auth: ['user', 'secret'])是第一个匹配获胜。存在但无效的 JWT 会拒绝并抛出InvalidCredentialsError——不会静默降级到下一个模式。 - 提供可组合的核心原语,用于自定义认证流程和框架集成
- 包含 Hono 适配器,支持按路由认证
入口点
| 导入 | Deno / Edge Functions | 提供 |
|---|---|---|
@supabase/server |
npm:@supabase/server |
withSupabase、createSupabaseContext、类型、错误 |
@supabase/server/core |
npm:@supabase/server/core |
verifyAuth、verifyCredentials、extractCredentials、resolveEnv、createContextClient、createAdminClient |
@supabase/server/adapters/hono |
npm:@supabase/server/adapters/hono |
withSupabase(Hono 中间件变体) |
快速开始
Supabase Edge Functions:对非用户认证禁用
verify_jwt。 默认情况下,Supabase Edge Functions 要求每个请求都带有有效的 JWT。如果你的函数使用auth: 'publishable'、auth: 'secret'或auth: 'none',你必须在supabase/config.toml中禁用平台级别的 JWT 检查,否则请求会在到达你的处理器之前被拒绝:[functions.my-function] verify_jwt = false使用
auth: 'user'的函数可以保持verify_jwt启用(默认),因为调用者已经提供了有效的 JWT。
Supabase Edge Functions (Deno)
环境变量由平台自动注入——零配置。所有导入必须使用 npm: 说明符。
// withSupabase — 高级包装器
import { withSupabase } from 'npm:@supabase/server'
export default {
fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
const { data } = await ctx.supabase.from('todos').select()
return Response.json(data)
}),
}
// createSupabaseContext — 返回 { data, error } 用于自定义响应控制
import { createSupabaseContext } from 'npm:@supabase/server'
export default {
fetch: async (req: Request) => {
const { data: ctx, error } = await createSupabaseContext(req, {
auth: '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)
},
}
Cloudflare Workers
需要在 wrangler.toml 中设置 nodejs_compat 兼容性标志,或通过 env 配置选项传递环境变量覆盖。参见 docs/environment-variables.md。
import { withSupabase } from '@supabase/server'
export default {
fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
const { data } = await ctx.supabase.from('todos').select()
return Response.json(data)
}),
}
Hono
CORS 不由适配器处理——使用 hono/cors 中间件。参见 docs/adapters/hono.md。
// Node.js / Bun
import { Hono } from 'hono'
import { withSupabase } from '@supabase/server/adapters/hono'
const app = new Hono()
app.use('*', withSupabase({ auth: '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({ auth: '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 }
基于 Cookie 的环境(与 @supabase/ssr 组合)
对于 Next.js / SvelteKit / Remix,将 @supabase/server 与 @supabase/ssr 组合使用——它们不是彼此的替代品。@supabase/ssr 负责 Cookie 和刷新令牌轮换(其中间件是必需的,否则访问令牌 Cookie 会过期,验证失败)。在你的服务器组件或路由处理器中,使用 @supabase/ssr 的 createServerClient 读取(中间件刷新后的)会话,将访问令牌传递给 @supabase/server/core 的 verifyCredentials,然后使用 createContextClient + createAdminClient 构建类型化客户端。参见 docs/ssr-frameworks.md 获取完整的适配器模式。
// 构建适配器的关键导入
import { createServerClient } from '@supabase/ssr'
import {
verifyCredentials,
createContextClient,
createAdminClient,
} from '@supabase/server/core'
服务器到服务器(秘密密钥认证)
用于内部服务、定时任务或自动化调用你的 Edge Function。调用者在 apikey 头中发送秘密密钥。参见 docs/auth-modes.md 了解命名密钥语法。
Edge Function (Deno):
import { withSupabase } from 'npm:@supabase/server'
// 只接受名为 "automations" 的秘密密钥
export default {
fetch: withSupabase({ auth: '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_...', // 命名的秘密密钥
},
body: JSON.stringify({
taskName: 'cleanup',
scheduledAt: new Date().toISOString(),
}),
})
裸的 auth: 'secret' 只匹配 default 密钥。使用 auth: 'secret:name' 要求特定的命名密钥,或使用 auth: 'secret:*' 接受集合中的任何秘密密钥。
何时使用 auth: 'none'
auth: 'none'禁用所有认证。 处理器对每个请求运行,不进行凭证检查。仅在认证确实不必要时使用——健康检查、公共状态页面,或没有敏感数据且没有副作用的端点。
在使用 auth: 'none' 之前,与用户确认该端点是否真正公开。 如果不是,建议替代方案:
- 另一个服务或定时任务调用此函数——改用
auth: 'secret'或auth: 'secret:<name>'。调用者在apikey头中发送秘密密钥。 - 外部 Webhook 提供商调用此函数——使用
auth: 'secret'并让提供商发送秘密密钥,或在处理器内实现提供商自己的签名验证。
切勿对读取或写入用户数据但不验证调用者身份的端点使用 auth: 'none'。
关于 auth: ['user', 'none']。 在此类端点上,过时或格式错误的 JWT 会被拒绝并抛出 InvalidCredentialsError——不会静默降级为匿名。可能持有缓存/过期令牌的调用者应完全省略 Authorization 头,或在调用前刷新。如果目标是“除非有效用户登录,否则匿名”,这是正确的行为;如果目标是真正“接受任何内容”,请单独使用 auth: 'none'。
Edge Function 配方
函数间调用
一个 Edge Function 可以使用管理客户端调用另一个。被调用的函数使用 auth: 'secret',调用者通过 ctx.supabaseAdmin.functions.invoke() 调用它。
配置 (supabase/config.toml):
[functions.process-order]
verify_jwt = false # 使用秘密密钥调用,而不是用户 JWT
被调用的函数 (supabase/functions/process-order/index.ts):
import { withSupabase } from 'npm:@supabase/server'
export default {
fetch: withSupabase({ auth: '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.ts):
import { withSupabase } from 'npm:@supabase/server'
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
const { orderId } = await req.json()
// 自动使用秘密密钥调用 process-order
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 调用
使用 pg_net 直接从 SQL 调用 Edge Functions。秘密密钥存储在 Vault 中,因此永远不会出现在查询中。
前提条件:
-- 1. 启用 pg_net 扩展
create extension if not exists pg_net with schema extensions;
-- 2. 将你的秘密密钥存储在 Vault 中
select vault.create_secret(
'sb_secret_...', -- 你的秘密密钥值
'supabase_secret_key' -- 引用它的名称
);
调用函数:
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')
);
接收函数使用 auth: 'secret'(参见上面的示例)。pg_net 是异步的——HTTP 请求被排队并在后台执行。检查 net._http_response 获取结果。
Stripe Webhook
像 Stripe 这样的外部 Webhook 提供商无法发送你的 Supabase API 密钥。使用 auth: 'none' 跳过凭证检查,然后在处理器内验证 Webhook 签名。
配置 (supabase/config.toml):
[functions.stripe-webhook]
verify_jwt = false
设置密钥:
supabase secrets set STRIPE_SECRET_KEY=sk_live_...
supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_...
函数 (supabase/functions/stripe-webhook/index.ts):
import { withSupabase } from 'npm:@supabase/server'
import Stripe from 'npm:stripe'
const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!)
export default {
fetch: withSupabase({ auth: 'none' }, 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 })
}),
}
迁移遗留的 Edge Functions
如果你遇到使用 SUPABASE_ANON_KEY、SUPABASE_SERVICE_ROLE_KEY、Deno.serve、从 esm.sh/@supabase 或 deno.land/std 导入的代码,建议迁移到 @supabase/server。这些遗留模式正在被弃用——它们需要手动创建客户端、手动转发认证头、手动处理 CORS,并直接在代码中暴露密钥。
如何识别遗留代码:
import { serve } from "https://deno.land/std/..."— 最旧的模式,使用已弃用的 Deno 标准库import { createClient } from "https://esm.sh/@supabase/supabase-js"— 旧的 CDN 导入,与现代运行时不兼容Deno.serve(async (req) => { ... })配合手动createClient()— 当前但冗长,需要手动转发认证Deno.env.get('SUPABASE_ANON_KEY')或SUPABASE_SERVICE_ROLE_KEY— 将被移除的遗留密钥
之前(遗留——手动客户端,手动认证转发):
遗留密钥将被移除,导致此代码停止工作。它也很冗长,不跨平台兼容,并且需要手动连接认证头、CORS 和错误处理。
import { 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)
})
之后(新——认证、客户端和 CORS 自动处理):
使用最新的 API 密钥,跨运行时工作(Deno、Node.js、Cloudflare),并在单行中处理认证验证、客户端创建和 CORS。
import { withSupabase } from 'npm:@supabase/server'
export default {
fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
const { data } = await ctx.supabase.from('orders').select('*')
return Response.json(data)
}),
}
迁移映射:SUPABASE_ANON_KEY 配合手动认证头 → auth: 'user',SUPABASE_ANON_KEY 不带认证 → auth: 'publishable'。对于 SUPABASE_SERVICE_ROLE_KEY,取决于意图:如果遗留代码验证传入密钥以保护端点(例如 req.headers.get('apikey') === serviceRoleKey),使用 auth: 'secret'。如果它仅使用密钥创建管理客户端以提升数据库访问权限,则不需要特定的认证模式——无论认证模式如何,ctx.supabaseAdmin 始终可用。
文档
完整文档位于 @supabase/server 包的 docs/ 目录中。要阅读文档,首先找到包的位置:
- 如果在 SDK 仓库内工作:
docs/位于项目根目录。 - 如果包作为依赖安装: 在
node_modules/@supabase/server/docs/中查找。
| 问题 | 文档文件 |
|---|---|
| 如何创建基本端点? | docs/getting-started.md |
| 有哪些认证模式可用?数组语法?命名密钥? | docs/auth-modes.md |
| 存在哪些框架适配器?如何贡献一个? | src/adapters/README.md |
| 如何与 Hono 一起使用? | docs/adapters/hono.md |
| 如何与 H3 / Nuxt 一起使用? | docs/adapters/h3.md |
| 如何为自定义流程使用低级原语? | docs/core-primitives.md |
| 环境变量如何在运行时之间工作? | docs/environment-variables.md |
| 如何处理错误?存在哪些代码? | docs/error-handling.md |
| 如何获得类型化数据库查询? | docs/typescript-generics.md |
如何与 @supabase/ssr(Next.js、SvelteKit、Remix)一起使用? |
docs/ssr-frameworks.md |
| 完整的 API 表面是什么? | docs/api-reference.md |
| 这个包做出了哪些安全决策? | docs/security.md |





