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

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)

ParameterTypeDescription
requestRequestThe incoming HTTP request object
envObjectEnvironment variables and bindings
ctxObjectExecution context. Use ctx.waitUntil(promise) to extend the function’s lifetime for async tasks

firewall(request, env, ctx) — ES Modules

ParameterTypeDescription
requestRequestThe incoming HTTP request object
envObjectEnvironment variables and bindings
ctxObjectExecution 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

PropertyDescription
event.requestAccess 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 export
export default function(request) {
return new Response('Hello');
}
// ❌ Named exports
export function fetch(request) {
return new Response('Hello');
}
// ❌ No export
function 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');
}