| name | Supabase Edge Functions |
| description | Create Supabase Edge Functions with Deno, TypeScript, JWT auth, database connections, and AI integration. Use when creating serverless functions, webhooks, or API endpoints on Supabase. |
| version | 1.0.0 |
Supabase Edge Functions
This skill teaches agents how to create and deploy Supabase Edge Functions following best practices for Deno, TypeScript, authentication, and AI integration.
When to Use
- When creating serverless functions on Supabase
- When implementing webhooks or API endpoints
- When integrating Gemini AI agents (ProfileExtractor, ProfileEnhancer, etc.)
- When handling authentication and authorization
- When connecting to Supabase database from Edge Functions
Core Principles
Deno Runtime
Use Web APIs and Deno Core APIs:
- Use
fetch instead of Axios
- Use WebSockets API instead of node-ws
- Use
Deno.serve instead of serve from deno.land/std
- Prefer Deno APIs over Node APIs when available
Use Node APIs when needed:
- Import with
node: specifier: import process from "node:process"
- Use when Deno APIs have gaps
- Common Node APIs:
node:crypto, node:http, node:process
Import Patterns
Required: Use npm: or jsr: specifiers
- ✅
import { GoogleGenAI } from "npm:@google/genai"
- ✅
import express from "npm:express@4.18.2"
- ❌
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
- ❌ Bare specifiers without prefix
Always specify versions:
- ✅
npm:express@4.18.2
- ❌
npm:express (version required)
Preferred sources (in order):
npm: and jsr: (preferred)
node: for Node built-ins
- Minimize
@deno.land/x, esm.sh, @unpkg.com
Shared Utilities
Location: supabase/functions/_shared/
- Add reusable utilities to
_shared directory
- Import using relative paths:
import { helper } from "../_shared/helper.ts"
- NO cross-dependencies between Edge Functions
- Each function is independent
Environment Variables
Pre-populated (no manual setup):
SUPABASE_URL
SUPABASE_PUBLISHABLE_OR_ANON_KEY
SUPABASE_SERVICE_ROLE_KEY
SUPABASE_DB_URL
Custom secrets:
supabase secrets set --env-file path/to/env-file
Access in code:
const apiKey = Deno.env.get('GEMINI_API_KEY');
Function Structure
Basic Function Template
console.info('server started');
Deno.serve(async (req: Request) => {
try {
if (req.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
},
});
}
const payload = await req.json();
return new Response(JSON.stringify({ success: true }), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
} catch (error: any) {
console.error('Error:', error);
return new (.({ : error. }), {
: ,
: { : },
});
}
});
CORS Handling
Always handle OPTIONS requests:
if (req.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
},
});
}
Authentication
JWT Validation:
import { createClient } from 'npm:@supabase/supabase-js@2';
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
);
const authHeader = req.headers.get('Authorization');
const token = authHeader?.replace('Bearer ', '');
const { data: { user }, error } = await supabase.auth.getUser(token ?? '');
if (error || !user) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
AI Integration
Gemini AI Setup
Import SDK:
import { GoogleGenAI } from "npm:@google/genai";
Initialize Client:
const ai = new GoogleGenAI({ apiKey: Deno.env.get('GEMINI_API_KEY') });
Common Patterns:
- Use
gemini-2.5-flash-preview for fast tasks
- Use
gemini-3-pro-preview for complex reasoning
- Always use Structured Output for predictable responses
- Use URL Context for website/LinkedIn enrichment
Structured Output Example
import { Type } from "npm:@google/genai";
const schema = {
type: Type.OBJECT,
properties: {
name: { type: Type.STRING },
industry: { type: Type.STRING },
description: { type: Type.STRING },
},
required: ['name', 'industry'],
};
const response = await ai.models.generateContent({
model: 'gemini-2.5-flash-preview',
contents: prompt,
config: {
responseMimeType: "application/json",
responseSchema: schema,
},
});
const result = JSON.parse(response.text || "{}");
URL Context for Enrichment
const response = await ai.models.generateContent({
model: "gemini-2.5-flash-preview",
contents: [`Analyze ${url} and extract company information`],
config: {
tools: [{ urlContext: {} }],
},
});
Database Connections
Supabase Client
Service Role Client (for admin operations):
import { createClient } from 'npm:@supabase/supabase-js@2';
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
);
Direct Database Connection:
import { Client } from 'npm:pg@8.11.3';
const client = new Client({
connectionString: Deno.env.get('SUPABASE_DB_URL'),
});
await client.connect();
const result = await client.query('SELECT * FROM startups WHERE id = $1', [id]);
await client.end();
File Operations
Write Operations: ONLY /tmp directory
const tempPath = `/tmp/${filename}`;
await Deno.writeTextFile(tempPath, content);
await Deno.writeTextFile('./output.txt', content);
Routing with Hono or Express
Recommended: Use Hono or Express for multiple routes
import { Hono } from 'npm:hono@4';
const app = new Hono();
app.post('/function-name/extract', async (c) => {
});
app.post('/function-name/enrich', async (c) => {
});
Deno.serve(app.fetch);
Important: Each route must be prefixed with /function-name for correct routing.
Background Tasks
Use EdgeRuntime.waitUntil for long-running tasks:
const response = new Response(JSON.stringify({ accepted: true }), {
headers: { 'Content-Type': 'application/json' },
});
EdgeRuntime.waitUntil(
async () => {
await processData();
}
);
return response;
Note: Do NOT assume EdgeRuntime.waitUntil is available in all contexts.
Error Handling
Always use try-catch:
try {
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' },
});
} catch (error: any) {
console.error('Error:', error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
Deployment
Local Testing
supabase start
supabase functions serve function-name
curl -X POST http://localhost:54321/functions/v1/function-name \
-H "Authorization: Bearer $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"test": "data"}'
Deploy to Production
supabase functions deploy function-name
supabase secrets set GEMINI_API_KEY=your-key
supabase functions deploy function-name
supabase functions logs function-name
Common Patterns
AI Agent Function Pattern
Deno.serve(async (req: Request) => {
const { action, context } = await req.json();
switch (action) {
case 'wizard_extract_startup':
return await extractStartup(context);
case 'enrich_profile':
return await enrichProfile(context);
default:
return new Response(JSON.stringify({ error: 'Unknown action' }), {
status: 400,
});
}
});
Cost Tracking Pattern
await supabase.from('ai_runs').insert({
action: 'extract_startup',
input_tokens: usage.promptTokenCount,
output_tokens: usage.candidatesTokenCount,
cost: calculateCost(usage),
org_id: user.org_id,
});
Best Practices
✅ DO
- Use
Deno.serve for request handling
- Handle CORS with OPTIONS method
- Validate JWT for authenticated routes
- Use structured output for AI responses
- Handle errors with try-catch
- Log important operations
- Use
_shared directory for utilities
- Specify versions for npm packages
❌ DON'T
- Don't use bare specifiers for imports
- Don't write files outside
/tmp
- Don't expose API keys to frontend
- Don't skip authentication checks
- Don't forget CORS handling
- Don't use
deno.land/std serve (use Deno.serve)
- Don't create cross-dependencies between functions
Reference
- Rules:
.cursor/rules/supabase/writing-supabase-edge-functions.mdc
- Prompts:
prompts/14-edge-functions-setup.md
- Supabase Docs: Edge Functions
Created: 2025-01-16
Based on: Supabase Edge Functions best practices
Version: 1.0.0