Documentation Index Fetch the complete documentation index at: https://mintlify.com/supabase/supabase/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Debugging Edge Functions involves viewing logs, testing locally, handling errors, and monitoring performance. This guide covers tools and techniques to help you identify and fix issues quickly.
Viewing Logs
CLI Logs
View real-time logs for a function:
supabase functions logs hello-world
View logs with tail:
supabase functions logs hello-world --tail
This streams logs in real-time as requests come in.
Filter Logs
Filter logs by time range:
# Last hour
supabase functions logs hello-world --since 1h
# Last 30 minutes
supabase functions logs hello-world --since 30m
# Last 500 lines
supabase functions logs hello-world --limit 500
Dashboard Logs
View logs in the Supabase Dashboard:
Go to Edge Functions in your project
Click on your function
Navigate to the Logs tab
The dashboard provides:
Searchable logs
Log level filtering (info, warn, error)
Request/response inspection
Performance metrics
Local Development
Serve Locally
Run functions locally for easier debugging:
supabase functions serve hello-world
With environment variables:
supabase functions serve hello-world --env-file ./supabase/.env.local
Skip JWT verification for testing:
supabase functions serve hello-world --no-verify-jwt
Hot Reload
Changes are automatically reloaded when you save files:
supabase functions serve hello-world
# Edit index.ts
# Save file
# Function automatically reloads
Debug with Deno
Use Deno’s built-in debugger:
deno run --inspect-brk --allow-all supabase/functions/hello-world/index.ts
Then connect with Chrome DevTools:
Open chrome://inspect in Chrome
Click “inspect” under your function
Set breakpoints and step through code
Console Logging
Basic Logging
Use console.log() for debugging:
Deno . serve ( async ( req ) => {
console . log ( 'Function invoked' )
const { name } = await req . json ()
console . log ( 'Received name:' , name )
const response = { message: `Hello ${ name } !` }
console . log ( 'Sending response:' , response )
return new Response ( JSON . stringify ( response ))
})
Log Levels
Use different log levels:
Deno . serve ( async ( req ) => {
console . log ( 'Info: Function started' )
console . warn ( 'Warning: Deprecated parameter used' )
console . error ( 'Error: Failed to process request' )
try {
const data = await processRequest ( req )
return new Response ( JSON . stringify ( data ))
} catch ( error ) {
console . error ( 'Error details:' , error )
return new Response ( 'Internal error' , { status: 500 })
}
})
Structured Logging
Log structured data for better analysis:
Deno . serve ( async ( req ) => {
const requestId = crypto . randomUUID ()
console . log ( JSON . stringify ({
level: 'info' ,
requestId ,
timestamp: new Date (). toISOString (),
message: 'Request received' ,
method: req . method ,
url: req . url
}))
try {
const result = await processRequest ( req )
console . log ( JSON . stringify ({
level: 'info' ,
requestId ,
message: 'Request completed' ,
duration: performance . now ()
}))
return new Response ( JSON . stringify ( result ))
} catch ( error ) {
console . error ( JSON . stringify ({
level: 'error' ,
requestId ,
message: 'Request failed' ,
error: error . message ,
stack: error . stack
}))
return new Response ( 'Error' , { status: 500 })
}
})
Error Handling
Try-Catch Blocks
Always wrap async operations:
Deno . serve ( async ( req ) => {
try {
const { data } = await req . json ()
// Validate input
if ( ! data ) {
throw new Error ( 'Missing data field' )
}
// Process request
const result = await processData ( data )
return new Response ( JSON . stringify ( result ), {
headers: { 'Content-Type' : 'application/json' }
})
} catch ( error ) {
console . error ( 'Error:' , error )
return new Response (
JSON . stringify ({
error: error . message ,
timestamp: new Date (). toISOString ()
}),
{
status: 500 ,
headers: { 'Content-Type' : 'application/json' }
}
)
}
})
Custom Error Classes
Create custom error types:
class ValidationError extends Error {
constructor ( message : string ) {
super ( message )
this . name = 'ValidationError'
}
}
class AuthenticationError extends Error {
constructor ( message : string ) {
super ( message )
this . name = 'AuthenticationError'
}
}
Deno . serve ( async ( req ) => {
try {
const { data } = await req . json ()
if ( ! data . email ) {
throw new ValidationError ( 'Email is required' )
}
const user = await authenticateUser ( data . email )
if ( ! user ) {
throw new AuthenticationError ( 'Invalid credentials' )
}
return new Response ( JSON . stringify ({ user }))
} catch ( error ) {
console . error ( ` ${ error . name } : ${ error . message } ` )
if ( error instanceof ValidationError ) {
return new Response (
JSON . stringify ({ error: error . message }),
{ status: 400 , headers: { 'Content-Type' : 'application/json' } }
)
}
if ( error instanceof AuthenticationError ) {
return new Response (
JSON . stringify ({ error: error . message }),
{ status: 401 , headers: { 'Content-Type' : 'application/json' } }
)
}
return new Response (
JSON . stringify ({ error: 'Internal server error' }),
{ status: 500 , headers: { 'Content-Type' : 'application/json' } }
)
}
})
Error Response Helper
Create a reusable error handler:
function errorResponse ( error : Error , status : number = 500 ) {
console . error ( `Error [ ${ status } ]:` , error . message )
return new Response (
JSON . stringify ({
error: error . message ,
timestamp: new Date (). toISOString ()
}),
{
status ,
headers: { 'Content-Type' : 'application/json' }
}
)
}
Deno . serve ( async ( req ) => {
try {
const result = await processRequest ( req )
return new Response ( JSON . stringify ( result ))
} catch ( error ) {
if ( error . message . includes ( 'validation' )) {
return errorResponse ( error , 400 )
}
if ( error . message . includes ( 'unauthorized' )) {
return errorResponse ( error , 401 )
}
return errorResponse ( error , 500 )
}
})
Testing Functions
Manual Testing with curl
Test locally:
curl -L -X POST 'http://localhost:54321/functions/v1/hello-world' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
-H 'Content-Type: application/json' \
-d '{"name":"Test"}'
Test production:
curl -L -X POST 'https://your-project.supabase.co/functions/v1/hello-world' \
-H 'Authorization: Bearer YOUR_ANON_KEY' \
-H 'Content-Type: application/json' \
-d '{"name":"Test"}'
Automated Tests
Create test files:
// supabase/functions/hello-world/hello-world.test.ts
import { assertEquals } from 'https://deno.land/std@0.170.0/testing/asserts.ts'
import { createClient } from 'npm:supabase-js@2'
Deno . test ( 'hello-world returns greeting' , async () => {
const supabase = createClient (
'http://localhost:54321' ,
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs'
)
const { data , error } = await supabase . functions . invoke ( 'hello-world' , {
body: { name: 'Deno' }
})
assertEquals ( error , null )
assertEquals ( data . message , 'Hello Deno!' )
})
Deno . test ( 'hello-world handles missing name' , async () => {
const supabase = createClient (
'http://localhost:54321' ,
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs'
)
const { data , error } = await supabase . functions . invoke ( 'hello-world' , {
body: {}
})
assertEquals ( error , null )
assertEquals ( data . message , 'Hello undefined!' )
})
Run tests:
# Start functions locally
supabase functions serve hello-world
# Run tests in another terminal
deno test --allow-all supabase/functions/hello-world/hello-world.test.ts
Integration Tests
Test with database interactions:
import { assertEquals } from 'https://deno.land/std@0.170.0/testing/asserts.ts'
import { createClient } from 'npm:supabase-js@2'
Deno . test ( 'create-user creates user in database' , async () => {
const supabase = createClient (
Deno . env . get ( 'SUPABASE_URL' ) ! ,
Deno . env . get ( 'SUPABASE_SERVICE_ROLE_KEY' ) !
)
// Call function
const { data : functionData , error : functionError } = await supabase . functions . invoke (
'create-user' ,
{ body: { email: 'test@example.com' , name: 'Test User' } }
)
assertEquals ( functionError , null )
assertEquals ( functionData . success , true )
// Verify in database
const { data : userData , error : dbError } = await supabase
. from ( 'users' )
. select ( '*' )
. eq ( 'email' , 'test@example.com' )
. single ()
assertEquals ( dbError , null )
assertEquals ( userData . name , 'Test User' )
// Cleanup
await supabase . from ( 'users' ). delete (). eq ( 'email' , 'test@example.com' )
})
Measure Execution Time
Track function performance:
Deno . serve ( async ( req ) => {
const startTime = performance . now ()
try {
const result = await processRequest ( req )
const duration = performance . now () - startTime
console . log ( `Request completed in ${ duration . toFixed ( 2 ) } ms` )
return new Response (
JSON . stringify ( result ),
{
headers: {
'Content-Type' : 'application/json' ,
'X-Execution-Time' : ` ${ duration } ms`
}
}
)
} catch ( error ) {
const duration = performance . now () - startTime
console . error ( `Request failed after ${ duration . toFixed ( 2 ) } ms:` , error )
return new Response ( 'Error' , { status: 500 })
}
})
Profile Slow Operations
Identify bottlenecks:
Deno . serve ( async ( req ) => {
const timings : Record < string , number > = {}
const startTime = performance . now ()
// Database query
const dbStart = performance . now ()
const data = await fetchFromDatabase ()
timings . database = performance . now () - dbStart
// External API call
const apiStart = performance . now ()
const apiData = await callExternalAPI ()
timings . api = performance . now () - apiStart
// Processing
const processStart = performance . now ()
const result = processData ( data , apiData )
timings . processing = performance . now () - processStart
timings . total = performance . now () - startTime
console . log ( 'Performance timings:' , timings )
return new Response ( JSON . stringify ( result ))
})
Memory Usage
Monitor memory usage:
Deno . serve ( async ( req ) => {
const memoryBefore = Deno . memoryUsage ()
const result = await processLargeDataset ()
const memoryAfter = Deno . memoryUsage ()
const memoryUsed = ( memoryAfter . heapUsed - memoryBefore . heapUsed ) / 1024 / 1024
console . log ( `Memory used: ${ memoryUsed . toFixed ( 2 ) } MB` )
return new Response ( JSON . stringify ( result ))
})
Common Issues
Function Timeout
If functions timeout:
Check execution time limits :
Free tier: 10 seconds
Pro tier: 150 seconds
Optimize slow operations :
// Bad: Sequential API calls
const data1 = await fetch ( url1 ). then ( r => r . json ())
const data2 = await fetch ( url2 ). then ( r => r . json ())
const data3 = await fetch ( url3 ). then ( r => r . json ()
// Good: Parallel API calls
const [ data1 , data2 , data3 ] = await Promise . all ([
fetch ( url1 ). then ( r => r . json ()),
fetch ( url2 ). then ( r => r . json ()),
fetch ( url3 ). then ( r => r . json ())
])
Use streaming for large responses
CORS Errors
Fix CORS issues:
import { corsHeaders } from 'jsr:@supabase/supabase-js@2/cors'
Deno . serve ( async ( req ) => {
// Handle preflight
if ( req . method === 'OPTIONS' ) {
return new Response ( 'ok' , { headers: corsHeaders })
}
try {
const data = await processRequest ( req )
return new Response (
JSON . stringify ( data ),
{
headers: {
... corsHeaders ,
'Content-Type' : 'application/json'
}
}
)
} catch ( error ) {
return new Response (
JSON . stringify ({ error: error . message }),
{
status: 500 ,
headers: {
... corsHeaders ,
'Content-Type' : 'application/json'
}
}
)
}
})
Authentication Errors
Debug auth issues:
import { createClient } from 'npm:supabase-js@2'
Deno . serve ( async ( req ) => {
const authHeader = req . headers . get ( 'Authorization' )
console . log ( 'Auth header:' , authHeader ? 'Present' : 'Missing' )
const supabaseClient = createClient (
Deno . env . get ( 'SUPABASE_URL' ) ?? '' ,
Deno . env . get ( 'SUPABASE_ANON_KEY' ) ?? '' ,
{
global: {
headers: { Authorization: authHeader ! }
}
}
)
const { data : { user }, error } = await supabaseClient . auth . getUser ()
if ( error ) {
console . error ( 'Auth error:' , error )
return new Response (
JSON . stringify ({ error: 'Unauthorized' , details: error . message }),
{ status: 401 , headers: { 'Content-Type' : 'application/json' } }
)
}
console . log ( 'Authenticated user:' , user . id )
return new Response ( JSON . stringify ({ userId: user . id }))
})
Cold Starts
Minimize cold start latency:
Keep functions small - Remove unnecessary dependencies
Lazy load modules - Import only when needed
Cache expensive operations - Use module-level caching
// Cache expensive initialization
let openaiClient : OpenAI | null = null
function getOpenAIClient () {
if ( ! openaiClient ) {
openaiClient = new OpenAI ({
apiKey: Deno . env . get ( 'OPENAI_API_KEY' )
})
}
return openaiClient
}
Deno . serve ( async ( req ) => {
const client = getOpenAIClient ()
// Use cached client
})
Dashboard Metrics
View metrics in the Dashboard:
Go to Edge Functions > Your function
Check the Metrics tab for:
Invocation count
Error rate
Average execution time
Memory usage
Custom Monitoring
Send metrics to external services:
Deno . serve ( async ( req ) => {
const startTime = performance . now ()
const requestId = crypto . randomUUID ()
try {
const result = await processRequest ( req )
const duration = performance . now () - startTime
// Send success metric
await sendMetric ({
name: 'function.invocation' ,
value: 1 ,
tags: { status: 'success' , requestId },
duration
})
return new Response ( JSON . stringify ( result ))
} catch ( error ) {
const duration = performance . now () - startTime
// Send error metric
await sendMetric ({
name: 'function.invocation' ,
value: 1 ,
tags: { status: 'error' , requestId },
duration ,
error: error . message
})
return new Response ( 'Error' , { status: 500 })
}
})
Best Practices
Use structured logging for easier analysis
Add request IDs to trace requests through logs
Handle errors gracefully with proper status codes
Test locally first before deploying
Monitor performance regularly
Set up alerts for error rates
Use version control to track changes
Document function behavior for your team
Next Steps
Deploy Functions Learn deployment strategies and CI/CD
Environment Variables Manage secrets and configuration
Resources