0
# Edge Functions
1
2
Serverless function invocation system for running custom business logic at the edge. Execute TypeScript/JavaScript functions with global distribution, automatic scaling, and built-in authentication integration.
3
4
## Capabilities
5
6
### Function Invocation
7
8
Execute edge functions with custom payloads, headers, and configuration options.
9
10
```typescript { .api }
11
/**
12
* Invoke a Supabase Edge Function
13
* @param functionName - The name of the function to invoke
14
* @param options - Function invocation options
15
* @returns Promise resolving to function response or error
16
*/
17
invoke<T = any>(
18
functionName: string,
19
options?: FunctionInvokeOptions
20
): Promise<FunctionResponse<T>>;
21
22
interface FunctionInvokeOptions {
23
/** Custom headers to send with the request */
24
headers?: Record<string, string>;
25
/** Function payload data */
26
body?: any;
27
/** Preferred region for function execution */
28
region?: FunctionRegion;
29
/** HTTP method to use (defaults to POST) */
30
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
31
}
32
33
interface FunctionResponse<T> {
34
/** Function return data */
35
data: T | null;
36
/** Function execution error */
37
error: FunctionsError | null;
38
}
39
40
enum FunctionRegion {
41
/** Any available region (automatic selection) */
42
Any = 'any',
43
/** Asia Pacific (Tokyo) */
44
ApNortheast1 = 'ap-northeast-1',
45
/** Asia Pacific (Singapore) */
46
ApSoutheast1 = 'ap-southeast-1',
47
/** Asia Pacific (Mumbai) */
48
ApSouth1 = 'ap-south-1',
49
/** Canada (Central) */
50
CaCentral1 = 'ca-central-1',
51
/** Europe (Frankfurt) */
52
EuCentral1 = 'eu-central-1',
53
/** Europe (Ireland) */
54
EuWest1 = 'eu-west-1',
55
/** Europe (London) */
56
EuWest2 = 'eu-west-2',
57
/** South America (São Paulo) */
58
SaEast1 = 'sa-east-1',
59
/** US East (N. Virginia) */
60
UsEast1 = 'us-east-1',
61
/** US West (N. California) */
62
UsWest1 = 'us-west-1',
63
/** US West (Oregon) */
64
UsWest2 = 'us-west-2'
65
}
66
```
67
68
**Usage Examples:**
69
70
```typescript
71
// Basic function invocation
72
const { data, error } = await supabase.functions.invoke('hello-world');
73
74
if (error) {
75
console.error('Function error:', error);
76
} else {
77
console.log('Function response:', data);
78
}
79
80
// Function with payload
81
const { data, error } = await supabase.functions.invoke('process-data', {
82
body: {
83
userId: '123',
84
action: 'update_profile',
85
data: {
86
name: 'John Doe',
87
email: 'john@example.com'
88
}
89
}
90
});
91
92
// Function with custom headers
93
const { data, error } = await supabase.functions.invoke('authenticated-function', {
94
headers: {
95
'Authorization': `Bearer ${userToken}`,
96
'Content-Type': 'application/json',
97
'X-Custom-Header': 'custom-value'
98
},
99
body: { message: 'Hello from client' }
100
});
101
102
// Function with region preference
103
const { data, error } = await supabase.functions.invoke('region-specific', {
104
body: { data: 'process-me' },
105
region: FunctionRegion.EuWest1
106
});
107
108
// GET request to function
109
const { data, error } = await supabase.functions.invoke('api-endpoint', {
110
method: 'GET'
111
});
112
113
// TypeScript with typed response
114
interface ApiResponse {
115
success: boolean;
116
message: string;
117
result: any;
118
}
119
120
const { data, error } = await supabase.functions.invoke<ApiResponse>('typed-function', {
121
body: { input: 'test' }
122
});
123
124
if (data) {
125
console.log('Success:', data.success);
126
console.log('Message:', data.message);
127
console.log('Result:', data.result);
128
}
129
```
130
131
### Error Handling
132
133
Different types of errors that can occur during function execution.
134
135
```typescript { .api }
136
/**
137
* Base class for all function-related errors
138
*/
139
abstract class FunctionsError extends Error {
140
abstract name: string;
141
context?: Record<string, any>;
142
}
143
144
/**
145
* HTTP-related errors (4xx, 5xx status codes)
146
*/
147
class FunctionsHttpError extends FunctionsError {
148
name = 'FunctionsHttpError';
149
status: number;
150
statusText: string;
151
152
constructor(message: string, status: number, statusText: string) {
153
super(message);
154
this.status = status;
155
this.statusText = statusText;
156
}
157
}
158
159
/**
160
* Network/fetch-related errors
161
*/
162
class FunctionsFetchError extends FunctionsError {
163
name = 'FunctionsFetchError';
164
165
constructor(message: string) {
166
super(message);
167
}
168
}
169
170
/**
171
* Relay/connection errors
172
*/
173
class FunctionsRelayError extends FunctionsError {
174
name = 'FunctionsRelayError';
175
176
constructor(message: string) {
177
super(message);
178
}
179
}
180
```
181
182
**Usage Examples:**
183
184
```typescript
185
const { data, error } = await supabase.functions.invoke('my-function', {
186
body: { input: 'test' }
187
});
188
189
if (error) {
190
if (error instanceof FunctionsHttpError) {
191
console.error(`HTTP Error ${error.status}: ${error.statusText}`);
192
console.error('Message:', error.message);
193
194
// Handle specific HTTP status codes
195
switch (error.status) {
196
case 400:
197
console.error('Bad request - check your payload');
198
break;
199
case 401:
200
console.error('Unauthorized - check authentication');
201
break;
202
case 403:
203
console.error('Forbidden - insufficient permissions');
204
break;
205
case 404:
206
console.error('Function not found');
207
break;
208
case 500:
209
console.error('Internal server error in function');
210
break;
211
default:
212
console.error('Other HTTP error');
213
}
214
} else if (error instanceof FunctionsFetchError) {
215
console.error('Network error:', error.message);
216
// Handle network issues, retries, etc.
217
} else if (error instanceof FunctionsRelayError) {
218
console.error('Relay error:', error.message);
219
// Handle connection issues
220
} else {
221
console.error('Unknown error:', error.message);
222
}
223
}
224
```
225
226
## Advanced Function Patterns
227
228
### Authentication Integration
229
230
```typescript
231
// Invoke function with user authentication
232
const invokeWithAuth = async (functionName: string, payload: any) => {
233
// Get current session
234
const { data: { session }, error: sessionError } = await supabase.auth.getSession();
235
236
if (sessionError || !session) {
237
console.error('No active session');
238
return { data: null, error: new Error('Authentication required') };
239
}
240
241
// Invoke function with auth token
242
return supabase.functions.invoke(functionName, {
243
headers: {
244
'Authorization': `Bearer ${session.access_token}`
245
},
246
body: payload
247
});
248
};
249
250
// Usage
251
const { data, error } = await invokeWithAuth('user-specific-function', {
252
action: 'get_user_data'
253
});
254
```
255
256
### Retry Logic
257
258
```typescript
259
// Function invocation with retry logic
260
const invokeWithRetry = async (
261
functionName: string,
262
options: FunctionInvokeOptions,
263
maxRetries: number = 3,
264
delay: number = 1000
265
): Promise<FunctionResponse<any>> => {
266
let lastError: FunctionsError | null = null;
267
268
for (let attempt = 1; attempt <= maxRetries; attempt++) {
269
try {
270
const { data, error } = await supabase.functions.invoke(functionName, options);
271
272
if (!error) {
273
return { data, error: null };
274
}
275
276
lastError = error;
277
278
// Only retry on network errors or 5xx status codes
279
if (error instanceof FunctionsHttpError && error.status < 500) {
280
break; // Don't retry client errors
281
}
282
283
if (attempt < maxRetries) {
284
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
285
await new Promise(resolve => setTimeout(resolve, delay));
286
delay *= 2; // Exponential backoff
287
}
288
} catch (err) {
289
lastError = err as FunctionsError;
290
if (attempt < maxRetries) {
291
await new Promise(resolve => setTimeout(resolve, delay));
292
delay *= 2;
293
}
294
}
295
}
296
297
return { data: null, error: lastError };
298
};
299
300
// Usage
301
const { data, error } = await invokeWithRetry(
302
'unreliable-function',
303
{ body: { data: 'test' } },
304
3,
305
1000
306
);
307
```
308
309
### Batch Function Execution
310
311
```typescript
312
// Execute multiple functions concurrently
313
const executeBatch = async (functionCalls: Array<{
314
name: string;
315
options?: FunctionInvokeOptions;
316
}>) => {
317
const promises = functionCalls.map(call =>
318
supabase.functions.invoke(call.name, call.options)
319
);
320
321
const results = await Promise.allSettled(promises);
322
323
return results.map((result, index) => ({
324
functionName: functionCalls[index].name,
325
success: result.status === 'fulfilled' && !result.value.error,
326
data: result.status === 'fulfilled' ? result.value.data : null,
327
error: result.status === 'fulfilled' ? result.value.error : result.reason
328
}));
329
};
330
331
// Usage
332
const results = await executeBatch([
333
{ name: 'function-1', options: { body: { id: 1 } } },
334
{ name: 'function-2', options: { body: { id: 2 } } },
335
{ name: 'function-3', options: { body: { id: 3 } } }
336
]);
337
338
results.forEach((result, index) => {
339
if (result.success) {
340
console.log(`Function ${result.functionName} succeeded:`, result.data);
341
} else {
342
console.error(`Function ${result.functionName} failed:`, result.error);
343
}
344
});
345
```
346
347
### Streaming Responses
348
349
```typescript
350
// Handle streaming responses (if your function returns streaming data)
351
const handleStreamingFunction = async (functionName: string, payload: any) => {
352
try {
353
const { data, error } = await supabase.functions.invoke(functionName, {
354
body: payload,
355
headers: {
356
'Accept': 'text/stream'
357
}
358
});
359
360
if (error) {
361
console.error('Streaming function error:', error);
362
return;
363
}
364
365
// Handle streaming data (implementation depends on function response format)
366
if (typeof data === 'string') {
367
const lines = data.split('\n');
368
lines.forEach(line => {
369
if (line.trim()) {
370
try {
371
const jsonData = JSON.parse(line);
372
console.log('Streamed data:', jsonData);
373
} catch {
374
console.log('Streamed text:', line);
375
}
376
}
377
});
378
}
379
} catch (err) {
380
console.error('Streaming error:', err);
381
}
382
};
383
```
384
385
### Function Response Caching
386
387
```typescript
388
// Simple in-memory cache for function responses
389
class FunctionCache {
390
private cache = new Map<string, { data: any; timestamp: number; ttl: number }>();
391
392
async invoke<T>(
393
functionName: string,
394
options: FunctionInvokeOptions = {},
395
cacheTTL: number = 300000 // 5 minutes default
396
): Promise<FunctionResponse<T>> {
397
const cacheKey = this.getCacheKey(functionName, options);
398
const cached = this.cache.get(cacheKey);
399
400
// Return cached response if still valid
401
if (cached && Date.now() - cached.timestamp < cached.ttl) {
402
console.log('Returning cached response for', functionName);
403
return { data: cached.data, error: null };
404
}
405
406
// Invoke function
407
const { data, error } = await supabase.functions.invoke<T>(functionName, options);
408
409
// Cache successful responses
410
if (!error && data !== null) {
411
this.cache.set(cacheKey, {
412
data,
413
timestamp: Date.now(),
414
ttl: cacheTTL
415
});
416
}
417
418
return { data, error };
419
}
420
421
private getCacheKey(functionName: string, options: FunctionInvokeOptions): string {
422
return `${functionName}:${JSON.stringify(options)}`;
423
}
424
425
clearCache(): void {
426
this.cache.clear();
427
}
428
429
clearExpired(): void {
430
const now = Date.now();
431
for (const [key, value] of this.cache.entries()) {
432
if (now - value.timestamp >= value.ttl) {
433
this.cache.delete(key);
434
}
435
}
436
}
437
}
438
439
// Usage
440
const functionCache = new FunctionCache();
441
442
const { data, error } = await functionCache.invoke(
443
'expensive-calculation',
444
{ body: { input: 'complex-data' } },
445
600000 // Cache for 10 minutes
446
);
447
```
448
449
### Function Development Integration
450
451
```typescript
452
// Helper for local development with Edge Functions
453
const isDevelopment = process.env.NODE_ENV === 'development';
454
const EDGE_FUNCTION_URL = isDevelopment
455
? 'http://localhost:54321/functions/v1'
456
: undefined;
457
458
// Create client with local functions URL for development
459
const supabaseClient = isDevelopment
460
? createClient(supabaseUrl, supabaseKey, {
461
global: {
462
headers: {
463
'x-forwarded-host': 'localhost:3000'
464
}
465
}
466
})
467
: supabase;
468
469
// Function invocation wrapper that logs in development
470
const invokeFunction = async (functionName: string, options?: FunctionInvokeOptions) => {
471
if (isDevelopment) {
472
console.log(`Invoking function: ${functionName}`, options);
473
}
474
475
const startTime = Date.now();
476
const { data, error } = await supabaseClient.functions.invoke(functionName, options);
477
const duration = Date.now() - startTime;
478
479
if (isDevelopment) {
480
console.log(`Function ${functionName} completed in ${duration}ms`);
481
if (error) {
482
console.error('Function error:', error);
483
} else {
484
console.log('Function response:', data);
485
}
486
}
487
488
return { data, error };
489
};
490
```