Edge Functions

Developing Edge Functions locally

Get started with Edge Functions on your local machine.


Let's create a basic Edge Function on your local machine and then invoke it using the Supabase CLI.

Initialize a project

Create a new Supabase project in a folder on your local machine:


_10
supabase init

Create an Edge Function

Let's create a new Edge Function called hello-world inside your project:


_10
supabase functions new hello-world

This creates a function stub in your supabase folder:


_10
└── supabase
_10
├── functions
_10
│ └── hello-world
_10
│ │ └── index.ts ## Your function code
_10
└── config.toml

How to write the code

The generated function uses native Deno.serve to handle requests. It gives you access to Request and Response objects.

Here's the generated Hello World Edge Function, that accepts a name in the Request and responds with a greeting:


_10
Deno.serve(async (req) => {
_10
const { name } = await req.json()
_10
const data = {
_10
message: `Hello ${name}!`,
_10
}
_10
_10
return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })
_10
})

Running Edge Functions locally

You can run your Edge Function locally using supabase functions serve:


_10
supabase start # start the supabase stack
_10
supabase functions serve # start the Functions watcher

The functions serve command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server.

Invoking Edge Functions locally

While serving your local Edge Function, you can invoke it using curl or one of the client libraries. To call the function from a browser you need to handle CORS requests. See CORS.

cURL
JavaScript

_10
curl --request POST 'http://localhost:54321/functions/v1/hello-world' \
_10
--header 'Authorization: Bearer SUPABASE_ANON_KEY' \
_10
--header 'Content-Type: application/json' \
_10
--data '{ "name":"Functions" }'

You should see the response { "message":"Hello Functions!" }.

If you execute the function with a different payload, the response will change.

Modify the --data '{"name":"Functions"}' line to --data '{"name":"World"}' and try invoking the command again.

Next steps

Check out the Deploy to Production guide to make your Edge Function available to the world.

Read on for some common development tips.

Development tips

Here are a few recommendations when developing Edge Functions.

Skipping authorization checks

By default, Edge Functions require a valid JWT in the authorization header. If you want to use Edge Functions without Authorization checks (commonly used for Stripe webhooks), you can pass the --no-verify-jwt flag when serving your Edge Functions locally.


_10
supabase functions serve hello-world --no-verify-jwt

Be careful when using this flag, as it will allow anyone to invoke your Edge Function without a valid JWT. The Supabase client libraries automatically handle authorization.

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.

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:


_16
└── supabase
_16
├── functions
_16
│ ├── import_map.json # A top-level import map to use across functions.
_16
│ ├── _shared
_16
│ │ ├── supabaseAdmin.ts # Supabase client with SERVICE_ROLE key.
_16
│ │ └── supabaseClient.ts # Supabase client with ANON key.
_16
│ │ └── cors.ts # Reusable CORS headers.
_16
│ ├── function-one # Use hyphens to name functions.
_16
│ │ └── index.ts
_16
│ └── function-two
_16
│ │ └── index.ts
_16
│ └── tests
_16
│ └── function-one-test.ts
_16
│ └── function-two-test.ts
_16
├── migrations
_16
└── config.toml

Using config.toml

Individual function configuration like JWT verification and import map location can be set via the config.toml file.

supabase/config.toml

_10
[functions.hello-world]
_10
verify_jwt = false
_10
import_map = './import_map.json'

Error handling

The supabase-js library provides several error types that you can use to handle errors that might occur when invoking Edge Functions:


_15
import { FunctionsHttpError, FunctionsRelayError, FunctionsFetchError } from '@supabase/supabase-js'
_15
_15
const { data, error } = await supabase.functions.invoke('hello', {
_15
headers: { 'my-custom-header': 'my-custom-header-value' },
_15
body: { foo: 'bar' },
_15
})
_15
_15
if (error instanceof FunctionsHttpError) {
_15
const errorMessage = await error.context.json()
_15
console.log('Function returned an error', errorMessage)
_15
} else if (error instanceof FunctionsRelayError) {
_15
console.log('Relay error:', error.message)
_15
} else if (error instanceof FunctionsFetchError) {
_15
console.log('Fetch error:', error.message)
_15
}

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.