Supabase Edge Function Project Structure
TypeScript edge functions on Deno with Supabase client, shared code, and local dev workflow.
Project Directory
supabase/
functions/
Edge functions directory
hello-world/
Example function
index.ts
Function entry point
send-email/
Email sending function
index.ts
stripe-webhook/
Payment webhooks
index.ts
_shared/
Shared code (underscore = not deployed)
supabase-client.ts
Initialized client
cors.ts
CORS headers helper
types.ts
Shared type definitions
supabase/
Supabase config
config.toml
Project configuration
migrations/
Database migrations
20240101000000_init.sql
seed.sql
Seed data
.env.local
Local secrets
import_map.json
Deno import aliases
deno.json
Deno workspace config
README.md
Why This Structure?
Each function lives in its own folder under functions/. The _shared/ folder (prefixed with underscore) contains reusable code that won't be deployed as a separate function. Functions run on Deno Deploy's edge network for low latency globally.
Key Directories
- functions/-Each subfolder becomes a deployed function
- functions/_shared/-Shared utilities, not deployed independently
- supabase/-Database migrations and project config
Basic Edge Function
import { serve } from "https://deno.land/std/http/server.ts"
import { createClient } from "jsr:@supabase/supabase-js@2"
import { corsHeaders } from "../_shared/cors.ts"
serve(async (req) => {
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
)
const { data } = await supabase.from("users").select()
return new Response(JSON.stringify(data), {
headers: { ...corsHeaders, "Content-Type": "application/json" }
})
})
Getting Started
supabase initto create projectsupabase functions new hello-worldsupabase startfor local dev with Dockersupabase functions serveto test locallysupabase functions deploy hello-world
Best Practices
- Use
_shared/for common code like CORS and clients - Keep functions small and focused (one responsibility)
- Use
Deno.env.get()for secrets, never hardcode - Return proper CORS headers for browser requests
- Use
import_map.jsonfor cleaner imports
When To Use This
- Serverless API endpoints without managing servers
- Webhook handlers for Stripe, GitHub, etc.
- Background tasks triggered by database changes
- Auth-related logic like custom JWT claims
- Integrating third-party services securely
Trade-offs
- Cold starts-First request may be slower (~50-200ms)
- Execution limits-2-second wall time for free tier
- Deno only-No Node.js APIs, use Deno or npm: specifier
Local Development
Run supabase start to spin up local Postgres, Auth, Storage, and Edge Functions. Use supabase functions serve --env-file .env.local to test functions with hot reload.