fix(examples): read new API keys directly from parsed SUPABASE_SECRET_KEYS (#46604)

## Problem

Edge Function examples that use the new publishable/secret API keys read
them with a double lookup:

```ts
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const secretKey = Deno.env.get(SUPABASE_SECRET_KEYS['default']) // ❌ returns undefined
```

`SUPABASE_SECRET_KEYS` / `SUPABASE_PUBLISHABLE_KEYS` are a JSON object
that maps a key name to the **actual key value** (e.g.
`{"default":"sb_secret_..."}`), confirmed by:
- the self-hosted injection in `docker/docker-compose.yml`
(`SUPABASE_SECRET_KEYS: "{\"default\":\"${SUPABASE_SECRET_KEY:-}\"}"`)
- the `@supabase/server` SDK README

So `SUPABASE_SECRET_KEYS['default']` is already the key. Wrapping it in
another `Deno.env.get(...)` looks up an env var named `sb_secret_...`,
which doesn't exist, so the value is `undefined` and the examples fail
at runtime.

## Fix

Unwrap the outer `Deno.env.get(...)` so the key is read directly:

```ts
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const secretKey = SUPABASE_SECRET_KEYS['default'] // ✅
```

Applied across 23 files (example functions, the
`examples/prompts/edge-functions.md` codegen guidance, and two docs
guides). The correct `JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)`
declaration line is untouched. The generated `apps/docs/examples/` copy
regenerates from `examples/` at build time.

## Notes

- Docs context:
[#46600](https://github.com/supabase/supabase/pull/46600), which
documents the same key model.
- Follow-up (not in this PR): a few examples send the secret key on the
`Authorization: Bearer` header, which the new keys reject. Worth a
separate audit.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Clarified examples and guides for correctly reading parsed Supabase
secret and publishable key maps.

* **Examples**
* Standardized credential usage across Edge Functions and samples so
Supabase clients consistently receive keys from the parsed key maps
rather than indirect lookups.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Chris Chinchilla <chris.ward@supabase.io>
This commit is contained in:
Tomás PozoandChris Chinchilla authored and GitHub committed 2026-06-03 11:05:20 -05:00
1 parent c582d3749c
commit e3be344f5c
23 files changed
+23 -35

No files matched your search

@@ -65,7 +65,7 @@ Deno.serve(async (req) => {
// Supabase API URL - env var exported by default when deployed.
Deno.env.get('SUPABASE_URL') ?? '',
// Supabase API SECRET KEY - env var exported by default when deployed.
Deno.env.get(SUPABASE_SECRET_KEYS['default']) ?? ''
SUPABASE_SECRET_KEYS['default'] ?? ''
)
// Construct image url from storage
@@ -120,7 +120,7 @@ const elevenLabsClient = new ElevenLabsClient({
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const supabase = createClient(
Deno.env.get('SUPABASE_URL') || '',
Deno.env.get(SUPABASE_SECRET_KEYS['default']) || ''
SUPABASE_SECRET_KEYS['default'] || ''
)
async function scribe({
@@ -58,7 +58,7 @@ Deno.serve(async (req) => {
// Supabase API URL - env var exported by default.
Deno.env.get('SUPABASE_URL')!,
// Supabase API SECRET KEY - env var exported by default.
Deno.env.get(SUPABASE_SECRET_KEYS['default'])!
SUPABASE_SECRET_KEYS['default']!
)
const { data: upload, error: uploadError } = await supabaseClient.storage
@@ -15,7 +15,7 @@ const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const supabase = createClient<Database>(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get(SUPABASE_SECRET_KEYS['default'])!
SUPABASE_SECRET_KEYS['default']!
)
const model = new Supabase.ai.Session('gte-small')
@@ -7,7 +7,7 @@ const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const supabase = createClient<Database>(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get(SUPABASE_SECRET_KEYS['default'])!
SUPABASE_SECRET_KEYS['default']!
)
const model = new Supabase.ai.Session('gte-small')
@@ -4,10 +4,7 @@ import OpenAI from 'https://deno.land/x/openai@v4.68.2/mod.ts'
const client = new OpenAI({ apiKey: Deno.env.get('OPENAI_API_KEY')! })
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get(SUPABASE_SECRET_KEYS['default'])!
)
const supabase = createClient(Deno.env.get('SUPABASE_URL')!, SUPABASE_SECRET_KEYS['default']!)
type StorageFileApi = ReturnType<typeof supabase.storage.from>
type StorageUploadPromise = ReturnType<StorageFileApi['upload']>
@@ -19,7 +19,7 @@ const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const supabase = createClient(
Deno.env.get('SUPABASE_URL') || '',
Deno.env.get(SUPABASE_SECRET_KEYS['default']) || ''
SUPABASE_SECRET_KEYS['default'] || ''
)
async function scribe({
@@ -5,10 +5,7 @@ import { ElevenLabsClient } from 'npm:elevenlabs@1.52.0'
import * as hash from 'npm:object-hash'
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get(SUPABASE_SECRET_KEYS['default'])!
)
const supabase = createClient(Deno.env.get('SUPABASE_URL')!, SUPABASE_SECRET_KEYS['default']!)
const client = new ElevenLabsClient({
apiKey: Deno.env.get('ELEVENLABS_API_KEY'),
@@ -29,7 +29,7 @@ app.use(async (ctx) => {
// Supabase API URL - env var exported by default.
Deno.env.get('SUPABASE_URL')!,
// Supabase publishable key - env var exported by default.
Deno.env.get(SUPABASE_PUBLISHABLE_KEYS['default'])!
SUPABASE_PUBLISHABLE_KEYS['default']!
)
//upload image to Storage
@@ -61,7 +61,7 @@ Deno.serve(async (req) => {
// Supabase API URL - env var exported by default when deployed.
Deno.env.get('SUPABASE_URL') ?? '',
// Supabase API SECRET KEY - env var exported by default when deployed.
Deno.env.get(SUPABASE_SECRET_KEYS['default']) ?? ''
SUPABASE_SECRET_KEYS['default'] ?? ''
)
// Submit email to draw
const { error } = await supabaseAdminClient.from('get-tshirt-competition-2').upsert(
@@ -23,7 +23,7 @@ Deno.serve(async (req) => {
// Supabase API URL - env var exported by default when deployed.
Deno.env.get('SUPABASE_URL') ?? '',
// Supabase API SECRET KEY - env var exported by default when deployed.
Deno.env.get(SUPABASE_SECRET_KEYS['default']) ?? ''
SUPABASE_SECRET_KEYS['default'] ?? ''
)
// Construct image url from storage
@@ -201,7 +201,7 @@ export async function handler(req: Request) {
// Supabase API URL - env var exported by default when deployed.
Deno.env.get('SUPABASE_URL') ?? '',
// Supabase API SECRET KEY - env var exported by default when deployed.
Deno.env.get(SUPABASE_SECRET_KEYS['default']) ?? ''
SUPABASE_SECRET_KEYS['default'] ?? ''
)
// Upload image to storage.
@@ -85,7 +85,7 @@ Deno.serve(async (req) => {
// Upload the generated image to Supabase Storage
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') || '',
Deno.env.get(SUPABASE_SECRET_KEYS['default']) || ''
SUPABASE_SECRET_KEYS['default'] || ''
)
// Create a unique identifier for this generation
@@ -25,7 +25,7 @@ Deno.serve(async (req) => {
// Supabase API URL - env var exported by default.
Deno.env.get('SUPABASE_URL') ?? '',
// Supabase API publishable key - env var exported by default.
Deno.env.get(SUPABASE_PUBLISHABLE_KEYS['default']) ?? '',
SUPABASE_PUBLISHABLE_KEYS['default'] ?? '',
// Create client with Auth context of the user that called the function.
// This way your row-level-security (RLS) policies are applied.
{
@@ -82,7 +82,7 @@ Deno.serve(async (req) => {
// Supabase API URL - env var exported by default.
Deno.env.get('SUPABASE_URL') ?? '',
// Supabase publishable key - env var exported by default.
Deno.env.get(SUPABASE_PUBLISHABLE_KEYS['default']) ?? '',
SUPABASE_PUBLISHABLE_KEYS['default'] ?? '',
// Create client with Auth context of the user that called the function.
// This way your row-level-security (RLS) policies are applied.
{
@@ -23,7 +23,7 @@ Deno.serve(async (req: Request) => {
// Supabase API URL - env var exported by default.
Deno.env.get('SUPABASE_URL') ?? '',
// Supabase API PUBLISHABLE KEY - env var exported by default.
Deno.env.get(SUPABASE_PUBLISHABLE_KEYS['default']) ?? '',
SUPABASE_PUBLISHABLE_KEYS['default'] ?? '',
// Create client with Auth context of the user that called the function.
// This way your row-level-security (RLS) policies are applied.
{
@@ -9,10 +9,7 @@ import * as Sentry from 'https://deno.land/x/sentry@7.102.0/index.mjs'
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get(SUPABASE_SECRET_KEYS['default'])!
)
const supabase = createClient(Deno.env.get('SUPABASE_URL')!, SUPABASE_SECRET_KEYS['default']!)
Sentry.init({
dsn: Deno.env.get('SENTRY_DSN'),
@@ -70,7 +70,7 @@ export async function handler(req: Request) {
// Supabase API URL - env var exported by default when deployed.
Deno.env.get('SUPABASE_URL') ?? '',
// Supabase API SECRET KEY - env var exported by default when deployed.
Deno.env.get(SUPABASE_SECRET_KEYS['default']) ?? ''
SUPABASE_SECRET_KEYS['default'] ?? ''
)
// Upload image to storage.
@@ -12,7 +12,7 @@ Deno.serve(async (req) => {
// Supabase API URL - env var exported by default.
Deno.env.get('SUPABASE_URL') ?? '',
// Supabase publishable key - env var exported by default.
Deno.env.get(SUPABASE_PUBLISHABLE_KEYS['default']) ?? '',
SUPABASE_PUBLISHABLE_KEYS['default'] ?? '',
// Create client with Auth context of the user that called the function.
// This way your row-level-security (RLS) policies are applied.
{
+1 -1
View File
@@ -23,7 +23,7 @@ You're an expert in writing TypeScript and Deno JavaScript runtime. Generate **h
- SUPABASE_SECRET_KEYS
- SUPABASE_DB_URL
You then need to use `JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)` or `JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)` to access the actual keys in the code. For example, `Deno.env.get(SUPABASE_SECRET_KEYS['default'])` to access the default service key. 9. To set other environment variables (ie. secrets) users can put them in a env file and run the `supabase secrets set --env-file path/to/env-file` 10. A single Edge Function can handle multiple routes. It is recommended to use a library like Express or Hono to handle the routes as it's easier for developer to understand and maintain. Each route must be prefixed with `/function-name` so they are routed correctly. 11. File write operations are ONLY permitted on `/tmp` directory. You can use either Deno or Node File APIs. 12. Use `EdgeRuntime.waitUntil(promise)` static method to run long-running tasks in the background without blocking response to a request. Do NOT assume it is available in the request / execution context.
You then need to parse them with `JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)` or `JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)` to access the actual keys in the code. For example, assign the parsed map first with `const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)` and then index it with `SUPABASE_SECRET_KEYS['default']` to access the default secret key. 9. To set other environment variables (ie. secrets) users can put them in a env file and run the `supabase secrets set --env-file path/to/env-file` 10. A single Edge Function can handle multiple routes. It is recommended to use a library like Express or Hono to handle the routes as it's easier for developer to understand and maintain. Each route must be prefixed with `/function-name` so they are routed correctly. 11. File write operations are ONLY permitted on `/tmp` directory. You can use either Deno or Node File APIs. 12. Use `EdgeRuntime.waitUntil(promise)` static method to run long-running tasks in the background without blocking response to a request. Do NOT assume it is available in the request / execution context.
## Example Templates
@@ -27,6 +27,6 @@ Deno.serve((req) => {
const { method, headers } = req
// Add Auth header
const modHeaders = new Headers(headers)
modHeaders.append('authorization', `Bearer ${Deno.env.get(SUPABASE_SECRET_KEYS['default'])!}`)
modHeaders.append('authorization', `Bearer ${SUPABASE_SECRET_KEYS['default']!}`)
return fetch(url, { method, headers: modHeaders })
})
@@ -5,7 +5,7 @@ const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
Deno.serve(async (req) => {
const SUPABASE_URL = Deno.env.get('SUPABASE_URL') ?? ''
const SUPABASE_SECRET_KEY = Deno.env.get(SUPABASE_SECRET_KEYS['default']) ?? ''
const SUPABASE_SECRET_KEY = SUPABASE_SECRET_KEYS['default'] ?? ''
const supabase = createClient(SUPABASE_URL, SUPABASE_SECRET_KEY)
@@ -20,10 +20,7 @@ interface WebhookPayload {
}
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get(SUPABASE_SECRET_KEYS['default'])!
)
const supabase = createClient(Deno.env.get('SUPABASE_URL')!, SUPABASE_SECRET_KEYS['default']!)
Deno.serve(async (req) => {
const payload: WebhookPayload = await req.json()