Skip to main content

Introduction

Supabase Edge Functions are server-side TypeScript functions that run on Deno, distributed globally at the edge for low latency. They’re perfect for custom business logic, third-party API integrations, and any server-side processing you need.
Edge Functions run on Deno Deploy, providing a fast, secure runtime with global distribution.

Key Features

TypeScript Native

Write functions in TypeScript with full type safety and modern JavaScript features

Global Distribution

Functions run on edge servers worldwide for minimal latency

Secure by Default

Automatic JWT verification and built-in authentication context

Seamless Integration

Direct access to your Supabase database, auth, and storage

What You Can Build

API Integrations

Connect to third-party services:
  • Payment processing: Stripe, PayPal webhooks
  • Email sending: Resend, SendGrid, Mailgun
  • AI services: OpenAI, Anthropic, HuggingFace
  • Social platforms: Twitter, Discord, Slack bots

Custom Business Logic

  • Complex calculations and data transformations
  • Batch processing and scheduled jobs
  • Custom authentication flows
  • PDF generation and image processing

Database Operations

  • Complex queries with RLS enforcement
  • Data validation and sanitization
  • Aggregations and analytics
  • Database triggers and webhooks

Quick Start

Create Your First Function

// supabase/functions/hello-world/index.ts
Deno.serve(async (req) => {
  const { name } = await req.json()
  
  const data = {
    message: `Hello ${name}!`
  }

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

Deploy the Function

# Login to Supabase CLI
supabase login

# Link your project
supabase link --project-ref your-project-ref

# Deploy the function
supabase functions deploy hello-world

Invoke the Function

From your client application:
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'https://your-project.supabase.co',
  'your-anon-key'
)

const { data, error } = await supabase.functions.invoke('hello-world', {
  body: { name: 'Alice' }
})

console.log(data) // { message: 'Hello Alice!' }
Or with curl:
curl -L -X POST 'https://your-project.supabase.co/functions/v1/hello-world' \
  -H 'Authorization: Bearer YOUR_ANON_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Alice"}'

Real-World Examples

Stripe Webhook Handler

Process Stripe webhooks securely:
// supabase/functions/stripe-webhooks/index.ts
import Stripe from 'https://esm.sh/stripe@14?target=denonext'

const stripe = new Stripe(Deno.env.get('STRIPE_API_KEY') as string, {
  apiVersion: '2024-11-20'
})

const cryptoProvider = Stripe.createSubtleCryptoProvider()

Deno.serve(async (request) => {
  const signature = request.headers.get('Stripe-Signature')
  const body = await request.text()
  
  let event
  try {
    event = await stripe.webhooks.constructEventAsync(
      body,
      signature!,
      Deno.env.get('STRIPE_WEBHOOK_SIGNING_SECRET')!,
      undefined,
      cryptoProvider
    )
  } catch (err) {
    return new Response(err.message, { status: 400 })
  }
  
  console.log(`Event received: ${event.id}`)
  
  // Process the event
  switch (event.type) {
    case 'payment_intent.succeeded':
      // Handle successful payment
      break
    case 'customer.subscription.created':
      // Handle new subscription
      break
  }
  
  return new Response(JSON.stringify({ ok: true }), { status: 200 })
})

OpenAI Integration

Stream completions from OpenAI:
// supabase/functions/openai/index.ts
import 'https://deno.land/x/xhr@0.3.0/mod.ts'
import { CreateCompletionRequest } from 'https://esm.sh/openai@3.1.0'

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

  const completionConfig: CreateCompletionRequest = {
    model: 'text-davinci-003',
    prompt: query,
    max_tokens: 256,
    temperature: 0,
    stream: true
  }

  return fetch('https://api.openai.com/v1/completions', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(completionConfig)
  })
})

RESTful API with Database Access

Create a full REST API with RLS:
// supabase/functions/restful-tasks/index.ts
import { createClient } from 'npm:supabase-js@2'
import { corsHeaders } from 'jsr:@supabase/supabase-js@2/cors'

