---
name: netlify-edge-functions
description: Create and deploy Netlify Edge Functions that run on Deno at the network edge. Use when implementing low-latency middleware, request/response transformations, A/B testing, geolocation routing, or authentication checks at the edge.
---

# Netlify Edge Functions

Netlify Edge Functions run on Deno at the network edge, providing low-latency middleware and request/response
transformations. They share the same handler signature pattern as serverless functions (Web API Request/Response) but
use the Deno runtime with Web APIs built in. Edge functions are configured with a path pattern and only paths matching
those patterns will run the edge function.

## Quick Start

Create an edge function file in the `netlify/edge-functions/` directory:

```typescript
// netlify/edge-functions/hello.ts
import type { Context, Config } from '@netlify/edge-functions'

export default async (req: Request, context: Context) => {
  const name = new URL(req.url).searchParams.get('name') || 'world'
  return new Response(`Hello, ${name}!`)
}

export const config: Config = {
  path: '/hello',
}
```

```javascript
// netlify/edge-functions/hello.js
export default async (req, context) => {
  const name = new URL(req.url).searchParams.get('name') || 'world'
  return new Response(`Hello, ${name}!`)
}

export const config = {
  path: '/hello',
}
```

Deploy and invoke at the configured `path`.

ALWAYS use TypeScript (`.ts`) if other functions in the project are TypeScript or if there are no existing edge functions.
ONLY use vanilla JavaScript if there are other `.js` files in the edge functions directory. DO NOT put global logic outside
of the exported function unless it is wrapped in a function definition. ALWAYS use the latest format of an edge
function structure.

If using TypeScript, ensure types are installed from `npm install @netlify/edge-functions`.

## Function Structure & Placement

**Default directory:** `netlify/edge-functions/` (relative to site base directory)

**Naming conventions:**
- Use `.ts` (TypeScript, recommended) or `.js` (JavaScript) extensions (not `.mts`/`.mjs` — Deno natively supports ESM)
- Filename becomes the function name
- One default export per file

**Valid function paths:**
- `netlify/edge-functions/hello.ts`
- `netlify/edge-functions/hello/index.ts`
- `netlify/edge-functions/hello/hello.ts`

**Custom directory** via `netlify.toml`:

```toml
[build]
edge_functions = "my_edge_functions"
```

## Handler Signature

```typescript
export default async (req: Request, context: Context) => Response | void
```

The first argument is a standard Web API Request object. The second argument is a Netlify-specific `Context` object.
Return a standard Web API Response, or return nothing to pass through to the next handler in the chain.

```typescript
import type { Context } from '@netlify/edge-functions'

export default async (req: Request, context: Context) => {
  const body = await req.json()
  return Response.json({ received: body })
}
```

## Deno Runtime & Module Support

Edge functions use Deno as runtime and should attempt to use built-in methods where possible.

### Node.js Built-in Modules

Use the `node:` prefix to import Node.js built-in modules:

```typescript
import { Buffer } from 'node:buffer'
import { randomBytes } from 'node:crypto'
import process from 'node:process'
```

### npm Packages (beta)

Install via `npm install` and import by package name:

```typescript
import lodash from 'lodash'
```

Some npm packages with native binaries (e.g., Prisma) or dynamic imports (e.g., cowsay) may not work.

### Deno URL Imports

Import modules directly by URL:

```typescript
import React from 'https://esm.sh/react'
```

### Import Maps

Use an import map to alias third-party modules with shorthand names. Define mappings in a separate JSON file (not in
`deno.json`):

```json
{
  "imports": {
    "html-rewriter": "https://ghuc.cc/worker-tools/html-rewriter/index.ts"
  }
}
```

Enable import maps in `netlify.toml`:

```toml
[functions]
deno_import_map = "./path/to/your/import_map.json"
```

Then import by name:

```typescript
import { HTMLRewriter } from 'html-rewriter'
```

### Available Web APIs

Edge functions have access to standard Web APIs:

- **Fetch API:** `fetch`, `Request`, `Response`, `URL`, `File`, `Blob`
- **Encoding:** `TextEncoder`, `TextDecoder`, `TextEncoderStream`, `TextDecoderStream`, `atob`, `btoa`
- **Crypto:** `crypto.randomUUID()`, `crypto.getRandomValues()`, `crypto.subtle` (SubtleCrypto)
- **Streams:** `ReadableStream`, `WritableStream`, `TransformStream`
- **Timers:** `setTimeout`, `clearTimeout`, `setInterval`
- **Other:** `console.*`, `Performance`, `WebSocket`, `URLPattern`

## In-Code Config & Routing

Prefer to use in-code configuration via exporting a `config` object. Prefer to provide a friendly path using the
config object. `path` and `excludedPath` support substring patterns or the URLPattern syntax from the web platform.
Unless explicitly asked to modify other properties, only set `path`, `pattern`, `excludedPath` when creating functions.

