Building and deploying Supabase Edge Functions: project structure, environment variables, CORS handling, client invocation, and error handling.
Building and deploying Supabase Edge Functions: project structure, environment variables, CORS handling, client invocation, and error handling.
BeforeMerge offers hundreds of code review rules, guides, and detection patterns to help your team ship better code.
Edge Functions are server-side TypeScript functions that run on Deno Deploy, distributed globally at the edge.
supabase/
functions/
my-function/
index.ts # Entry point (required)
shared/
cors.ts # Shared utilities
types.tsEach function lives in its own directory under supabase/functions/.
import { serve } from "https://deno.land/std@0.177.0/http/server.ts";
serve(async (req: Request) => {
const { name } = await req.json();
return new Response(
JSON.stringify({ message: `Hello, ${name}!` }),
{ headers: { "Content-Type": "application/json" } }
);
});Create a shared CORS utility:
// supabase/functions/shared/cors.ts
export const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};Use in your function:
import { corsHeaders } from "../shared/cors.ts";
serve(async (req: Request) => {
// Handle CORS preflight
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
// Your logic here
return new Response(
JSON.stringify({ data: "result" }),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
});Set secrets via CLI:
supabase secrets set MY_API_KEY=sk-xxx
supabase secrets listAccess in code:
const apiKey = Deno.env.get("MY_API_KEY");Built-in variables (always available):
SUPABASE_URLSUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYSUPABASE_DB_URLconst { data, error } = await supabase.functions.invoke("my-function", {
body: { name: "World" },
});The client automatically sends the user's auth token in the Authorization header.
serve(async (req: Request) => {
try {
const body = await req.json();
if (!body.email) {
return new Response(
JSON.stringify({ error: "email is required" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}
// process...
return new Response(JSON.stringify({ success: true }), {
headers: { "Content-Type": "application/json" },
});
} catch (err) {
return new Response(
JSON.stringify({ error: "Internal server error" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
});# Deploy a single function
supabase functions deploy my-function
# Deploy all functions
supabase functions deploy
# Local development
supabase functions serve my-function --env-file .env.localcurl -i --location --request POST \
http://localhost:54321/functions/v1/my-function \
--header 'Authorization: Bearer YOUR_ANON_KEY' \
--header 'Content-Type: application/json' \
--data '{"name": "World"}'