Skip to main content

Overview

Supabase Edge Functions are server-side TypeScript functions that run on Deno, distributed globally at the edge. They’re perfect for running custom business logic, integrations with third-party services, or any server-side code.

Base URL

Edge Functions are available at:
https://<PROJECT_REF>.supabase.co/functions/v1/

Invoking Functions

Basic Invocation

Invoke a function with a POST request.
curl -X POST 'https://<PROJECT_REF>.supabase.co/functions/v1/hello-world' \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Functions"}'
function_name
string
required
The name of the function to invoke (in the URL path).
apikey
string
required
Your Supabase API key.
Authorization
string
Bearer token for authenticated requests (optional for public functions).
Content-Type
string
Set to application/json for JSON payloads.

Response

{
  "message": "Hello Functions!"
}

GET Request

Invoke a function with a GET request.
curl 'https://<PROJECT_REF>.supabase.co/functions/v1/hello-world?name=Functions' \
  -H "apikey: YOUR_ANON_KEY"

Custom Headers

Pass custom headers to your function.
curl -X POST 'https://<PROJECT_REF>.supabase.co/functions/v1/webhook-handler' \
  -H "apikey: YOUR_ANON_KEY" \
  -H "X-Custom-Header: custom-value" \
  -H "Content-Type: application/json" \
  -d '{"event": "user.created"}'

Function Examples

Basic Function

A simple Edge Function that returns a greeting.
// supabase/functions/hello-world/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'

serve(async (req) => {
  const { name } = await req.json()
  
  const data = {
    message: `Hello ${name}!`,
  }

  return new Response(
    JSON.stringify(data),
    { headers: { 'Content-Type': 'application/json' } },
  )
})

Database Access

Access your Supabase database from an Edge Function.
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  const supabaseClient = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_ANON_KEY') ?? '',
    {
      global: {
        headers: { Authorization: req.headers.get('Authorization')! },
      },
    }
  )

  const { data, error } = await supabaseClient
    .from('countries')
    .select('*')
    .limit(10)

  if (error) {
    return new Response(
      JSON.stringify({ error: error.message }),
      { status: 500, headers: { 'Content-Type': 'application/json' } }
    )
  }

  return new Response(
    JSON.stringify(data),
    { headers: { 'Content-Type': 'application/json' } }
  )
})

OpenAI Integration

Integrate with third-party APIs like OpenAI.
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { Configuration, OpenAIApi } from 'https://esm.sh/openai@3.1.0'

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

  const configuration = new Configuration({
    apiKey: Deno.env.get('OPENAI_API_KEY'),
  })
  const openai = new OpenAIApi(configuration)

  const completion = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    messages: [{ role: 'user', content: prompt }],
  })

  return new Response(
    JSON.stringify({
      response: completion.data.choices[0].message?.content,
    }),
    { headers: { 'Content-Type': 'application/json' } }
  )
})

Webhook Handler

Handle webhooks from third-party services.
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  // Verify webhook signature
  const signature = req.headers.get('x-webhook-signature')
  if (!verifySignature(signature, await req.text())) {
    return new Response('Unauthorized', { status: 401 })
  }

  const payload = await req.json()
  const supabaseClient = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
  )

  // Process webhook
  await supabaseClient
    .from('webhook_events')
    .insert({
      event_type: payload.type,
      data: payload.data,
    })

  return new Response('OK', { status: 200 })
})

function verifySignature(signature: string | null, body: string): boolean {
  // Implement signature verification logic
  return true
}

Environment Variables

Access environment variables in your functions.
const supabaseUrl = Deno.env.get('SUPABASE_URL')
const apiKey = Deno.env.get('SUPABASE_ANON_KEY')
const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
const customVar = Deno.env.get('MY_CUSTOM_VAR')

Built-in Environment Variables

SUPABASE_URL
string
Your Supabase project URL.
SUPABASE_ANON_KEY
string
Your Supabase anon key.
SUPABASE_SERVICE_ROLE_KEY
string
Your Supabase service role key (use with caution).
SUPABASE_DB_URL
string
Direct connection string to your Postgres database.

