## TL;DR aligns the remaining Phase 2 Edge Functions docs snippets with `@supabase/server` ## Whats Fixed? updated outdated imports and version references, and refreshed JSON examples to use Response.json() where it makes sense. left non-JSON responses as is where the integration or format actually needs them ## Ref: - towards COM-269 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Updated numerous Edge Function guides and examples to use modern `npm:`/`jsr:` import specifiers instead of legacy Deno URL imports. * Standardized success and error responses to return JSON consistently (using `Response.json()` and equivalent helpers) and added/clarified appropriate HTTP status codes. * Improved example error payload shapes in several guides for clearer, structured failures. * **Chores** * Refreshed version ranges in documentation and examples across SDKs and client libraries. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
7.2 KiB
description, alwaysApply
| description | alwaysApply |
|---|---|
| Coding rules for Supabase Edge Functions | false |
Writing Supabase Edge Functions
You're an expert in writing TypeScript and Deno JavaScript runtime. Generate high-quality Supabase Edge Functions that adhere to the following best practices:
Guidelines
-
Try to use Web APIs and Deno's core APIs instead of external dependencies (eg: use fetch instead of Axios, use WebSockets API instead of node-ws)
-
If you are reusing utility methods between Edge Functions, add them to
supabase/functions/_sharedand import using a relative path. Do NOT have cross dependencies between Edge Functions. -
Do NOT use bare specifiers when importing dependencies. If you need to use an external dependency, make sure it's prefixed with either
npm:orjsr:. For example,@supabase/supabase-jsshould be written asnpm:@supabase/supabase-js. -
For external imports, always define a version. For example,
npm:expressshould be written asnpm:express@4.18.2. -
For external dependencies, importing via
npm:andjsr:is preferred. Minimize the use of imports fromdeno.land/x,esm.shandunpkg.com. If you have a package from one of those CDNs, you can replace the CDN hostname with thenpm:specifier. -
You can also use Node built-in APIs. You will need to import them using the
node:specifier. For example, to import Node process:import process from "node:process". Use Node APIs when you find gaps in Deno APIs. -
Do NOT use
import { serve } from "https://deno.land/std@0.168.0/http/server.ts", and do NOT useDeno.serve. Instead, export a default object with afetchhandler:export default { fetch: async (req: Request) => { return Response.json({ message: 'Hello world' }) }, }This is the request handler contract for Supabase Edge Functions, and it also runs unchanged on Cloudflare Workers and Bun. Always wrap this handler with
withSupabaseto secure and configure it (see guideline 8). -
Write your handler with
withSupabasefromnpm:@supabase/server@^1. One wrapper gives you:- Authentication: verifies the caller's credentials.
- Authorization: only lets through callers that match the
authmode you declare. - Pre-configured clients on
ctx:ctx.supabase(scoped to the caller's RLS) andctx.supabaseAdmin(bypasses RLS). - CORS handling, including preflight requests.
Your one decision is the
authmode:import { withSupabase } from 'npm:@supabase/server@^1' export default { fetch: withSupabase({ auth: 'user' }, async (req, ctx) => { const { data, error } = await ctx.supabase.from('countries').select('*') if (error) throw error return Response.json({ data }) }), }Choose the
authmode by who calls the function:Caller authverify_jwtClient Signed-in user (JWT on Authorization)'user'true(default, omit)ctx.supabase(RLS-scoped)Cron, worker, pg_net, or another function'secret'falsectx.supabaseAdmin(bypasses RLS)Public client 'publishable'falsectx.supabasePublic endpoint or external webhook (verify in code) 'none'falsectx.supabaseAdminif neededFor any mode other than
'user', setverify_jwt = falsefor that function insupabase/config.toml:[functions.my-function] verify_jwt = falsectx.userClaimsholds the verified user identity. To accept only one named key, useauth: 'secret:<name>'orauth: 'publishable:<name>'. For a public endpoint, useauth: 'none'; you still get CORS handling andctx.supabaseAdmin. -
The following environment variables (ie. secrets) are pre-populated in both local and hosted Supabase environments. Users don't need to manually set them:
- SUPABASE_URL
- SUPABASE_PUBLISHABLE_KEYS
- SUPABASE_SECRET_KEYS
- SUPABASE_DB_URL
withSupabasereads these for you, so prefer it over reading keys by hand. If you must read a key without the SDK, parse the JSON map and index it by name:const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!), thenSUPABASE_SECRET_KEYS['default']for the default secret key. The publishable keys work the same way throughSUPABASE_PUBLISHABLE_KEYS. -
To set other environment variables (ie. secrets) users can put them in an env file and run
supabase secrets set --env-file path/to/env-file. -
A single Edge Function can handle multiple routes. It is recommended to use a library like Hono or Express to handle the routes as it's easier for developers to understand and maintain. Each route must be prefixed with
/function-nameso they are routed correctly. For per-route Supabase auth with Hono, use the adapter fromnpm:@supabase/server@^1/adapters/hono. -
File write operations are ONLY permitted on the
/tmpdirectory. You can use either Deno or Node File APIs. -
Use the
EdgeRuntime.waitUntil(promise)static method to run long-running tasks in the background without blocking the response to a request. Do NOT assume it is available in the request / execution context.
Example Templates
Recommended: Edge Function with withSupabase
import { withSupabase } from 'npm:@supabase/server@^1'
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
const { data, error } = await ctx.supabase.from('countries').select('*')
if (error) throw error
return Response.json({ data })
}),
}
Simple Hello World Function
interface reqPayload {
name: string
}
console.info('server started')
export default {
fetch: async (req: Request) => {
const { name }: reqPayload = await req.json()
const data = {
message: `Hello ${name} from foo!`,
}
return Response.json(data)
},
}
Example Function using Node built-in API
import { randomBytes } from 'node:crypto'
import { createServer } from 'node:http'
import process from 'node:process'
const generateRandomString = (length) => {
const buffer = randomBytes(length)
return buffer.toString('hex')
}
const randomString = generateRandomString(10)
console.log(randomString)
const server = createServer((req, res) => {
const message = `Hello`
res.end(message)
})
server.listen(9999)
Using npm packages in Functions
import express from 'npm:express@^5'
const app = express()
app.get(/(.*)/, (req, res) => {
res.send('Welcome to Supabase')
})
app.listen(8000)
Generate embeddings using built-in @Supabase.ai API
const model = new Supabase.ai.Session('gte-small')
export default {
fetch: async (req: Request) => {
const params = new URL(req.url).searchParams
const input = params.get('text')
const output = await model.run(input, { mean_pool: true, normalize: true })
return Response.json(output)
},
}