Skip to main content

Prerequisites

Before you begin, make sure you have:

Install Supabase CLI

brew install supabase/tap/supabase
Verify installation:
supabase --version

Create a Function

Initialize Your Project

If you haven’t already, initialize Supabase in your project:
supabase init
This creates a supabase directory with the following structure:
supabase/
├── config.toml
└── functions/

Create Your First Function

Create a new function called hello-world:
supabase functions new hello-world
This creates:
supabase/functions/
└── hello-world/
    └── index.ts

Write Function Code

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

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

Test Locally

Start Supabase Services

Start the local Supabase stack (requires Docker):
supabase start
This starts:
  • PostgreSQL database
  • Auth server
  • Realtime server
  • Storage server
  • Edge Functions runtime

Serve Your Function

Start the function locally:
supabase functions serve hello-world
You should see:
Serving hello-world at http://localhost:54321/functions/v1/hello-world

Test with curl

In another terminal, invoke the function:
curl -L -X POST 'http://localhost:54321/functions/v1/hello-world' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Functions"}'
Response:
{
  "message": "Hello Functions!",
  "timestamp": "2024-03-04T10:30:00.000Z"
}

Test with JavaScript

Create a test client:
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'http://localhost:54321',
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs'
)

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

if (error) {
  console.error('Error:', error)
} else {
  console.log('Response:', data)
}

Deploy to Production

Login to Supabase

Authenticate with your Supabase account:
supabase login
This opens a browser to generate an access token. Link to your Supabase project:
supabase link --project-ref your-project-ref
Find your project ref in the Supabase Dashboard under Settings > General.

Deploy the Function

Deploy your function:
supabase functions deploy hello-world
Output:
Deployed function hello-world in 2.3s
  https://your-project.supabase.co/functions/v1/hello-world

Test Production Function

Invoke the deployed function:
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":"Production"}'
Replace YOUR_ANON_KEY with your project’s anon key from Settings > API in the Dashboard.

Add Business Logic

Connect to Database

Access your Supabase database from the function:
import { createClient } from 'npm:supabase-js@2'

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

    // Query the database
    const { data, error } = await supabaseClient
      .from('users')
      .select('*')
      .limit(10)

    if (error) throw error

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

Add Authentication

Enforce authentication in your function:
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 authenticated user
  const { data: { user }, error: authError } = await supabaseClient.auth.getUser()

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

  // User is authenticated
  return new Response(
    JSON.stringify({ 
      message: `Hello ${user.email}!`,
      userId: user.id
    }),
    { headers: { 'Content-Type': 'application/json' } }
  )
})

Handle CORS

Enable CORS for browser requests:
import { corsHeaders } from 'jsr:@supabase/supabase-js@2/cors'

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

  try {
    const { name } = await req.json()
    
    return new Response(
      JSON.stringify({ message: `Hello ${name}!` }),
      { 
        headers: { 
          ...corsHeaders, 
          'Content-Type': 'application/json' 
        }
      }
    )
  } catch (error) {
    return new Response(
      JSON.stringify({ error: error.message }),
      { 
        status: 400,
        headers: { 
          ...corsHeaders, 
          'Content-Type': 'application/json' 
        }
      }
    )
  }
})

Real-World Example: Send Email

Create a function to send emails with Resend:

Create the Function

supabase functions new send-email

Write the Code

// supabase/functions/send-email/index.ts
const RESEND_API_KEY = Deno.env.get('RESEND_API_KEY')

Deno.serve(async (req) => {
  try {
    const { to, subject, html } = await req.json()

    const res = await fetch('https://api.resend.com/emails', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${RESEND_API_KEY}`
      },
      body: JSON.stringify({
        from: 'onboarding@yourdomain.com',
        to,
        subject,
        html
      })
    })

    const data = await res.json()

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

Set Environment Variable

supabase secrets set RESEND_API_KEY=your_resend_api_key

Deploy

supabase functions deploy send-email

Invoke

const { data, error } = await supabase.functions.invoke('send-email', {
  body: {
    to: 'user@example.com',
    subject: 'Welcome!',
    html: '<h1>Welcome to our app!</h1>'
  }
})

Development Workflow

Watch Mode

Auto-reload on file changes:
supabase functions serve hello-world --no-verify-jwt
The --no-verify-jwt flag skips JWT verification for easier local testing.

Use Environment Variables Locally

Create .env.local:
# supabase/.env.local
RESEND_API_KEY=your_api_key
OPENAI_API_KEY=your_openai_key
Serve with environment variables:
supabase functions serve --env-file ./supabase/.env.local

Test with Postman

Import this cURL command into Postman:
curl -L -X POST 'http://localhost:54321/functions/v1/hello-world' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Postman"}'

Next Steps

Deploy Functions

Learn advanced deployment strategies and CI/CD

Environment Variables

Manage secrets and configuration

Debugging

Debug and monitor your functions

Troubleshooting

Function Not Found

If you get a 404 error:
  1. Check the function name matches the directory name
  2. Verify deployment: supabase functions list
  3. Ensure you’re using the correct URL

CORS Errors

If browser requests fail:
  1. Add CORS headers to your response
  2. Handle OPTIONS requests
  3. Include the Authorization header in CORS config

Authentication Errors

If auth doesn’t work:
  1. Verify you’re passing the Authorization header
  2. Check the JWT token is valid
  3. Use --no-verify-jwt for local testing

Resources