Migrating handler patterns in Functions
Azion Functions supports two handler patterns: ES Modules (recommended) and Service Worker (legacy). This guide explains the differences between them and how to migrate your existing functions to the ES Modules pattern.
Supported patterns
ES Modules (recommended)
The ES Modules pattern is the recommended way to structure your functions on Azion. It provides a modern, clean syntax with native support in production and better performance.
export default { fetch: (request, env, ctx) => { return new Response('Hello World'); }, firewall: (request, env, ctx) => { // Firewall logic ctx.deny(); }};Service Worker (legacy)
The Service Worker pattern is maintained for backward compatibility. If you’re using this pattern, Azion recommends migrating to ES Modules.
addEventListener('fetch', (event) => { event.respondWith(handleRequest(event.request));});
addEventListener('firewall', (event) => { // Firewall logic event.deny();});
async function handleRequest(request) { return new Response('Hello World');}Handler parameters
fetch(request, env, ctx)
| Parameter | Type | Description |
|---|---|---|
request | Request | The incoming HTTP request object |
env | Object | Environment variables and bindings |
ctx | Object | Execution context. Use ctx.waitUntil(promise) to extend the function’s lifetime for async tasks |
firewall(request, env, ctx) — ES Modules
| Parameter | Type | Description |
|---|---|---|
request | Request | The incoming HTTP request object |
env | Object | Environment variables and bindings |
ctx | Object | Execution context. Call ctx.deny() to block the request immediately. If ctx.deny() is not called, the request continues to the fetch handler |
Firewall event — Service Worker
| Property | Description |
|---|---|
event.request | Access to the Request object |
event.deny() | Blocks the request immediately. If not called, the request continues to the fetch handler |
Migrating from Service Worker to ES Modules
Basic fetch handler
Before (Service Worker):
addEventListener('fetch', (event) => { event.respondWith(handleRequest(event.request));});
async function handleRequest(request) { const url = new URL(request.url);
if (url.pathname === '/api/hello') { return new Response(JSON.stringify({ message: 'Hello World' }), { headers: { 'Content-Type': 'application/json' } }); }
return new Response('Not Found', { status: 404 });}After (ES Modules):
export default { fetch: async (request, env, ctx) => { const url = new URL(request.url);
if (url.pathname === '/api/hello') { return new Response(JSON.stringify({ message: 'Hello World' }), { headers: { 'Content-Type': 'application/json' } }); }
return new Response('Not Found', { status: 404 }); }};Firewall handler
Before (Service Worker):
addEventListener('fetch', (event) => { event.respondWith(handleRequest(event.request));});
addEventListener('firewall', (event) => { const clientIP = event.request.headers.get('X-Forwarded-For'); const userAgent = event.request.headers.get('User-Agent');
// Block bot requests if (userAgent && userAgent.includes('bot')) { event.deny(); return; }
// Block specific IPs if (clientIP === '192.168.1.100') { event.deny(); return; }
// Allow request to continue to fetch handler});
async function handleRequest(request) { return new Response('Hello World');}After (ES Modules):
export default { fetch: async (request, env, ctx) => { return new Response('Access granted'); },
firewall: async (request, env, ctx) => { const clientIP = request.headers.get('X-Forwarded-For'); const userAgent = request.headers.get('User-Agent');
// Block bot requests if (userAgent && userAgent.includes('bot')) { ctx.deny(); return; }
// Block specific IPs if (clientIP === '192.168.1.100') { ctx.deny(); return; }
// Allow request to continue to fetch handler return; }};Using waitUntil for async tasks
export default { fetch: async (request, env, ctx) => { // Use waitUntil for async tasks that should not block the response ctx.waitUntil(logRequest(request));
return new Response('Hello World'); }};
async function logRequest(request) { console.log(`Request to: ${request.url}`);}Advanced firewall with path-based rules
export default { fetch: async (request, env, ctx) => { return new Response('Access granted'); },
firewall: async (request, env, ctx) => { const url = new URL(request.url); const userAgent = request.headers.get('User-Agent'); const clientIP = request.headers.get('X-Forwarded-For');
// Block bot requests if (userAgent && userAgent.includes('bot')) { ctx.deny(); return; }
// Restrict access to admin paths by IP range if (url.pathname.startsWith('/admin')) { if (!clientIP || !clientIP.startsWith('192.168.')) { ctx.deny(); return; } }
// Allow request to continue to fetch handler return; }};Unsupported patterns
The following patterns are not supported by Azion Functions. If your code uses any of them, migrate to the ES Modules pattern.
// ❌ Direct function exportexport default function(request) { return new Response('Hello');}
// ❌ Named exportsexport function fetch(request) { return new Response('Hello');}
// ❌ No exportfunction handleRequest(request) { return new Response('Hello');}Troubleshooting
”Unsupported handler pattern detected”
This error appears when your code doesn’t follow any of the supported patterns. To resolve it, migrate to the ES Modules pattern:
export default { fetch: async (request, env, ctx) => { return new Response('Hello World'); }};Alternatively, use the Service Worker pattern as a temporary measure:
addEventListener('fetch', (event) => { event.respondWith(handleRequest(event.request));});
async function handleRequest(request) { return new Response('Hello World');}