```typescript
import type { Config } from '@netlify/edge-functions'

export default async (req: Request, context: Context) => {
  return new Response('Hello from the edge')
}

export const config: Config = {
  path: '/api/edge/*',
}
```

**Config properties:**

| Property | Type | Description |
|----------|------|-------------|
| `path` | `string \| string[]` | URLPattern expression for paths. Must start with `/` |
| `excludedPath` | `string \| string[]` | Paths to exclude from execution. Must start with `/` |
| `pattern` | `RegExp \| RegExp[]` | Regex for path matching (alternative to `path`) |
| `excludedPattern` | `RegExp \| RegExp[]` | Regex patterns to exclude |
| `method` | `string \| string[]` | HTTP method(s) to match (`GET`, `POST`, etc.) |
| `onError` | `'bypass' \| 'fail'` | Error behavior (`'bypass'` serves static/origin) |
| `cache` | `'manual'` | Enables CDN response caching |

**Route parameters:**

```typescript
// :param - named parameter
export const config: Config = { path: '/api/users/:id' }
// context.params.id = '123' for /api/users/123

// * - wildcard
export const config: Config = { path: '/api/*' }
// Matches /api/anything/here
```

**Multiple paths:**

```typescript
export const config: Config = {
  path: ['/api/edge/:name', '/edge/:name'],
}
```

## Context Object

The `context` parameter provides Netlify-specific metadata. All standard context properties are available:

### Standard Properties

```typescript
context.ip            // string - client IP address
context.geo.city      // string - city name
context.geo.country   // { code: string, name: string } - ISO 3166
context.geo.latitude  // number
context.geo.longitude // number
context.geo.subdivision // { code: string, name: string } - ISO 3166
context.geo.timezone  // string - IANA timezone
context.geo.postalCode // string
context.site.id       // string - unique site ID
context.site.name     // string - site subdomain name
context.site.url      // string - primary site URL
context.deploy.context   // string - e.g., 'production', 'deploy-preview'
context.deploy.id        // string - unique deploy ID
context.deploy.published // boolean - is this the published deploy?
context.account.id    // string - team/account ID
context.requestId     // string - unique request identifier
context.params        // route parameters from config.path
context.server.region // string - e.g., 'us-east-1'
```

### `context.cookies`

Simplified cookie interface:

```typescript
const session = context.cookies.get('session')
context.cookies.set({ name: 'session', value: 'abc123', path: '/', httpOnly: true })
context.cookies.delete('session')
```

### `context.waitUntil(promise)`

Extends execution after the response is sent:

```typescript
export default async (req: Request, context: Context) => {
  context.waitUntil(
    fetch('https://analytics.example.com/track', {
      method: 'POST',
      body: JSON.stringify({ path: new URL(req.url).pathname }),
    }),
  )
  return new Response('OK')
}
```

### Edge-Only: `context.next()`

Passes the request to the next edge function in the chain or to the origin.

**IMPORTANT:** When the goal is to skip edge function execution and let the request proceed normally, prefer an empty
`return` (or `return undefined`) over `return context.next()`. An empty return is the most performant way to terminate
execution — calling `context.next()` when you don't need the response is unnecessary and slows down overall execution.

ONLY use `context.next()` when you need to read or modify the upstream response (e.g., adding headers, transforming
the body). Note that empty returns don't set cache-control headers, so the function will always execute on every
request. To cache responses, you must use `context.next()`, set appropriate cache headers, and return it explicitly.

```typescript
// GOOD: Skip execution and let the request proceed (most performant)
export default async (req: Request, context: Context) => {
  if (!shouldProcess(req)) {
    return  // terminates execution, request proceeds normally
  }
  // ... process request
}

// GOOD: Modify the upstream response (valid use of context.next())
export default async (req: Request, context: Context) => {
  const response = await context.next()
  response.headers.set('x-custom-header', 'value')
  return response
}
```

Optionally use conditional requests:

```typescript
const response = await context.next({ sendConditionalRequest: true })
```

### Edge-Only: `context.nextRequest()`

Passes a modified request to the next handler:

```typescript
export default async (req: Request, context: Context) => {
  const modifiedReq = new Request(req.url, {
    headers: { ...Object.fromEntries(req.headers), 'x-added': 'true' },
  })
  return context.nextRequest(modifiedReq)
}
```

## Global `Netlify` Object

Available in all edge function contexts:

### `Netlify.env`

Access environment variables:

```typescript
const apiKey = Netlify.env.get('API_KEY')     // string | undefined
const exists = Netlify.env.has('API_KEY')      // boolean
const all = Netlify.env.toObject()             // Record<string, string>

// Scoped to current invocation only
Netlify.env.set('TEMP_VAR', 'value')
Netlify.env.delete('TEMP_VAR')
```

