AI Prompt: Writing Supabase Edge Functions
How to use#
Copy the prompt to a file in your repo.
Use the "include file" feature from your AI tool to include the prompt when chatting with your AI assistant. For example, with GitHub Copilot, use #<filename>, in Cursor, use @Files, and in Zed, use /file.
Prompt#
1# Writing Supabase Edge Functions23You're an expert in writing TypeScript and Deno JavaScript runtime. Generate **high-quality Supabase Edge Functions** that adhere to the following best practices:45## Guidelines671. 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)82. If you are reusing utility methods between Edge Functions, add them to `supabase/functions/_shared` and import using a relative path. Do NOT have cross dependencies between Edge Functions.93. Do NOT use bare specifiers when importing dependencies. If you need to use an external dependency, make sure it's prefixed with either `npm:` or `jsr:`. For example, `@supabase/supabase-js` should be written as `npm:@supabase/supabase-js`.104. For external imports, always define a version. For example, `npm:express` should be written as `npm:express@4.18.2`.115. For external dependencies, importing via `npm:` and `jsr:` is preferred. Minimize the use of imports from `deno.land/x`, `esm.sh` and `unpkg.com`. If you have a package from one of those CDNs, you can replace the CDN hostname with the `npm:` specifier.126. 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.137. Do NOT use `import { serve } from "https://deno.land/std@0.168.0/http/server.ts"`, and do NOT use `Deno.serve`. Instead, export a default object with a `fetch` handler:1415 ```ts16 export default {17 fetch: async (req: Request) => {18 return Response.json({ message: 'Hello world' })19 },20 }21 ```2223 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 `withSupabase` to secure and configure it (see guideline 8).24258. Write your handler with `withSupabase` from `npm:@supabase/server@^1`. One wrapper gives you:26 - Authentication: verifies the caller's credentials.27 - Authorization: only lets through callers that match the `auth` mode you declare.28 - Pre-configured clients on `ctx`: `ctx.supabase` (scoped to the caller's RLS) and `ctx.supabaseAdmin` (bypasses RLS).29 - CORS handling, including preflight requests.3031 Your one decision is the `auth` mode:3233 ```ts34 import { withSupabase } from 'npm:@supabase/server@^1'3536 export default {37 fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {38 const { data, error } = await ctx.supabase.from('countries').select('*')39 if (error) throw error40 return Response.json({ data })41 }),42 }43 ```4445 Choose the `auth` mode by who calls the function:4647 | Caller | `auth` | `verify_jwt` | Client |48 | ---------------------------------------------------- | --------------- | ---------------------- | ---------------------------------- |49 | Signed-in user (JWT on `Authorization`) | `'user'` | `true` (default, omit) | `ctx.supabase` (RLS-scoped) |50 | Cron, worker, `pg_net`, or another function | `'secret'` | `false` | `ctx.supabaseAdmin` (bypasses RLS) |51 | Public client | `'publishable'` | `false` | `ctx.supabase` |52 | Public endpoint or external webhook (verify in code) | `'none'` | `false` | `ctx.supabaseAdmin` if needed |5354 For any mode other than `'user'`, set `verify_jwt = false` for that function in `supabase/config.toml`:5556 ```toml57 [functions.my-function]58 verify_jwt = false59 ```6061 `ctx.userClaims` holds the verified user identity. To accept only one named key, use `auth: 'secret:<name>'` or `auth: 'publishable:<name>'`. For a public endpoint, use `auth: 'none'`; you still get CORS handling and `ctx.supabaseAdmin`.62639. The following environment variables (ie. secrets) are pre-populated in both local and hosted Supabase environments. Users don't need to manually set them:64 - SUPABASE_URL65 - SUPABASE_PUBLISHABLE_KEYS66 - SUPABASE_SECRET_KEYS67 - SUPABASE_DB_URL6869 `withSupabase` reads 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')!)`, then `SUPABASE_SECRET_KEYS['default']` for the default secret key. The publishable keys work the same way through `SUPABASE_PUBLISHABLE_KEYS`.707110. 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`.7211. 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-name` so they are routed correctly. For per-route Supabase auth with Hono, use the adapter from `npm:@supabase/server@^1/adapters/hono`.7312. File write operations are ONLY permitted on the `/tmp` directory. You can use either Deno or Node File APIs.7413. 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.7576## Example Templates7778### Recommended: Edge Function with `withSupabase`7980```ts81import { withSupabase } from 'npm:@supabase/server@^1'8283export default {84 fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {85 const { data, error } = await ctx.supabase.from('countries').select('*')86 if (error) throw error87 return Response.json({ data })88 }),89}90```9192### Simple Hello World Function9394```ts95interface reqPayload {96 name: string97}9899console.info('server started')100101export default {102 fetch: async (req: Request) => {103 const { name }: reqPayload = await req.json()104 const data = {105 message: `Hello ${name} from foo!`,106 }107108 return Response.json(data)109 },110}111```112113### Example Function using Node built-in API114115```ts116import { randomBytes } from 'node:crypto'117import { createServer } from 'node:http'118import process from 'node:process'119120const generateRandomString = (length) => {121 const buffer = randomBytes(length)122 return buffer.toString('hex')123}124125const randomString = generateRandomString(10)126console.log(randomString)127128const server = createServer((req, res) => {129 const message = `Hello`130 res.end(message)131})132133server.listen(9999)134```135136### Using npm packages in Functions137138```ts139import express from 'npm:express@4.18.2'140141const app = express()142143app.get(/(.*)/, (req, res) => {144 res.send('Welcome to Supabase')145})146147app.listen(8000)148```149150### Generate embeddings using built-in @Supabase.ai API151152```ts153const model = new Supabase.ai.Session('gte-small')154155export default {156 fetch: async (req: Request) => {157 const params = new URL(req.url).searchParams158 const input = params.get('text')159 const output = await model.run(input, { mean_pool: true, normalize: true })160 return Response.json(output)161 },162}163```