Example: Amazon Bedrock image generation in Edge Function
This example shows using the AWS SDK for Bedrock in an Edge Function to generate images and store them in Supabase Storage:
```ts
import { prepareVirtualFile } from 'https://deno.land/x/mock_file@v1.1.2/mod.ts'
import { BedrockRuntimeClient, InvokeModelCommand } from 'npm:@aws-sdk/client-bedrock-runtime@^3'
import { withSupabase } from 'npm:@supabase/server@^1'
import { decode } from 'npm:base64-arraybuffer@^1'
console.log('Hello from Amazon Bedrock!')
export default {
fetch: withSupabase({ auth: 'publishable' }, async (req, ctx) => {
prepareVirtualFile('./aws/config')
prepareVirtualFile('./aws/credentials')
const client = new BedrockRuntimeClient({
region: Deno.env.get('AWS_DEFAULT_REGION') ?? 'us-west-2',
credentials: {
accessKeyId: Deno.env.get('AWS_ACCESS_KEY_ID') ?? '',
secretAccessKey: Deno.env.get('AWS_SECRET_ACCESS_KEY') ?? '',
sessionToken: Deno.env.get('AWS_SESSION_TOKEN') ?? '',
},
})
const { prompt, seed } = await req.json()
console.log(prompt)
const input = {
contentType: 'application/json',
accept: '*/*',
modelId: 'amazon.titan-image-generator-v1',
body: JSON.stringify({
taskType: 'TEXT_IMAGE',
textToImageParams: { text: prompt },
imageGenerationConfig: {
numberOfImages: 1,
quality: 'standard',
cfgScale: 8.0,
height: 512,
width: 512,
seed: seed ?? 0,
},
}),
}
const command = new InvokeModelCommand(input)
const response = await client.send(command)
console.log(response)
if (response.$metadata.httpStatusCode === 200) {
const { body, $metadata } = response
const textDecoder = new TextDecoder('utf-8')
const jsonString = textDecoder.decode(body.buffer)
const parsedData = JSON.parse(jsonString)
console.log(parsedData)
const image = parsedData.images[0]
const { data: upload, error: uploadError } = await ctx.supabase.storage
.from('images')
.upload(`${$metadata.requestId ?? ''}.png`, decode(image), {
contentType: 'image/png',
cacheControl: '3600',
upsert: false,
})
if (!upload) {
return Response.json({ error: uploadError?.message ?? 'Upload failed' }, { status: 500 })
}
const { data } = ctx.supabase.storage.from('images').getPublicUrl(upload.path!)
return Response.json(data)
}
return Response.json(response)
}),
}
```
This example demonstrates invoking an AWS Bedrock model from an Edge Function, processing the base64-encoded image response, uploading it to Supabase Storage, and returning the public URL.
Edge Functions support third-party integrations
Edge Functions commonly integrate with third-party services. Examples include Stripe webhooks and other external APIs.
Image filtering example: workflow with Storage integration
A photo-sharing app use case demonstrates edge functions: a user uploads an original image to Supabase Storage, then the client-side app (using the Supabase JavaScript SDK) invokes an edge function named 'apply-filter'. The edge function downloads the original image from Supabase Storage, applies the filter using a library like ImageMagick, uploads the processed image back to Storage, and returns the path to the filtered image to the client.
Gzip decompression example in edge function
The following example shows how to decompress a gzip request body: import gunzipSync from 'node:zlib', check content-encoding header, read request as arrayBuffer, decompress with gunzipSync(new Uint8Array(compressedBody)), decode with TextDecoder, and parse JSON.
beforeunload event handler example
This example shows how to use the beforeunload event handler:
```tsx
import { withSupabase } from 'npm:@supabase/server@^1'
EdgeRuntime.waitUntil(asyncLongRunningTask())
// Use beforeunload event handler to be notified when function is about to shutdown
addEventListener('beforeunload', (ev) => {
console.log('Function will be shutdown due to', ev.detail?.reason)
// Save state or log the current progress
})
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
return Response.json({ ok: true })
}),
}
```
Background task example inside request handler
This example shows how to use EdgeRuntime.waitUntil inside the request handler:
```ts
import { withSupabase } from 'npm:@supabase/server@^1'
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
// Won't block the request, runs in background.
EdgeRuntime.waitUntil(asyncLongRunningTask())
return Response.json({ ok: true })
}),
}
```
Example: manual CORS handling with corsHeaders
import { corsHeaders } from 'npm:@supabase/supabase-js@^2/cors'
console.log(`Function "browser-with-cors" up and running!`)
export default {
fetch: async (req) => {
// Handle the CORS preflight request.
if (req.method === 'OPTIONS') {
return Response.json({ ok: true }, { headers: corsHeaders })
}
try {
const { name } = await req.json()
return Response.json({ message: `Hello ${name}!` }, { headers: corsHeaders })
} catch (error) {
return Response.json({ error: error.message }, { status: 400, headers: corsHeaders })
}
},
}
This example shows how to handle CORS preflight requests, process a POST request, and return error responses all with proper CORS headers.
Example: withSupabase wrapper with automatic CORS
import { withSupabase } from 'npm:@supabase/server@^1'
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
const { name } = await req.json()
return Response.json({ message: `Hello ${name}!` })
}),
}
This example shows that when using withSupabase, CORS headers are handled automatically and you do not need to manually add them.
Invoke deployed function with cURL
Test a deployed Edge Function using cURL with the command: `curl --request POST 'https://<project_id>.supabase.co/functions/v1/hello-world' --header 'apikey: PUBLISHABLE_KEY' --header 'Content-Type: application/json' --data '{ "name":"Functions" }'`. The PUBLISHABLE_KEY can be found in the API settings of the Supabase Dashboard.
Invoke deployed function with JavaScript SDK
Test a deployed Edge Function using the Supabase JavaScript client: `import { createClient } from '@supabase/supabase-js'; const supabase = createClient('https://xyzcompany.supabase.co', 'sb_publishable_...'); const { data, error } = await supabase.functions.invoke('hello-world', { body: { name: 'Functions' } });`
Example of Edge Functions error handling
import { FunctionsFetchError, FunctionsHttpError, FunctionsRelayError } from '@supabase/supabase-js'
const { data, error } = await supabase.functions.invoke('hello', {
headers: { 'my-custom-header': 'my-custom-header-value' },
body: { foo: 'bar' },
})
if (error instanceof FunctionsHttpError) {
const errorMessage = await error.context.json()
console.log('Function returned an error', errorMessage)
} else if (error instanceof FunctionsRelayError) {
console.log('Relay error:', error.message)
} else if (error instanceof FunctionsFetchError) {
console.log('Fetch error:', error.message)
}
Archive processing example with zip extraction
Example showing how to handle photo uploads as zip files using ephemeral storage with background tasks: write the zip file to /tmp, use ZipReader to extract entries, and upload files to Supabase Storage without loading the entire archive into memory. Uses EdgeRuntime.waitUntil() to process in the background and avoid memory limits.
Cloudflare Turnstile CAPTCHA example function
This example demonstrates how to create an Edge Function that validates Cloudflare Turnstile CAPTCHA tokens. The function receives a token from the client, extracts the client IP address from the x-forwarded-for header, and sends a validation request to Cloudflare's siteverify API endpoint at https://challenges.cloudflare.com/turnstile/v0/siteverify with the secret key, response token, and remote IP address in the request body.
Authorize Discord application on server
To install a Discord application's slash commands on a server, go to the OAuth2 section of the Discord application page, select the 'applications.commands' scope, copy the generated authorization URL, visit it in a browser, select the target server, and click Authorize. After installation, users can invoke the slash commands by typing '/' in the Discord server.
Discord bot slash command registration via API
To register a slash command with Discord, send a POST request to https://discord.com/api/v8/applications/{CLIENT_ID}/commands with Authorization header set to 'Bot {BOT_TOKEN}', Content-Type application/json, and a JSON body containing the command definition. The JSON body must include: name (string, the command name), description (string, the command description), and options (array, command parameters). Each option in the options array must have: name (string), description (string), type (integer, 3 for string type), and required (boolean). Example: {"name":"hello","description":"Greet a person","options":[{"name":"name","description":"The name of the person","type":3,"required":true}]}
Discord interaction request validation headers
Discord sends two headers with every request that must be validated: X-Signature-Ed25519 and X-Signature-Timestamp. The request must be a POST request. These headers are used for signature verification to ensure the request is authentically from Discord.
Verify Discord request signature with TweetNaCl
Use the nacl.sign.detached.verify() function from the TweetNaCl library to verify Discord requests. The function takes three Uint8Array arguments: first, the encoded concatenation of the X-Signature-Timestamp header and request body; second, the decoded X-Signature-Ed25519 header from the request; third, the decoded DISCORD_PUBLIC_KEY from environment variables. The signature is valid if verification succeeds; Discord intentionally sends invalid requests to test verification, and a 401 response should be returned for invalid signatures.
Discord interaction type enum values
Discord defines two main interaction types: Type 1 (Ping) is used by Discord to test the application and requires a response type of 1 (Pong); Type 2 (ApplicationCommand) is issued when a user executes a slash command. A response type of 4 responds with a message while retaining the user's input at the top.
Extract parameter value from Discord slash command
Discord passes command parameters in the data.options array. Each option object has a name and value property. To extract a specific parameter, find the option object where the name property matches the desired parameter name, then access its value property.
Discord bot slash command example
Example of a Discord bot handling a slash command using Sift routing and TweetNaCl verification:
import nacl from 'https://cdn.skypack.dev/tweetnacl@v1.0.3?dts'
import { json, serve, validateRequest } from 'https://deno.land/x/sift@0.6.0/mod.ts'
enum DiscordCommandType {
Ping = 1,
ApplicationCommand = 2,
}
serve({
'/discord-bot': home,
})
async function home(request: Request) {
const { error } = await validateRequest(request, {
POST: {
headers: ['X-Signature-Ed25519', 'X-Signature-Timestamp'],
},
})
if (error) {
return json({ error: error.message }, { status: error.status })
}
const { valid, body } = await verifySignature(request)
if (!valid) {
return json(
{ error: 'Invalid request' },
{
status: 401,
}
)
}
const { type = 0, data = { options: [] } } = JSON.parse(body)
if (type === DiscordCommandType.Ping) {
return json({
type: 1,
})
}
if (type === DiscordCommandType.ApplicationCommand) {
const { value } = data.options.find(
(option: { name: string; value: string }) => option.name === 'name'
)
return json({
type: 4,
data: {
content: `Hello, ${value}!`,
},
})
}
return json({ error: 'bad request' }, { status: 400 })
}
async function verifySignature(request: Request): Promise<{ valid: boolean; body: string }> {
const PUBLIC_KEY = Deno.env.get('DISCORD_PUBLIC_KEY')!
const signature = request.headers.get('X-Signature-Ed25519')!
const timestamp = request.headers.get('X-Signature-Timestamp')!
const body = await request.text()
const valid = nacl.sign.detached.verify(
new TextEncoder().encode(timestamp + body),
hexToUint8Array(signature),
hexToUint8Array(PUBLIC_KEY)
)
return { valid, body }
}
function hexToUint8Array(hex: string) {
return new Uint8Array(hex.match(/.{1,2}/g)!.map((val) => parseInt(val, 16)))
}
Send email with Resend API
To send an email using Resend: create a new Resend instance with `new Resend(apiKey)`, then call `resend.emails.send()` with parameters: from (string), to (array of email addresses), subject (string), and html (string). The method returns an object with optional error property.
React Email template component structure
React Email templates for auth emails use components like Html, Head, Body, Container, Heading, Link, Text, and Preview from @react-email/components. Templates receive props containing Supabase authentication data like token, token_hash, redirect_to, and email_action_type.
Render React Email templates to HTML
Use the `renderAsync` function from `@react-email/components` to convert React Email components to HTML: `const html = await renderAsync(React.createElement(ComponentName, props))`.
Custom auth email handler example
Example of a complete auth email handler function:
```tsx
import { renderAsync } from 'npm:@react-email/components@^1'
import { withSupabase } from 'npm:@supabase/server@^1'
import React from 'npm:react@^19'
import { Resend } from 'npm:resend@^6'
import { Webhook } from 'npm:standardwebhooks@^1'
import { MagicLinkEmail } from './_templates/magic-link.tsx'
const resend = new Resend(Deno.env.get('RESEND_API_KEY') as string)
const hookSecret = (Deno.env.get('SEND_EMAIL_HOOK_SECRET') as string).replace('v1,whsec_', '')
export default {
fetch: withSupabase({ auth: 'none' }, async (req) => {
if (req.method !== 'POST') {
return Response.json({ error: 'not allowed' }, { status: 400 })
}
const payload = await req.text()
const headers = Object.fromEntries(req.headers)
const wh = new Webhook(hookSecret)
try {
const {
user,
email_data: { token, token_hash, redirect_to, email_action_type },
} = wh.verify(payload, headers) as {
user: {
email: string
}
email_data: {
token: string
token_hash: string
redirect_to: string
email_action_type: string
site_url: string
token_new: string
token_hash_new: string
}
}
const html = await renderAsync(
React.createElement(MagicLinkEmail, {
supabase_url: Deno.env.get('SUPABASE_URL') ?? '',
token,
token_hash,
redirect_to,
email_action_type,
})
)
const { error } = await resend.emails.send({
from: 'welcome <onboarding@resend.dev>',
to: [user.email],
subject: 'Supa Custom MagicLink!',
html,
})
if (error) {
throw error
}
} catch (error) {
console.log(error)
return Response.json(
{
error: {
http_code: error.code,
message: error.message,
},
},
{ status: 401 }
)
}
return Response.json({})
}),
}
```
ElevenLabs speech generation example
Example using ElevenLabs SDK in Edge Functions: const response = await client.textToSpeech.convertAsStream(voiceId, { output_format: 'mp3_44100_128', model_id: 'eleven_multilingual_v2', text }) where client is initialized with apiKey from Deno.env.get().
Use curl to test Edge Function with file upload
To test an Edge Function that accepts file uploads, use curl with the --form option: curl --location 'http://localhost:54321/functions/v1/image-blur' --form 'file=@"/path/to/image.png"' --output '/path/to/output.png'
magick-wasm for image manipulation
magick-wasm is a WebAssembly port of the ImageMagick library that can be used in Edge Functions for custom image manipulation. It supports processing over 100 file formats.
OG image handler example file
import { ImageResponse } from 'npm:@vercel/og@^0'
import React from 'npm:react@^19'
export default function handler(req: Request) {
return new ImageResponse(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 128,
background: 'lavender',
}}
>
Hello OG Image!
</div>
)
}
Generate OG images with @vercel/og and React in Edge Functions
Open Graph images can be generated using Deno and Supabase Edge Functions with the @vercel/og package and React. The ImageResponse component from @vercel/og renders a React component as an image.
OG image handler example using React and ImageResponse
A handler function can be created in handler.tsx that uses ImageResponse to render a React component as an OG image. The example creates a div with flexbox layout, sets width and height to 100%, centers content, and applies styling like fontSize and background color.
OG image index.ts entry point example
import { withSupabase } from 'npm:@supabase/server@^1'
import handler from './handler.tsx'
console.log('Hello from og-image Function!')
// Public image endpoint, so deploy with --no-verify-jwt.
export default { fetch: withSupabase({ auth: 'none' }, handler) }
Extract metadata from Telegram message objects
Telegram message objects contain type-specific metadata accessible as properties. For example, `ctx.message?.voice`, `ctx.message?.audio`, and `ctx.message?.video` contain metadata objects with properties like `mime_type` and `duration`. At least one will be present depending on the message type.
Example: Database insert with Supabase admin client
```ts
const logLine = {
file_type: fileType,
duration: duration,
chat_id: chatId,
message_id: messageId,
username: username,
language_code: languageCode,
error: errorMsg,
}
await supabaseAdmin.from('transcription_logs').insert({ ...logLine, transcript })
```
This example shows how to insert a row into a database table using the admin Supabase client in an Edge Function.
Telegram webhook setup with secret parameter
To set up a Telegram webhook for a bot, make a GET request to `https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/setWebhook?url=<WEBHOOK_URL>`. Include a secret parameter in the webhook URL query string (e.g., `?secret=<FUNCTION_SECRET>`) that can be validated in the function to verify requests come from Telegram.
Telegram webhook verification in Edge Function
In an Edge Function handling Telegram webhooks, validate incoming requests by checking a secret parameter passed in the query string. Compare it against the `FUNCTION_SECRET` environment variable using `url.searchParams.get('secret')`. Reject requests that do not match.
Example: Telegram bot with grammY framework
```ts
import { Bot, webhookCallback } from 'npm:grammy@^1'
import { withSupabase } from 'npm:@supabase/server@^1'
const bot = new Bot(Deno.env.get('TELEGRAM_BOT_TOKEN') || '')
bot.command('start', (ctx) => ctx.reply('Welcome!'))
bot.on(':voice', async (ctx) => {
const file = await ctx.getFile()
// Process file
})
const handleUpdate = webhookCallback(bot, 'std/http')
export default {
fetch: withSupabase({ auth: 'none' }, async (req, ctx) => {
const url = new URL(req.url)
if (url.searchParams.get('secret') !== Deno.env.get('FUNCTION_SECRET')) {
return Response.json({ error: 'not allowed' }, { status: 405 })
}
return await handleUpdate(req)
}),
}
```
This example shows a minimal Telegram bot using the grammY framework in a Supabase Edge Function, with webhook verification and response delegation.
Immediately reply to Telegram user before background processing
To provide immediate user feedback while processing continues in the background, reply to the Telegram message synchronously (e.g., 'Received. Processing...'), then wrap the long-running operation in `EdgeRuntime.waitUntil()` so it executes after the response is sent.
Access Telegram file with bot API URL
After calling `ctx.getFile()` in grammY, construct the Telegram file URL as `https://api.telegram.org/file/bot<TELEGRAM_BOT_TOKEN>/<file_path>`. This URL can then be fetched to download the file content.
Rate limiting with Upstash Redis in Edge Functions
Upstash provides an HTTP/REST based Redis client that is ideal for serverless use-cases and works well with Supabase Edge Functions. Redis can be used for rate limiting by leveraging atomic operations like incrementing a value.
Why Upstash for Edge Functions
Upstash is chosen for Supabase Edge Functions because it provides an HTTP/REST based interface rather than a traditional TCP connection, making it ideal for the stateless, short-lived nature of serverless functions.
Redis atomic operations for rate limiting
Redis is optimized for atomic operations like incrementing a value, which makes it suitable for implementing rate limiting and view counters in Edge Functions.
Puppeteer for screenshots in Edge Functions
Puppeteer can be used to programmatically take screenshots and generate PDFs in Edge Functions. However, it presents challenges due to size restrictions in the Edge Functions environment.
Example: Puppeteer screenshot code
A complete example implementation of taking screenshots with Puppeteer in Supabase Edge Functions is available on GitHub at https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/puppeteer.
Browserless.io integration for screenshots
Browserless.io is a serverless browser offering that can be connected to Edge Functions via WebSockets to overcome size restrictions when using Puppeteer for screenshots and PDF generation.
FCM push notifications example code
The edge function for Firebase Cloud Messaging (FCM) push notifications uses withSupabase({ auth: 'secret' }). It imports a service-account.json file, generates an access token using google-auth-library JWT with scopes ['https://www.googleapis.com/auth/firebase.messaging'], queries the profiles table for an fcm_token, and sends a POST request to https://fcm.googleapis.com/v1/projects/${serviceAccount.project_id}/messages:send with Authorization header Bearer ${accessToken}. The request body contains a message object with token, notification.title, and notification.body fields. The function is triggered by database webhook on INSERT events to the notifications table.
FCM setup steps
For Firebase Cloud Messaging: follow the official FCM Setup Guide at https://firebase.google.com/docs/cloud-messaging, generate a new service account private key from Firebase console Project Settings > Service Accounts > Generate new private key, and save it as service-account.json in the supabase/functions directory.
Expo push notification setup requirement
To use Expo's push notification service, you must follow the official Expo Push Notifications Setup Guide at https://docs.expo.dev/push-notifications/push-notifications-setup/ to get credentials for Android and iOS. The project should use Expo's EAS build service. Create a new Expo project, link the app with 'eas init --id your-expo-project-id', create a build for your physical device, start the development server with 'npx expo start --dev-client', and scan the QR code with your physical device.
Error handling in FCM edge functions
When using Firebase Cloud Messaging, check the HTTP response status and throw an error if the status is not between 200 and 299 inclusive. The error response data is parsed from res.json() and can be thrown to indicate the request failed.
Expo edge function uses Bearer token authentication
When calling the Expo push notification API at https://exp.host/--/api/v2/push/send, use an Authorization header with format 'Bearer ${EXPO_ACCESS_TOKEN}'. The EXPO_ACCESS_TOKEN is generated from Expo account settings.
FCM database schema for push notifications
For Firebase Cloud Messaging, create a profiles table with 'id uuid references auth.users(id) not null primary key' and 'fcm_token text' columns. Create a notifications table with 'id uuid not null default gen_random_uuid()', 'user_id uuid references auth.users(id) not null', 'created_at timestamp with time zone not null default now()', and 'body text not null'. If a profiles table already exists, alter it to add the fcm_token column.
Expo database schema for push notifications
For Expo push notifications, the profiles table requires an expo_push_token column to store the token for each user. A notifications table is needed with user_id and body fields. The database webhook will query the profiles table by user_id to retrieve the expo_push_token.
Expo push notifications example code
The edge function code for sending Expo push notifications to React Native apps is available at https://github.com/supabase/supabase/blob/master/examples/user-management/expo-push-notifications/supabase/functions/push/index.ts. The function uses withSupabase({ auth: 'secret' }), queries the profiles table for an expo_push_token, and sends a POST request to https://exp.host/--/api/v2/push/send with the Authorization header Bearer ${EXPO_ACCESS_TOKEN}, including sound: 'default' and the notification body. The function is triggered by a database webhook on INSERT events to the notifications table.
Database webhook configuration for push notifications
To create a database webhook for push notifications: navigate to Database Webhooks settings in the dashboard, create a new hook with conditions to fire on the notifications table INSERT event, select Supabase Edge Functions as the webhook configuration, select the push edge function with POST method and 1000ms timeout, add an auth header with service key and Content-Type: application/json header.
Example: Sending emails with Resend API from Edge Functions
This example shows how to send emails from an Edge Function using the Resend API:
```tsx
import { withSupabase } from 'npm:@supabase/server@^1'
const RESEND_API_KEY = Deno.env.get('RESEND_API_KEY')
const handler = async (_request: Request): Promise<Response> => {
const res = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${RESEND_API_KEY}`,
},
body: JSON.stringify({
from: 'onboarding@resend.dev',
to: 'delivered@resend.dev',
subject: 'hello world',
html: '<strong>it works!</strong>',
}),
})
const data = await res.json()
return Response.json(data)
}
export default { fetch: withSupabase({ auth: ['user', 'secret'] }, handler) }
```
The example demonstrates fetching from an external API, using environment variables for secrets, and returning JSON responses.
Sentry initialization configuration
Initialize Sentry with `Sentry.init()` passing a configuration object that includes: dsn (from environment variable SENTRY_DSN), defaultIntegrations set to false, tracesSampleRate (set to 1.0 for full sampling), and profilesSampleRate (set to 1.0, relative to tracesSampleRate).
Sentry Deno SDK import
Import Sentry for Deno using `import * as Sentry from 'npm:@sentry/deno@^8'`.
Workaround for Sentry scope issues in Edge Functions
To work around the lack of scope separation, disable all default integrations by setting `defaultIntegrations: false` in the Sentry configuration. Then use `withScope` to encapsulate Sentry SDK API calls, or pass context directly to `captureException()` or `captureMessage()` calls instead of relying on global context.
Flush Sentry before process close
Call `await Sentry.flush(2000)` before the running process closes to ensure all events are sent to Sentry. The timeout is specified in milliseconds.
Capture exceptions with Sentry
Capture exceptions by calling `Sentry.captureException(e)` in catch blocks.
Set Sentry tags for region and execution ID
Use `Sentry.setTag('region', Deno.env.get('SB_REGION'))` and `Sentry.setTag('execution_id', Deno.env.get('SB_EXECUTION_ID'))` to set custom tags for tracking.