Development tips
Tips for getting started with Edge Functions.
Here are a few recommendations when you first start developing Edge Functions.
Using HTTP methods#
Edge Functions support GET, POST, PUT, PATCH, DELETE, and OPTIONS. A Function can be designed to perform different actions based on a request's HTTP method. See the example on building a RESTful service to learn how to handle different HTTP methods in your Function.
HTML not supported
HTML content is not supported. GET requests that return text/html will be rewritten to text/plain.
Naming Edge Functions#
We recommend using hyphens to name functions because hyphens are the most URL-friendly of all the naming conventions (snake_case, camelCase, PascalCase).
Organizing your Edge Functions#
We recommend developing "fat functions". This means that you should develop few large functions, rather than many small functions. One common pattern when developing Functions is that you need to share code between two or more Functions. To do this, you can store any shared code in a folder prefixed with an underscore (_). We also recommend a separate folder for Unit Tests including the name of the function followed by a -test suffix.
We recommend this folder structure:
└── supabase ├── functions │ ├── import_map.json # A top-level import map to use across functions. │ ├── _shared │ │ ├── supabaseAdmin.ts # Supabase client with SECRET key. │ │ └── supabaseClient.ts # Supabase client with PUBLISHABLE key. │ │ └── cors.ts # Reusable CORS headers. │ ├── function-one # Use hyphens to name functions. │ │ └── index.ts │ └── function-two │ │ └── index.ts │ └── tests │ └── function-one-test.ts │ └── function-two-test.ts ├── migrations └── config.tomlUsing config.toml#
Individual function configuration like JWT verification and import map location can be set via the config.toml file.
[functions.hello-world]verify_jwt = falseimport_map = './import_map.json'Not using TypeScript#
When you create a new Edge Function, it will use TypeScript by default. However, it is possible to write and deploy Edge Functions using pure JavaScript.
Save your Function as a JavaScript file (e.g. index.js) and then update the supabase/config.toml as follows:
entrypoint is available only in Supabase CLI version 1.215.0 or higher.
[functions.hello-world]# other entriesentrypoint = './functions/hello-world/index.js' # path must be relative to config.tomlYou can use any .ts, .js, .tsx, .jsx or .mjs file as the entrypoint for a Function.
Error handling#
The supabase-js library provides several error types that you can use to handle errors that might occur when invoking Edge Functions:
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)}Sending Realtime messages#
To send a Broadcast message from an Edge Function, call httpSend() without subscribing to the channel. httpSend() sends the message in a single HTTP request, so no connection stays open:
import { createClient } from 'npm:@supabase/supabase-js@2'const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)const supabase = createClient(Deno.env.get('SUPABASE_URL')!, SUPABASE_SECRET_KEYS['default'])Deno.serve(async (req) => { const { roomId, message } = await req.json() // Verify the caller is allowed to post to this room before sending const channel = supabase.channel(`room:${roomId}`) try { await channel.httpSend('new-message', { message }) } finally { await supabase.removeChannel(channel) } return new Response(null, { status: 204 })})httpSend() requires supabase-js 2.76.0 or later. For other languages, call the Broadcast REST API directly.
Why not subscribe to the channel#
Subscribing from an Edge Function opens more connections than you might expect, and you're billed for each one:
- Each worker that subscribes opens its own WebSocket connection and keeps it until the worker shuts down.
- Under load, a function can run many workers at once.
- Every connection counts toward Realtime Peak Connections.
- Every subscribed worker counts as a recipient of each message on the channel, which adds to Realtime Messages.
Database Functions vs Edge Functions#
For data-intensive operations we recommend using Database Functions, which are executed within your database and can be called remotely using the REST and GraphQL API.
For use-cases which require low-latency we recommend Edge Functions, which are globally-distributed and can be written in TypeScript.