Overview
Edge Functions are serverless TypeScript functions that run on Supabase’s global edge network using the Deno runtime. They handle complex operations that can’t be done with database queries alone, such as AI-powered features, push notifications, email delivery, and data processing.
Global Edge Low-latency execution from 30+ regions worldwide
TypeScript Full TypeScript support with Deno runtime
Secure JWT validation and RLS integration
Scalable Auto-scaling with pay-per-invocation pricing
Invoking Functions
All Edge Functions are invoked via HTTP POST requests to the functions endpoint.
Base URL
https://your-project.supabase.co/functions/v1/{function-name}
curl -X POST 'https://your-project.supabase.co/functions/v1/function-name' \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"key": "value"}'
Most functions require a valid user JWT token. Some scheduled functions (like check-pending-submissions) require the service role key instead.
Available Functions
Auction Excellence provides 8 Edge Functions:
Function Purpose Trigger import-csvImport CSV datasets with streaming progress Manual generate-sqlConvert natural language to SQL Manual execute-querySafe SQL execution with caching Manual send-push-notificationsSend push notifications via Expo Manual/Trigger send-chat-notificationChat message notifications Database trigger check-pending-submissionsCheck for overdue submissions pg_cron schedule generate-invite-linkGenerate shareable invite links Manual send-invite-emailSend invitation emails via Resend Manual
import-csv
Process uploaded CSV files with streaming progress updates.
Overview
Request
Response (SSE)
JavaScript Example
Purpose: Import CSV datasets into the reporting system with real-time progress tracking.Features:
Server-Sent Events (SSE) for real-time progress
Automatic data type detection and transformation
Column mapping with calculated date fields
Batch processing (default 1000 rows per batch)
50MB file size limit
Error tracking with row-level details
Trigger: Manual invocation from admin dashboardMethod: POSTEndpoint: /functions/v1/import-csvHeaders: Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json
Accept: text/event-stream # Required for SSE
Body: {
"dataset_id" : "uuid" ,
"storage_path" : "uploads/data.csv" ,
"column_mapping" : [
{
"csv_index" : 0 ,
"target_field" : "sale_date" ,
"data_type" : "date"
},
{
"csv_index" : 1 ,
"target_field" : "amount" ,
"data_type" : "number"
},
{
"csv_index" : 2 ,
"target_field" : "vin_number" ,
"data_type" : "text"
}
],
"options" : {
"skip_header" : true ,
"batch_size" : 1000 ,
"calculate_fields" : true
}
}
Column Data Types: Type Description textString values numberNumeric values (integers or decimals) dateDate values (YYYY-MM-DD) datetimeTimestamp values (ISO 8601) booleanBoolean values (true/false)
The function streams progress events: Progress Event: event: progress
data: {"processed": 1000, "total": 5000, "percent": 20}
event: progress
data: {"processed": 2000, "total": 5000, "percent": 40, "errors_count": 2}
Complete Event: event: complete
data: {"success": true, "rows_imported": 5000, "duration_ms": 3456, "errors_count": 2}
Error Event: event: error
data: {"error": "Dataset not found: uuid", "details": "No rows returned"}
import { createClient } from '@supabase/supabase-js'
const supabase = createClient ( url , anonKey )
async function importCSV (
datasetId : string ,
storagePath : string ,
columnMapping : ColumnMapping []
) {
const { data : { session } } = await supabase . auth . getSession ()
const response = await fetch (
` ${ supabaseUrl } /functions/v1/import-csv` ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ session ?. access_token } ` ,
'Content-Type' : 'application/json' ,
'Accept' : 'text/event-stream'
},
body: JSON . stringify ({
dataset_id: datasetId ,
storage_path: storagePath ,
column_mapping: columnMapping ,
options: { skip_header: true }
})
}
)
const reader = response . body ?. getReader ()
const decoder = new TextDecoder ()
while ( true ) {
const { done , value } = await reader ! . read ()
if ( done ) break
const chunk = decoder . decode ( value )
const lines = chunk . split ( ' \n ' )
for ( const line of lines ) {
if ( line . startsWith ( 'data: ' )) {
const data = JSON . parse ( line . slice ( 6 ))
console . log ( 'Progress:' , data . percent + '%' )
}
}
}
}
generate-sql
Convert natural language queries to SQL using OpenAI GPT-4.
Overview
Request
Response
Error Responses
JavaScript Example
Purpose: Enable users to query datasets using plain English.Features:
Natural language to SQL conversion via GPT-4
Conversational history for follow-up questions (max 20 messages)
SQL validation and safety checks
Visualization type suggestions (bar, line, pie, area, table)
Query logging for audit
Schema-aware prompts for accurate queries
AI Model: OpenAI GPT-4 Turbo PreviewMethod: POSTEndpoint: /functions/v1/generate-sqlBody: {
"dataset_id" : "uuid" ,
"natural_language_query" : "Show me total sales by month for 2024" ,
"conversation_history" : [
{
"role" : "user" ,
"content" : "What are the top selling categories?"
},
{
"role" : "assistant" ,
"content" : "SELECT category, SUM(amount) as total..."
}
]
}
Constraints:
natural_language_query: Max 2000 characters
conversation_history: Max 20 messages
{
"sql" : "SELECT DATE_TRUNC('month', sale_date) as month, SUM(sale_amount) as total FROM report_data_rows WHERE dataset_id = 'uuid' AND EXTRACT(YEAR FROM sale_date) = 2024 GROUP BY month ORDER BY month" ,
"explanation" : "This query groups sales by month for 2024 and calculates the total amount for each month." ,
"visualization_suggestion" : {
"chart_type" : "line" ,
"x_axis" : "month" ,
"y_axis" : "total"
},
"tokens_used" : 456 ,
"validation_warnings" : []
}
Dataset Not Found: {
"error" : "Dataset not found or access denied" ,
"code" : "NOT_FOUND"
}
LLM Error: {
"error" : "Failed to generate SQL query" ,
"code" : "LLM_ERROR" ,
"details" : "OpenAI API timeout"
}
Validation Error: {
"error" : "Generated SQL failed validation" ,
"code" : "VALIDATION_ERROR" ,
"details" : "Query contains disallowed operation: DROP"
}
async function generateSQL ( datasetId : string , query : string ) {
const response = await supabase . functions . invoke ( 'generate-sql' , {
body: {
dataset_id: datasetId ,
natural_language_query: query ,
conversation_history: []
}
})
if ( response . error ) {
throw new Error ( response . error . message )
}
return response . data
}
// Usage
const result = await generateSQL (
'dataset-uuid' ,
'Show me the top 5 locations by car count'
)
console . log ( 'SQL:' , result . sql )
console . log ( 'Chart type:' , result . visualization_suggestion . chart_type )
execute-query
Safely execute SQL queries with RLS enforcement and caching.
Overview
Request
Response
Error Responses
Purpose: Execute user-generated or LLM-generated SQL queries against datasets.Features:
SQL validation and sanitization
RLS enforcement (users can only query their data)
In-memory caching (5-minute TTL)
Rate limiting (10 queries per minute per user)
JSON and CSV export support
Column type detection
Service role support for public report execution
Method: POSTEndpoint: /functions/v1/execute-queryBody: {
"sql" : "SELECT location_name, SUM(car_count) as total FROM report_data_rows WHERE dataset_id = 'uuid' GROUP BY location_name" ,
"dataset_id" : "uuid" ,
"format" : "json" ,
"cache_key" : "optional-cache-key"
}
Parameters: Parameter Type Required Description sqlstring Yes SQL query (max 10000 chars) dataset_idUUID Yes Dataset to query against formatstring No json (default) or csvcache_keystring No Optional key for result caching (max 256 chars)
JSON Format: {
"data" : [
{ "location_name" : "North Lot" , "total" : 4567 },
{ "location_name" : "South Lot" , "total" : 3456 },
{ "location_name" : "East Lot" , "total" : 2345 }
],
"row_count" : 3 ,
"columns" : [
{ "name" : "location_name" , "type" : "text" },
{ "name" : "total" , "type" : "number" }
],
"execution_time_ms" : 45 ,
"cached" : false
}
CSV Format: "location_name", "total"
"North Lot", 4567
"South Lot", 3456
"East Lot", 2345
Validation Error: {
"error" : "SQL validation failed" ,
"code" : "VALIDATION_ERROR" ,
"details" : "Query contains disallowed operation: DROP"
}
Rate Limit: {
"error" : "Rate limit exceeded. Maximum 10 queries per minute." ,
"code" : "RATE_LIMITED"
}
Execution Error: {
"error" : "Query execution failed" ,
"code" : "EXECUTION_ERROR" ,
"details" : "column \" invalid_column \" does not exist"
}
send-push-notifications
Send push notifications to users via Expo Push API.
Overview
Request
Response
Token Validation
Purpose: Deliver push notifications to mobile devices for submission reminders.Features:
Batch processing (max 100 tokens per request)
Automatic invalid token cleanup (DeviceNotRegistered)
iOS and Android support via Expo
Custom title and body support
Notification type categorization
Trigger: Manual invocation or database trigger on submission eventsMethod: POSTEndpoint: /functions/v1/send-push-notificationsBody: {
"notifications" : [
{
"location_id" : "loc-uuid" ,
"location_name" : "North Lot" ,
"tokens" : [
"ExponentPushToken[xxx1]" ,
"ExponentPushToken[xxx2]"
]
}
],
"notification_type" : "submission_reminder" ,
"custom_title" : "Submission Reminder" ,
"custom_body" : "Time to submit count for North Lot"
}
Notification Types: Type Description submission_reminderLot submission reminders (default) quality_reminderQuality inspection reminders generalGeneral notifications
{
"success" : true ,
"sent_at" : "2025-01-15T10:30:00.000Z" ,
"total_messages" : 15 ,
"successful" : 13 ,
"failed" : 2 ,
"invalid_tokens_removed" : 1 ,
"errors" : [
"Expo API error (batch 1): DeviceNotRegistered"
]
}
Valid Expo push token formats:
ExponentPushToken[xxxxxx]
ExpoPushToken[xxxxxx]
Invalid tokens are automatically:
Logged as warnings
Skipped during send
Removed from the push_tokens table if DeviceNotRegistered
send-chat-notification
Send chat message notifications with per-channel preferences.
Overview
Request
Response
Preference Logic
Purpose: Notify users of new chat messages based on their preferences.Features:
Per-channel notification preferences (all, mentions, none)
@mention detection and priority handling
Automatic sender exclusion
Message content truncation (100 chars max)
Deep link support for navigation
Batch processing for large channels
Trigger: Database trigger on message insertMethod: POSTEndpoint: /functions/v1/send-chat-notificationBody: {
"message_id" : "msg-uuid" ,
"channel_id" : "chan-uuid" ,
"auction_id" : "auction-uuid" ,
"sender_id" : "sender-uuid" ,
"sender_name" : "John Doe" ,
"content" : "Hey @jane, check this out!" ,
"channel_name" : "general" ,
"mentions" : [ "user-uuid-jane" ]
}
{
"success" : true ,
"sent" : 5 ,
"errors" : []
}
Preference Behavior allReceive all messages in channel mentionsOnly receive notifications when @mentioned noneNo notifications from this channel
Users are never notified of their own messages. Mentioned users always receive notifications, even if they have none preference.
Notification Format:
Title: {sender_name} in #{channel_name}
Body: {truncated_content} (max 100 chars)
Data: { type: "chat_message", channel_id, message_id }
check-pending-submissions
Scheduled job to check for overdue location submissions.
Overview
Request
Response
pg_cron Configuration
Purpose: Identify locations that haven’t submitted within their required frequency.Features:
Operating hours awareness (respects location schedules)
Submission frequency checking
User and location grouping
Push token aggregation for notifications
Overdue time calculation
Trigger: pg_cron schedule (typically every 15 minutes)Method: POSTEndpoint: /functions/v1/check-pending-submissionsBody: None required (uses current timestamp)Manual Trigger: curl -X POST 'https://your-project.supabase.co/functions/v1/check-pending-submissions' \
-H "Authorization: Bearer SERVICE_ROLE_KEY"
Manual invocation requires the service role key, not a user JWT.
{
"success" : true ,
"checked_at" : "2025-01-15T10:30:00.000Z" ,
"pending_count" : 5 ,
"notifications_to_send" : [
{
"location_id" : "loc-uuid-1" ,
"location_name" : "North Lot" ,
"minutes_overdue" : 45 ,
"users" : [
{
"user_id" : "user-uuid" ,
"user_name" : "John Doe" ,
"user_email" : "[email protected] " ,
"tokens" : [ "ExponentPushToken[xxx]" ]
}
]
}
]
}
-- Schedule check every 15 minutes
SELECT cron . schedule (
'check-pending-submissions' ,
'*/15 * * * *' ,
$$
SELECT net . http_post (
url : = 'https://your-project.supabase.co/functions/v1/check-pending-submissions' ,
headers : = jsonb_build_object(
'Authorization' , 'Bearer ' || current_setting( 'app.service_role_key' ),
'Content-Type' , 'application/json'
)
);
$$
);
-- View job status
SELECT * FROM cron . job_run_details
WHERE jobid = ( SELECT jobid FROM cron . job WHERE jobname = 'check-pending-submissions' )
ORDER BY start_time DESC
LIMIT 10 ;
generate-invite-link
Generate shareable auction invitation links.
Overview
Request
Response
JavaScript Example
Purpose: Create shareable invitation links for onboarding new team members via SMS, Slack, or other channels.Features:
Secure token generation
Role assignment at invite time
7-day default expiration
RLS permission enforcement
Placeholder email for anonymous sharing
Method: POSTEndpoint: /functions/v1/generate-invite-linkBody: {
"auction_id" : "auction-uuid" ,
"role" : "team_member" ,
"email" : "[email protected] "
}
Parameters: Parameter Type Required Default Description auction_idUUID Yes - Target auction rolestring No team_memberowner, admin, or team_memberemailstring No Generated placeholder Recipient email (optional for shareable links)
{
"invite_link" : "https://app.auctionexcellence.com/invite/abc123xyz789" ,
"token" : "abc123xyz789" ,
"expires_at" : "2025-01-22T10:30:00.000Z"
}
async function generateInviteLink ( auctionId : string , role ?: string ) {
const response = await supabase . functions . invoke ( 'generate-invite-link' , {
body: {
auction_id: auctionId ,
role: role || 'team_member'
}
})
if ( response . error ) {
throw new Error ( response . error . message )
}
return response . data . invite_link
}
// Usage - shareable link (no email)
const link = await generateInviteLink ( 'auction-uuid' , 'team_member' )
console . log ( 'Share this link:' , link )
send-invite-email
Send invitation emails to new team members via Resend.
Overview
Request
Response
Email Template
JavaScript Example
Purpose: Send beautifully formatted invitation emails when users are invited to an auction.Features:
Resend email delivery service
Branded HTML email template
Role-aware messaging (Owner, Administrator, Team Member)
Expiration validation
Inviter name personalization
Method: POSTEndpoint: /functions/v1/send-invite-emailBody: {
"invite_id" : "invite-uuid"
}
The function fetches all invite details from the database using the invite_id. It retrieves the email, role, auction name, inviter name, and token automatically.
Success: {
"success" : true ,
"message_id" : "resend-message-id"
}
Error (Expired): {
"success" : false ,
"error" : "Invite has expired"
}
Error (Not Found): {
"success" : false ,
"error" : "Invite not found"
}
The email includes:
Subject: You've been invited to {Auction Name}
Body:
Inviter name and auction name
Role assignment (formatted: Team Member, Administrator, Owner)
Accept Invitation button linking to invite URL
7-day expiration notice
Auction Excellence branding
<!-- Email Preview -->
You've been invited!
John Smith has invited you to join Acme Auto Auction on Auction Excellence.
You'll be joining as a Team Member.
[Accept Invitation]
This invitation expires in 7 days.
// After creating an invite, send the email
async function inviteAndNotify ( auctionId : string , email : string , role : string ) {
// 1. Create the invite
const { data : invite , error } = await supabase . rpc ( 'invite_user_to_auction' , {
p_auction_id: auctionId ,
p_email: email ,
p_role: role
})
if ( error ) throw error
// 2. Send the email
const emailResponse = await supabase . functions . invoke ( 'send-invite-email' , {
body: { invite_id: invite [ 0 ]. invite_id }
})
if ( emailResponse . error ) {
console . error ( 'Email failed:' , emailResponse . error )
}
return invite [ 0 ]
}
Error Handling
All Edge Functions return consistent error responses.
{
"error" : "error_code" ,
"code" : "ERROR_CODE" ,
"details" : "Additional context if available"
}
Common Error Codes
Code HTTP Status Description UNAUTHORIZED401 Missing or invalid JWT token FORBIDDEN403 User lacks permission for operation NOT_FOUND404 Requested resource doesn’t exist INVALID_REQUEST400 Invalid request body or parameters VALIDATION_ERROR400 Input validation failed RATE_LIMITED429 Too many requests LLM_ERROR500 AI service error EXECUTION_ERROR400 SQL execution failed INTERNAL_ERROR500 Unexpected server error METHOD_NOT_ALLOWED405 Wrong HTTP method
Rate Limiting
Edge Functions have per-user rate limits:
Function Limit Window generate-sql30 1 minute execute-query10 1 minute import-csv5 1 hour send-push-notifications100 1 minute Other functions 120 1 minute
Rate limit responses include retry information:
{
"error" : "Rate limit exceeded. Maximum 10 queries per minute." ,
"code" : "RATE_LIMITED"
}
Security
Authentication
Most functions require a valid JWT token in the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Service Role Functions:
check-pending-submissions - Requires service role key (scheduled job)
RLS Integration
Functions that access database tables respect Row Level Security policies. Users can only access data within their auction memberships.
All request bodies are validated. Invalid requests are rejected with detailed error messages.
{
"error" : "dataset_id is required" ,
"code" : "INVALID_REQUEST"
}
SQL Validation
The generate-sql and execute-query functions validate SQL queries to prevent:
Data modification (INSERT, UPDATE, DELETE)
Schema changes (DROP, ALTER, CREATE)
Access to unauthorized tables
SQL injection attacks
Environment Variables
Edge Functions require the following environment variables:
Required for All Functions
Variable Description SUPABASE_URLSupabase project URL SUPABASE_ANON_KEYSupabase anonymous key SUPABASE_SERVICE_ROLE_KEYService role key (for admin operations)
Function-Specific Variables
Variable Functions Description OPENAI_API_KEYgenerate-sqlOpenAI API key for GPT-4 RESEND_API_KEYsend-invite-emailResend API key for email delivery APP_URLgenerate-invite-link, send-invite-emailBase URL for invite links
Local Development
Prerequisites
# Install Supabase CLI
brew install supabase/tap/supabase
# Start local Supabase
cd supabase
supabase start
Serve Functions Locally
# Serve all functions
supabase functions serve
# Serve specific function with env file
supabase functions serve generate-sql --env-file .env.local
Testing Functions
# Test a function locally
curl -X POST 'http://localhost:54321/functions/v1/generate-sql' \
-H "Authorization: Bearer YOUR_LOCAL_JWT" \
-H "Content-Type: application/json" \
-d '{
"dataset_id": "test-uuid",
"natural_language_query": "Show total car count"
}'
Environment Setup
Create a .env.local file in the supabase directory:
SUPABASE_URL=http://localhost:54321
SUPABASE_ANON_KEY=your-local-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-local-service-key
OPENAI_API_KEY=sk-...
RESEND_API_KEY=re_...
APP_URL=http://localhost:3000
Deployment
Deploy All Functions
supabase functions deploy
Deploy Specific Function
supabase functions deploy generate-sql
Set Production Secrets
# Set individual secrets
supabase secrets set OPENAI_API_KEY=sk-...
supabase secrets set RESEND_API_KEY=re_...
supabase secrets set APP_URL=https://app.auctionexcellence.com
# List current secrets
supabase secrets list
Monitoring
View function logs in the Supabase dashboard:
Navigate to Edge Functions in the dashboard
Select a function
View Logs tab for invocation history
Or via CLI:
# Tail logs for a function
supabase functions logs generate-sql --follow
Next Steps