ONLY use `Netlify.env.*` for interacting with environment variables in code. Do not use `process.env` or `Deno.env`.

### `Netlify.context`

Same as the `context` parameter in the handler. Available from outside the handler scope (e.g., imported modules).
Returns `null` outside a function invocation.

## netlify.toml Configuration

ONLY use `netlify.toml` `[[edge_functions]]` blocks for precise function execution order control. DO NOT use
`netlify.toml` if there are no edge function ordering requirements. When controlling order, include all edge functions.

```toml
[[edge_functions]]
function = "auth"
path = "/admin"

[[edge_functions]]
function = "injector"
path = "/admin"
cache = "manual"

[[edge_functions]]
function = "auth"
path = "/blog/*"

[[edge_functions]]
function = "rewriter"
path = "/blog/*"

[[edge_functions]]
function = "highlight"
pattern = "/products/(.*)"
excludedPattern = "/products/things/(.*)"

[[edge_functions]]
function = "common"
path = "/*"
excludedPath = "/img/*"
```

**Edge function properties in `netlify.toml`:**

| Property | Type | Description |
|----------|------|-------------|
| `function` | `string` | Name of the edge function |
| `path` | `string` | URL pattern to trigger the function (must start with `/`) |
| `excludedPath` | `string \| string[]` | Routes to exclude from `path` |
| `pattern` | `string` | Regex-based path matching |
| `excludedPattern` | `string \| string[]` | Regex patterns to exclude |
| `cache` | `string` | Set to `'manual'` to enable response caching |

## Execution Order

When multiple edge functions match the same path, they execute in this order:

1. Configuration-based edge functions (`netlify.toml`) run first
2. Framework-generated edge functions execute before user-defined functions
3. Non-cached edge functions execute before cached (`cache: 'manual'`) functions
4. Inline-declared `config` exports override duplicate `netlify.toml` declarations for the same function
5. Multiple inline edge functions sort alphabetically by filename

## Caveats

- If an edge function returns a response, redirects for that path DO NOT occur
- Edge functions DO NOT execute for rewritten static routing targets
- `fetch()` or `new URL()` with the same site origin triggers a new request chain, re-running matching functions.
  Use `context.next()` to continue processing instead of re-triggering functions
- Can only rewrite requests to same-site URLs (use `fetch()` for external content)
- Cached edge function responses override existing static files at the same path
- Function failure behavior depends on the `onError` configuration

## Limits

| Resource | Limit |
|----------|-------|
| Code size (compressed) | 20 MB |
| Memory per deployment | 512 MB |
| CPU time per request | 50 ms (excludes waiting time) |
| Response header timeout | 40 seconds |

**Incompatibilities:**
- Netlify's split testing feature
- Custom headers from `_headers` or `netlify.toml` config (use response headers in code instead)
- Netlify prerendering on paths served by edge functions
- Not included in Netlify's HIPAA-compliant hosting offering

**Restrictions:**
- Can only rewrite requests to same-site URLs
- Cached edge functions override existing static files
- No local caching in development (HTTP cache headers are ignored locally)
- Multiple framework adapters may generate conflicting edge functions

## Common Errors & Solutions

### "Edge function not found"

**Cause:** Function file not in the correct directory or missing `config.path`.

**Fix:**

1. Place function in `netlify/edge-functions/` (or configured directory)
2. Use `.ts` or `.js` extension
3. Ensure the file has a default export
4. Ensure the function exports a `config` with a `path` or `pattern`

### "CPU time exceeded" (50ms limit)

**Cause:** Function CPU-intensive work exceeds the 50ms CPU time limit.

**Fix:**

1. Optimize hot paths — the 50ms limit is CPU time only, not wall-clock time
2. Waiting on `fetch()`, `context.next()`, or other I/O does not count against the CPU limit
3. For heavy computation, offload to a serverless function instead

### Module import failures

**Cause:** Incompatible npm package or incorrect import syntax.

**Fix:**

1. Use `node:` prefix for Node.js built-in modules (`node:crypto`, not `crypto`)
2. Check that npm packages do not rely on native binaries or unsupported dynamic imports
3. For Deno URL imports, ensure the URL is accessible and returns valid ESM

### "CORS errors" in browser

**Cause:** Missing CORS headers on function response. NEVER add CORS headers unless explicitly requested by the user.

**Fix** (only when user asks for CORS support):

```typescript
return new Response(JSON.stringify(data), {
  headers: {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
  },
})
```

### Environment variable is undefined

**Cause:** Variable not available in the function context.

**Fix:**

1. Use `Netlify.env.get('VAR_NAME')` — do not use `process.env` or `Deno.env`
2. Ensure the variable is set in Netlify UI with correct scopes (Runtime)
3. Deploy to Netlify for env var injection