Deno.serve(async (req) => {
  const { url, method } = req

  // Handle CORS
  if (method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  try {
    // Create client with user's auth context
    const supabaseClient = createClient(
      Deno.env.get('SUPABASE_URL') ?? '',
      Deno.env.get('SUPABASE_ANON_KEY') ?? '',
      {
        global: {
          headers: { Authorization: req.headers.get('Authorization')! }
        }
      }
    )

    // Parse URL pattern
    const taskPattern = new URLPattern({ pathname: '/restful-tasks/:id' })
    const matchingPath = taskPattern.exec(url)
    const id = matchingPath?.pathname.groups.id

    // Handle different HTTP methods
    switch (true) {
      case id && method === 'GET':
        const { data: task } = await supabaseClient
          .from('tasks')
          .select('*')
          .eq('id', id)
          .single()
        return new Response(JSON.stringify({ task }), {
          headers: { ...corsHeaders, 'Content-Type': 'application/json' }
        })
        
      case method === 'POST':
        const { task: newTask } = await req.json()
        await supabaseClient.from('tasks').insert(newTask)
        return new Response(JSON.stringify({ task: newTask }), {
          headers: { ...corsHeaders, 'Content-Type': 'application/json' }
        })
        
      default:
        return new Response('Not found', { status: 404 })
    }
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 400
    })
  }
})

Core Concepts

Deno Runtime

Edge Functions run on Deno, which provides:
  • TypeScript native: No build step required
  • Secure by default: Explicit permissions for file, network, and environment access
  • Web standard APIs: Fetch, Request, Response, URL, etc.
  • NPM and Deno modules: Import from both ecosystems
// Import from Deno
import { serve } from 'https://deno.land/std@0.170.0/http/server.ts'

// Import from NPM
import { createClient } from 'npm:supabase-js@2'

// Import from JSR
import { corsHeaders } from 'jsr:@supabase/supabase-js@2/cors'

Environment Variables

Access secrets and configuration:
const apiKey = Deno.env.get('OPENAI_API_KEY')
const supabaseUrl = Deno.env.get('SUPABASE_URL') // Auto-provided
const supabaseKey = Deno.env.get('SUPABASE_ANON_KEY') // Auto-provided

Authentication Context

Access the authenticated user:
import { createClient } from 'npm:supabase-js@2'

Deno.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 authenticated user
  const { data: { user } } = await supabaseClient.auth.getUser()
  
  if (!user) {
    return new Response('Unauthorized', { status: 401 })
  }
  
  // User is authenticated, proceed with logic
  return new Response(JSON.stringify({ userId: user.id }))
})

Best Practices

Error Handling

Always handle errors gracefully:
Deno.serve(async (req) => {
  try {
    const data = await processRequest(req)
    return new Response(JSON.stringify(data), {
      headers: { 'Content-Type': 'application/json' }
    })
  } catch (error) {
    console.error('Error:', error)
    return new Response(
      JSON.stringify({ error: error.message }),
      { 
        status: 500,
        headers: { 'Content-Type': 'application/json' }
      }
    )
  }
})

CORS Configuration

Handle browser requests properly:
import { corsHeaders } from 'jsr:@supabase/supabase-js@2/cors'

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

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

Performance Optimization

  • Minimize cold starts: Keep functions small and focused
  • Cache expensive operations: Use in-memory caching for repeated requests
  • Stream large responses: Use streaming for large data
  • Parallel processing: Use Promise.all for independent operations

Limits and Pricing

Check your project’s Edge Functions limits in the Supabase Dashboard under Settings > Usage.

Common Limits

  • Execution time: 150 seconds max (Pro plan), 10 seconds (Free plan)
  • Memory: 512 MB per function
  • Request size: 10 MB max
  • Response size: 10 MB max

Billing

  • Free tier: 500K function invocations/month
  • Pro plan: 2M invocations/month included
  • Additional: $2 per 1M invocations

Next Steps

Quickstart

Build and deploy your first Edge Function

Deploy Functions

Learn deployment strategies and CI/CD

Environment Variables

Manage secrets and configuration

Debugging

Debug and monitor your functions

Resources