Edge Functions are server-side TypeScript functions hosted by Supabase. Use them for logic that should not run on the client: calling third-party APIs with secret keys, processing webhooks, sending emails, or any operation that needs server-level access.

For the full guide, see the Supabase Edge Functions docs.

Setup

You need the Supabase CLI to create and deploy functions. Install it, then log in and link to your project. You can find your project reference ID in the Supabase dashboard under Settings → General.

bash
npm install -g supabase
supabase login
supabase link --project-ref your-project-ref

Create a Function

This command creates a new function folder with a template file at supabase/functions/hello-world/index.ts. Edge Functions use Deno (a TypeScript runtime, similar to Node.js but with built-in TypeScript support).

bash
supabase functions new hello-world

Edit the generated file. The function receives an HTTP request and returns an HTTP response:

typescript
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'

serve(async (req) => {
  const { name } = await req.json()

  return new Response(
    JSON.stringify({ message: `Hello ${name}!` }),
    { headers: { 'Content-Type': 'application/json' } },
  )
})

Test Locally

Start the local Supabase environment (this runs a local Postgres database and all Supabase services), then serve your function:

bash
supabase start
supabase functions serve hello-world

Your function is now available at http://localhost:54321/functions/v1/hello-world. Test it with a POST request.

Deploy

Deploy to production with a single command. The function becomes available at your Supabase project's URL.

bash
supabase functions deploy hello-world

Call from Flutter

Use the Supabase client to invoke the function by name. Pass data in the body parameter and read the response from response.data.

dart
final response = await supabase.functions.invoke(
  'hello-world',
  body: {'name': 'Mitch'},
);

final data = response.data;
print(data['message']); // Hello Mitch!

Secrets

Store API keys and sensitive values as secrets. They are available as environment variables inside your function but never exposed to the client.

bash
# Set a secret via CLI
supabase secrets set STRIPE_KEY=sk_live_xxx
typescript
// Access it in your function
const stripeKey = Deno.env.get('STRIPE_KEY')!

Practical Example

A function that calls a third-party API using a secret key. The secret stays on the server and is never sent to the client app.

typescript
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'

serve(async (req) => {
  try {
    const apiKey = Deno.env.get('API_KEY')!
    const { query } = await req.json()

    const res = await fetch('https://api.example.com/search', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ q: query }),
    })

    if (!res.ok) {
      return new Response(
        JSON.stringify({ error: 'API request failed' }),
        { status: res.status, headers: { 'Content-Type': 'application/json' } },
      )
    }

    const data = await res.json()
    return new Response(JSON.stringify(data), {
      headers: { 'Content-Type': 'application/json' },
    })
  } catch (error) {
    return new Response(
      JSON.stringify({ error: error.message }),
      { status: 500, headers: { 'Content-Type': 'application/json' } },
    )
  }
})

Call it from Flutter the same way as any other function:

dart
final response = await supabase.functions.invoke(
  'search',
  body: {'query': 'flutter tutorials'},
);

if (response.status != 200) {
  // Handle error
  print('Error: ${response.data['error']}');
}