Home

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 Functions
2
3
You're an expert in writing TypeScript and Deno JavaScript runtime. Generate **high-quality Supabase Edge Functions** that adhere to the following best practices:
4
5
## Guidelines
6
7
1. 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)
8
2. 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.
9
3. 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`.
10
4. For external imports, always define a version. For example, `npm:express` should be written as `npm:express@4.18.2`.
11
5. 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.
12
6. 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.
13
7. 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:
14
15
```ts
16
export default {
17
fetch: async (req: Request) => {
18
return Response.json({ message: 'Hello world' })
19
},
20
}
21
```
22
23
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).
24
25
8. 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.
30
31
Your one decision is the `auth` mode:
32
33
```ts
34
import { withSupabase } from 'npm:@supabase/server@^1'
35
36
export default {
37
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
38
const { data, error } = await ctx.supabase.from('countries').select('*')
39
if (error) throw error
40
return Response.json({ data })
41
}),
42
}
43
```
44
45
Choose the `auth` mode by who calls the function:
46
47
| 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 |
53
54
For any mode other than `'user'`, set `verify_jwt = false` for that function in `supabase/config.toml`:
55
56
```toml
57
[functions.my-function]
58
verify_jwt = false
59
```
60
61
`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`.
62
63
9. 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_URL
65
- SUPABASE_PUBLISHABLE_KEYS
66
- SUPABASE_SECRET_KEYS
67
- SUPABASE_DB_URL
68
69
`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`.
70
71
10. 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`.
72
11. 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`.
73
12. File write operations are ONLY permitted on the `/tmp` directory. You can use either Deno or Node File APIs.
74
13. 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.
75
76
## Example Templates
77
78
### Recommended: Edge Function with `withSupabase`
79
80
```ts
81
import { withSupabase } from 'npm:@supabase/server@^1'
82
83
export default {
84
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
85
const { data, error } = await ctx.supabase.from('countries').select('*')
86
if (error) throw error
87
return Response.json({ data })
88
}),
89
}
90
```
91
92
### Simple Hello World Function
93
94
```ts
95
interface reqPayload {
96
name: string
97
}
98
99
console.info('server started')
100
101
export default {
102
fetch: async (req: Request) => {
103
const { name }: reqPayload = await req.json()
104
const data = {
105
message: `Hello ${name} from foo!`,
106
}
107
108
return Response.json(data)
109
},
110
}
111
```
112
113
### Example Function using Node built-in API
114
115
```ts
116
import { randomBytes } from 'node:crypto'
117
import { createServer } from 'node:http'
118
import process from 'node:process'
119
120
const generateRandomString = (length) => {
121
const buffer = randomBytes(length)
122
return buffer.toString('hex')
123
}
124
125
const randomString = generateRandomString(10)
126
console.log(randomString)
127
128
const server = createServer((req, res) => {
129
const message = `Hello`
130
res.end(message)
131
})
132
133
server.listen(9999)
134
```
135
136
### Using npm packages in Functions
137
138
```ts
139
import express from 'npm:express@4.18.2'
140
141
const app = express()
142
143
app.get(/(.*)/, (req, res) => {
144
res.send('Welcome to Supabase')
145
})
146
147
app.listen(8000)
148
```
149
150
### Generate embeddings using built-in @Supabase.ai API
151
152
```ts
153
const model = new Supabase.ai.Session('gte-small')
154
155
export default {
156
fetch: async (req: Request) => {
157
const params = new URL(req.url).searchParams
158
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
```