Request and Response

Request Object

Access request properties.
serve(async (req) => {
  // HTTP method
  const method = req.method // GET, POST, etc.

  // Headers
  const contentType = req.headers.get('Content-Type')
  const auth = req.headers.get('Authorization')

  // URL and query params
  const url = new URL(req.url)
  const name = url.searchParams.get('name')

  // Body
  const body = await req.json() // For JSON
  const text = await req.text() // For text
  const formData = await req.formData() // For forms

  return new Response('OK')
})

Response Object

Return different types of responses.
// JSON response
return new Response(
  JSON.stringify({ message: 'Success' }),
  {
    status: 200,
    headers: {
      'Content-Type': 'application/json',
      'Cache-Control': 'no-cache',
    },
  }
)

// Plain text
return new Response('Hello World', {
  headers: { 'Content-Type': 'text/plain' },
})

// HTML
return new Response('<h1>Hello World</h1>', {
  headers: { 'Content-Type': 'text/html' },
})

// Redirect
return new Response(null, {
  status: 302,
  headers: { Location: 'https://example.com' },
})

// Error
return new Response(
  JSON.stringify({ error: 'Not found' }),
  {
    status: 404,
    headers: { 'Content-Type': 'application/json' },
  }
)

CORS

Handle CORS for browser requests.
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { corsHeaders } from '../_shared/cors.ts'

serve(async (req) => {
  // Handle CORS preflight
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  // Your function logic
  const data = { message: 'Hello from CORS-enabled function' }

  return new Response(
    JSON.stringify(data),
    {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    }
  )
})

CORS Headers

// _shared/cors.ts
export const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

Authentication

Verify JWT

Verify the user’s JWT token.
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  const supabaseClient = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_ANON_KEY') ?? '',
    {
      global: {
        headers: { Authorization: req.headers.get('Authorization')! },
      },
    }
  )

  // Get the current user
  const {
    data: { user },
    error,
  } = await supabaseClient.auth.getUser()

  if (error || !user) {
    return new Response(
      JSON.stringify({ error: 'Unauthorized' }),
      { status: 401, headers: { 'Content-Type': 'application/json' } }
    )
  }

  return new Response(
    JSON.stringify({ userId: user.id, email: user.email }),
    { headers: { 'Content-Type': 'application/json' } }
  )
})

Deployment

Deploy via CLI

# Deploy a single function
supabase functions deploy hello-world

# Deploy all functions
supabase functions deploy

# Deploy with environment variables
supabase secrets set MY_SECRET=value
supabase functions deploy hello-world

Deploy via GitHub Actions

name: Deploy Functions

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: supabase/setup-cli@v1
        with:
          version: latest
      
      - name: Deploy functions
        run: supabase functions deploy
        env:
          SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
          PROJECT_REF: ${{ secrets.PROJECT_REF }}

Limits and Pricing

Free Tier

  • 500,000 invocations per month
  • 1GB bandwidth per month
  • 100 concurrent executions

Pro Tier

  • 2,000,000 invocations per month
  • 5GB bandwidth per month
  • 500 concurrent executions

Execution Limits

  • Timeout: 60 seconds per invocation
  • Memory: 150MB RAM
  • CPU: Limited based on plan
  • Cold start: ~1-3 seconds

Best Practices

Each function should do one thing well. Create multiple functions for different use cases.
Never hardcode API keys or secrets in your functions:
supabase secrets set OPENAI_API_KEY=sk-...
Always handle errors gracefully:
try {
  // Your logic
} catch (error) {
  return new Response(
    JSON.stringify({ error: error.message }),
    { status: 500 }
  )
}
TypeScript provides better developer experience and catches errors early.
Use appropriate cache headers to reduce function invocations:
headers: { 'Cache-Control': 'public, max-age=3600' }

Monitoring

View function logs and metrics in the Supabase Dashboard:
  • Logs: Real-time function execution logs
  • Metrics: Invocation count, error rate, execution time
  • Traces: Request/response details

Next Steps

Deploy Your First Function

Learn how to create and deploy Edge Functions

Client Libraries

Invoke functions from your app