Supabase Edge Functions
When to use
- Create new Edge Functions (Deno/TypeScript)
- Deploy or update existing functions via MCP
- Debug Edge Function errors using logs
- Structure shared utilities across functions
- Implement auth validation, CORS, error handling
- Connect Edge Functions to Postgres, Storage, Auth APIs
- Design service-based function architecture
Architecture
How Edge Functions Work
- Request enters edge gateway (handles routing, JWT validation)
- Auth & policies applied (rate limits, security checks)
- Edge runtime executes function on nearest node
- Function calls Supabase APIs or third-party services
- Response returns via gateway with request metadata logged
File Structure (Service-Based)
supabase/functions/
├── _shared/ # Shared across ALL functions
│ ├── clients/
│ │ └── supabaseAdmin.ts # Admin client singleton
│ ├── utils/
│ │ ├── auth.ts # JWT/auth helpers
│ │ ├── responses.ts # Standard response builders
│ │ ├── logger.ts # Structured logging
│ │ └── validation.ts # Input validators
│ ├── services/
│ │ └── notification.ts # FCM/notification helpers
│ └── config.ts # Runtime configuration
├── auth_service/
│ └── index.ts # Auth operations
├── fart_service/
│ └── index.ts # Core domain logic
├── social_service/
│ └── index.ts # Friends, blocking
└── cron_service/
└── index.ts # Scheduled jobs
Function Template
Basic Handler
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
Deno.serve(async (req: Request) => {
// Handle CORS preflight
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
try {
const { action, ...params } = await req.json();
switch (action) {
case "create":
return await handleCreate(req, params);
case "list":
return await handleList(req, params);
default:
return new Response(
JSON.stringify({ error: "Unknown action" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
});
Auth-Validated Handler
import { createClient } from "jsr:@supabase/supabase-js@2";
async function getAuthUser(req: Request) {
const authHeader = req.headers.get("Authorization");
if (!authHeader) throw new Error("Missing authorization header");
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_ANON_KEY")!,
{ global: { headers: { Authorization: authHeader } } }
);
const { data: { user }, error } = await supabase.auth.getUser();
if (error || !user) throw new Error("Unauthorized");
return { supabase, user };
}
Admin Client (Service Role)
import { createClient } from "jsr:@supabase/supabase-js@2";
export const supabaseAdmin = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
{ auth: { autoRefreshToken: false, persistSession: false } }
);
Deployment via MCP
deploy_edge_function({
project_id: "your-project-ref",
name: "my_function",
entrypoint_path: "index.ts",
verify_jwt: true, // ALWAYS true unless custom auth
files: [
{ name: "index.ts", content: "..." },
{ name: "deno.json", content: "..." } // include if exists
]
})
Debugging
- Check logs:
get_logs(service: "edge-function") for last 24h
- Common errors:
Boot failure — syntax error or bad import
Worker exceeded — function timed out (default 150s wall, 50s CPU for free tier)
413 — request body too large (default 2MB, configurable to 150MB)
- Environment variables: Set in Dashboard > Edge Functions > Secrets
- Local testing:
supabase functions serve my_function --env-file .env.local
Best Practices
- One service function can handle multiple actions via
action parameter routing
- Share code via
_shared/ directory (imported with relative paths)
- Use structured JSON responses:
{ data, error, message }
- Always handle CORS preflight for browser clients
- Use
SUPABASE_SERVICE_ROLE_KEY only in server-side functions, never expose to client
- Set
verify_jwt: true and validate auth in the function body for defense in depth
- Prefer database triggers over Edge Functions for simple counter updates
- For webhooks (Stripe, Apple S2S): disable JWT verification but implement signature validation
1---2name: supabase-edge-functions3description: Use for creating, deploying, debugging, and optimizing Supabase Edge Functions. Covers Deno runtime, request handling, auth validation, CORS, shared utilities, service-based architecture, and the deploy_edge_function MCP tool.4---56# Supabase Edge Functions78## When to use9- Create new Edge Functions (Deno/TypeScript)10- Deploy or update existing functions via MCP11- Debug Edge Function errors using logs12- Structure shared utilities across functions13- Implement auth validation, CORS, error handling14- Connect Edge Functions to Postgres, Storage, Auth APIs15- Design service-based function architecture1617## Architecture1819### How Edge Functions Work201. Request enters edge gateway (handles routing, JWT validation)212. Auth & policies applied (rate limits, security checks)223. Edge runtime executes function on nearest node234. Function calls Supabase APIs or third-party services245. Response returns via gateway with request metadata logged2526### File Structure (Service-Based)27```28supabase/functions/29├── _shared/ # Shared across ALL functions30│ ├── clients/31│ │ └── supabaseAdmin.ts # Admin client singleton32│ ├── utils/33│ │ ├── auth.ts # JWT/auth helpers34│ │ ├── responses.ts # Standard response builders35│ │ ├── logger.ts # Structured logging36│ │ └── validation.ts # Input validators37│ ├── services/38│ │ └── notification.ts # FCM/notification helpers39│ └── config.ts # Runtime configuration40├── auth_service/41│ └── index.ts # Auth operations42├── fart_service/43│ └── index.ts # Core domain logic44├── social_service/45│ └── index.ts # Friends, blocking46└── cron_service/47 └── index.ts # Scheduled jobs48```4950## Function Template5152### Basic Handler53```typescript54import "jsr:@supabase/functions-js/edge-runtime.d.ts";5556const corsHeaders = {57 "Access-Control-Allow-Origin": "*",58 "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",59};6061Deno.serve(async (req: Request) => {62 // Handle CORS preflight63 if (req.method === "OPTIONS") {64 return new Response("ok", { headers: corsHeaders });65 }6667 try {68 const { action, ...params } = await req.json();6970 switch (action) {71 case "create":72 return await handleCreate(req, params);73 case "list":74 return await handleList(req, params);75 default:76 return new Response(77 JSON.stringify({ error: "Unknown action" }),78 { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }79 );80 }81 } catch (error) {82 return new Response(83 JSON.stringify({ error: error.message }),84 { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }85 );86 }87});88```8990### Auth-Validated Handler91```typescript92import { createClient } from "jsr:@supabase/supabase-js@2";9394async function getAuthUser(req: Request) {95 const authHeader = req.headers.get("Authorization");96 if (!authHeader) throw new Error("Missing authorization header");9798 const supabase = createClient(99 Deno.env.get("SUPABASE_URL")!,100 Deno.env.get("SUPABASE_ANON_KEY")!,101 { global: { headers: { Authorization: authHeader } } }102 );103104 const { data: { user }, error } = await supabase.auth.getUser();105 if (error || !user) throw new Error("Unauthorized");106 return { supabase, user };107}108```109110### Admin Client (Service Role)111```typescript112import { createClient } from "jsr:@supabase/supabase-js@2";113114export const supabaseAdmin = createClient(115 Deno.env.get("SUPABASE_URL")!,116 Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,117 { auth: { autoRefreshToken: false, persistSession: false } }118);119```120121## Deployment via MCP122123```124deploy_edge_function({125 project_id: "your-project-ref",126 name: "my_function",127 entrypoint_path: "index.ts",128 verify_jwt: true, // ALWAYS true unless custom auth129 files: [130 { name: "index.ts", content: "..." },131 { name: "deno.json", content: "..." } // include if exists132 ]133})134```135136## Debugging1371381. **Check logs**: `get_logs(service: "edge-function")` for last 24h1392. **Common errors**:140 - `Boot failure` — syntax error or bad import141 - `Worker exceeded` — function timed out (default 150s wall, 50s CPU for free tier)142 - `413` — request body too large (default 2MB, configurable to 150MB)1433. **Environment variables**: Set in Dashboard > Edge Functions > Secrets1444. **Local testing**: `supabase functions serve my_function --env-file .env.local`145146## Best Practices147- One service function can handle multiple actions via `action` parameter routing148- Share code via `_shared/` directory (imported with relative paths)149- Use structured JSON responses: `{ data, error, message }`150- Always handle CORS preflight for browser clients151- Use `SUPABASE_SERVICE_ROLE_KEY` only in server-side functions, never expose to client152- Set `verify_jwt: true` and validate auth in the function body for defense in depth153- Prefer database triggers over Edge Functions for simple counter updates154- For webhooks (Stripe, Apple S2S): disable JWT verification but implement signature validation