@netlify/agent-runner-cli 1.69.3 → 1.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,478 @@
1
+ ---
2
+ name: netlify-edge-functions
3
+ 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.
4
+ ---
5
+
6
+ # Netlify Edge Functions
7
+
8
+ Netlify Edge Functions run on Deno at the network edge, providing low-latency middleware and request/response
9
+ transformations. They share the same handler signature pattern as serverless functions (Web API Request/Response) but
10
+ use the Deno runtime with Web APIs built in. Edge functions are configured with a path pattern and only paths matching
11
+ those patterns will run the edge function.
12
+
13
+ ## Quick Start
14
+
15
+ Create an edge function file in the `netlify/edge-functions/` directory:
16
+
17
+ ```typescript
18
+ // netlify/edge-functions/hello.ts
19
+ import type { Context, Config } from '@netlify/edge-functions'
20
+
21
+ export default async (req: Request, context: Context) => {
22
+ const name = new URL(req.url).searchParams.get('name') || 'world'
23
+ return new Response(`Hello, ${name}!`)
24
+ }
25
+
26
+ export const config: Config = {
27
+ path: '/hello',
28
+ }
29
+ ```
30
+
31
+ ```javascript
32
+ // netlify/edge-functions/hello.js
33
+ export default async (req, context) => {
34
+ const name = new URL(req.url).searchParams.get('name') || 'world'
35
+ return new Response(`Hello, ${name}!`)
36
+ }
37
+
38
+ export const config = {
39
+ path: '/hello',
40
+ }
41
+ ```
42
+
43
+ Deploy and invoke at the configured `path`.
44
+
45
+ ALWAYS use TypeScript (`.ts`) if other functions in the project are TypeScript or if there are no existing edge functions.
46
+ ONLY use vanilla JavaScript if there are other `.js` files in the edge functions directory. DO NOT put global logic outside
47
+ of the exported function unless it is wrapped in a function definition. ALWAYS use the latest format of an edge
48
+ function structure.
49
+
50
+ If using TypeScript, ensure types are installed from `npm install @netlify/edge-functions`.
51
+
52
+ ## Function Structure & Placement
53
+
54
+ **Default directory:** `netlify/edge-functions/` (relative to site base directory)
55
+
56
+ **Naming conventions:**
57
+ - Use `.ts` (TypeScript, recommended) or `.js` (JavaScript) extensions (not `.mts`/`.mjs` — Deno natively supports ESM)
58
+ - Filename becomes the function name
59
+ - One default export per file
60
+
61
+ **Valid function paths:**
62
+ - `netlify/edge-functions/hello.ts`
63
+ - `netlify/edge-functions/hello/index.ts`
64
+ - `netlify/edge-functions/hello/hello.ts`
65
+
66
+ **Custom directory** via `netlify.toml`:
67
+
68
+ ```toml
69
+ [build]
70
+ edge_functions = "my_edge_functions"
71
+ ```
72
+
73
+ ## Handler Signature
74
+
75
+ ```typescript
76
+ export default async (req: Request, context: Context) => Response | void
77
+ ```
78
+
79
+ The first argument is a standard Web API Request object. The second argument is a Netlify-specific `Context` object.
80
+ Return a standard Web API Response, or return nothing to pass through to the next handler in the chain.
81
+
82
+ ```typescript
83
+ import type { Context } from '@netlify/edge-functions'
84
+
85
+ export default async (req: Request, context: Context) => {
86
+ const body = await req.json()
87
+ return Response.json({ received: body })
88
+ }
89
+ ```
90
+
91
+ ## Deno Runtime & Module Support
92
+
93
+ Edge functions use Deno as runtime and should attempt to use built-in methods where possible.
94
+
95
+ ### Node.js Built-in Modules
96
+
97
+ Use the `node:` prefix to import Node.js built-in modules:
98
+
99
+ ```typescript
100
+ import { Buffer } from 'node:buffer'
101
+ import { randomBytes } from 'node:crypto'
102
+ import process from 'node:process'
103
+ ```
104
+
105
+ ### npm Packages (beta)
106
+
107
+ Install via `npm install` and import by package name:
108
+
109
+ ```typescript
110
+ import lodash from 'lodash'
111
+ ```
112
+
113
+ Some npm packages with native binaries (e.g., Prisma) or dynamic imports (e.g., cowsay) may not work.
114
+
115
+ ### Deno URL Imports
116
+
117
+ Import modules directly by URL:
118
+
119
+ ```typescript
120
+ import React from 'https://esm.sh/react'
121
+ ```
122
+
123
+ ### Import Maps
124
+
125
+ Use an import map to alias third-party modules with shorthand names. Define mappings in a separate JSON file (not in
126
+ `deno.json`):
127
+
128
+ ```json
129
+ {
130
+ "imports": {
131
+ "html-rewriter": "https://ghuc.cc/worker-tools/html-rewriter/index.ts"
132
+ }
133
+ }
134
+ ```
135
+
136
+ Enable import maps in `netlify.toml`:
137
+
138
+ ```toml
139
+ [functions]
140
+ deno_import_map = "./path/to/your/import_map.json"
141
+ ```
142
+
143
+ Then import by name:
144
+
145
+ ```typescript
146
+ import { HTMLRewriter } from 'html-rewriter'
147
+ ```
148
+
149
+ ### Available Web APIs
150
+
151
+ Edge functions have access to standard Web APIs:
152
+
153
+ - **Fetch API:** `fetch`, `Request`, `Response`, `URL`, `File`, `Blob`
154
+ - **Encoding:** `TextEncoder`, `TextDecoder`, `TextEncoderStream`, `TextDecoderStream`, `atob`, `btoa`
155
+ - **Crypto:** `crypto.randomUUID()`, `crypto.getRandomValues()`, `crypto.subtle` (SubtleCrypto)
156
+ - **Streams:** `ReadableStream`, `WritableStream`, `TransformStream`
157
+ - **Timers:** `setTimeout`, `clearTimeout`, `setInterval`
158
+ - **Other:** `console.*`, `Performance`, `WebSocket`, `URLPattern`
159
+
160
+ ## In-Code Config & Routing
161
+
162
+ Prefer to use in-code configuration via exporting a `config` object. Prefer to provide a friendly path using the
163
+ config object. `path` and `excludedPath` support substring patterns or the URLPattern syntax from the web platform.
164
+ Unless explicitly asked to modify other properties, only set `path`, `pattern`, `excludedPath` when creating functions.
165
+
166
+ ```typescript
167
+ import type { Config } from '@netlify/edge-functions'
168
+
169
+ export default async (req: Request, context: Context) => {
170
+ return new Response('Hello from the edge')
171
+ }
172
+
173
+ export const config: Config = {
174
+ path: '/api/edge/*',
175
+ }
176
+ ```
177
+
178
+ **Config properties:**
179
+
180
+ | Property | Type | Description |
181
+ |----------|------|-------------|
182
+ | `path` | `string \| string[]` | URLPattern expression for paths. Must start with `/` |
183
+ | `excludedPath` | `string \| string[]` | Paths to exclude from execution. Must start with `/` |
184
+ | `pattern` | `RegExp \| RegExp[]` | Regex for path matching (alternative to `path`) |
185
+ | `excludedPattern` | `RegExp \| RegExp[]` | Regex patterns to exclude |
186
+ | `method` | `string \| string[]` | HTTP method(s) to match (`GET`, `POST`, etc.) |
187
+ | `onError` | `'bypass' \| 'fail'` | Error behavior (`'bypass'` serves static/origin) |
188
+ | `cache` | `'manual'` | Enables CDN response caching |
189
+
190
+ **Route parameters:**
191
+
192
+ ```typescript
193
+ // :param - named parameter
194
+ export const config: Config = { path: '/api/users/:id' }
195
+ // context.params.id = '123' for /api/users/123
196
+
197
+ // * - wildcard
198
+ export const config: Config = { path: '/api/*' }
199
+ // Matches /api/anything/here
200
+ ```
201
+
202
+ **Multiple paths:**
203
+
204
+ ```typescript
205
+ export const config: Config = {
206
+ path: ['/api/edge/:name', '/edge/:name'],
207
+ }
208
+ ```
209
+
210
+ ## Context Object
211
+
212
+ The `context` parameter provides Netlify-specific metadata. All standard context properties are available:
213
+
214
+ ### Standard Properties
215
+
216
+ ```typescript
217
+ context.ip // string - client IP address
218
+ context.geo.city // string - city name
219
+ context.geo.country // { code: string, name: string } - ISO 3166
220
+ context.geo.latitude // number
221
+ context.geo.longitude // number
222
+ context.geo.subdivision // { code: string, name: string } - ISO 3166
223
+ context.geo.timezone // string - IANA timezone
224
+ context.geo.postalCode // string
225
+ context.site.id // string - unique site ID
226
+ context.site.name // string - site subdomain name
227
+ context.site.url // string - primary site URL
228
+ context.deploy.context // string - e.g., 'production', 'deploy-preview'
229
+ context.deploy.id // string - unique deploy ID
230
+ context.deploy.published // boolean - is this the published deploy?
231
+ context.account.id // string - team/account ID
232
+ context.requestId // string - unique request identifier
233
+ context.params // route parameters from config.path
234
+ context.server.region // string - e.g., 'us-east-1'
235
+ ```
236
+
237
+ ### `context.cookies`
238
+
239
+ Simplified cookie interface:
240
+
241
+ ```typescript
242
+ const session = context.cookies.get('session')
243
+ context.cookies.set({ name: 'session', value: 'abc123', path: '/', httpOnly: true })
244
+ context.cookies.delete('session')
245
+ ```
246
+
247
+ ### `context.waitUntil(promise)`
248
+
249
+ Extends execution after the response is sent:
250
+
251
+ ```typescript
252
+ export default async (req: Request, context: Context) => {
253
+ context.waitUntil(
254
+ fetch('https://analytics.example.com/track', {
255
+ method: 'POST',
256
+ body: JSON.stringify({ path: new URL(req.url).pathname }),
257
+ }),
258
+ )
259
+ return new Response('OK')
260
+ }
261
+ ```
262
+
263
+ ### Edge-Only: `context.next()`
264
+
265
+ Passes the request to the next edge function in the chain or to the origin.
266
+
267
+ **IMPORTANT:** When the goal is to skip edge function execution and let the request proceed normally, prefer an empty
268
+ `return` (or `return undefined`) over `return context.next()`. An empty return is the most performant way to terminate
269
+ execution — calling `context.next()` when you don't need the response is unnecessary and slows down overall execution.
270
+
271
+ ONLY use `context.next()` when you need to read or modify the upstream response (e.g., adding headers, transforming
272
+ the body). Note that empty returns don't set cache-control headers, so the function will always execute on every
273
+ request. To cache responses, you must use `context.next()`, set appropriate cache headers, and return it explicitly.
274
+
275
+ ```typescript
276
+ // GOOD: Skip execution and let the request proceed (most performant)
277
+ export default async (req: Request, context: Context) => {
278
+ if (!shouldProcess(req)) {
279
+ return // terminates execution, request proceeds normally
280
+ }
281
+ // ... process request
282
+ }
283
+
284
+ // GOOD: Modify the upstream response (valid use of context.next())
285
+ export default async (req: Request, context: Context) => {
286
+ const response = await context.next()
287
+ response.headers.set('x-custom-header', 'value')
288
+ return response
289
+ }
290
+ ```
291
+
292
+ Optionally use conditional requests:
293
+
294
+ ```typescript
295
+ const response = await context.next({ sendConditionalRequest: true })
296
+ ```
297
+
298
+ ### Edge-Only: `context.nextRequest()`
299
+
300
+ Passes a modified request to the next handler:
301
+
302
+ ```typescript
303
+ export default async (req: Request, context: Context) => {
304
+ const modifiedReq = new Request(req.url, {
305
+ headers: { ...Object.fromEntries(req.headers), 'x-added': 'true' },
306
+ })
307
+ return context.nextRequest(modifiedReq)
308
+ }
309
+ ```
310
+
311
+ ## Global `Netlify` Object
312
+
313
+ Available in all edge function contexts:
314
+
315
+ ### `Netlify.env`
316
+
317
+ Access environment variables:
318
+
319
+ ```typescript
320
+ const apiKey = Netlify.env.get('API_KEY') // string | undefined
321
+ const exists = Netlify.env.has('API_KEY') // boolean
322
+ const all = Netlify.env.toObject() // Record<string, string>
323
+
324
+ // Scoped to current invocation only
325
+ Netlify.env.set('TEMP_VAR', 'value')
326
+ Netlify.env.delete('TEMP_VAR')
327
+ ```
328
+
329
+ ONLY use `Netlify.env.*` for interacting with environment variables in code. Do not use `process.env` or `Deno.env`.
330
+
331
+ ### `Netlify.context`
332
+
333
+ Same as the `context` parameter in the handler. Available from outside the handler scope (e.g., imported modules).
334
+ Returns `null` outside a function invocation.
335
+
336
+ ## netlify.toml Configuration
337
+
338
+ ONLY use `netlify.toml` `[[edge_functions]]` blocks for precise function execution order control. DO NOT use
339
+ `netlify.toml` if there are no edge function ordering requirements. When controlling order, include all edge functions.
340
+
341
+ ```toml
342
+ [[edge_functions]]
343
+ function = "auth"
344
+ path = "/admin"
345
+
346
+ [[edge_functions]]
347
+ function = "injector"
348
+ path = "/admin"
349
+ cache = "manual"
350
+
351
+ [[edge_functions]]
352
+ function = "auth"
353
+ path = "/blog/*"
354
+
355
+ [[edge_functions]]
356
+ function = "rewriter"
357
+ path = "/blog/*"
358
+
359
+ [[edge_functions]]
360
+ function = "highlight"
361
+ pattern = "/products/(.*)"
362
+ excludedPattern = "/products/things/(.*)"
363
+
364
+ [[edge_functions]]
365
+ function = "common"
366
+ path = "/*"
367
+ excludedPath = "/img/*"
368
+ ```
369
+
370
+ **Edge function properties in `netlify.toml`:**
371
+
372
+ | Property | Type | Description |
373
+ |----------|------|-------------|
374
+ | `function` | `string` | Name of the edge function |
375
+ | `path` | `string` | URL pattern to trigger the function (must start with `/`) |
376
+ | `excludedPath` | `string \| string[]` | Routes to exclude from `path` |
377
+ | `pattern` | `string` | Regex-based path matching |
378
+ | `excludedPattern` | `string \| string[]` | Regex patterns to exclude |
379
+ | `cache` | `string` | Set to `'manual'` to enable response caching |
380
+
381
+ ## Execution Order
382
+
383
+ When multiple edge functions match the same path, they execute in this order:
384
+
385
+ 1. Configuration-based edge functions (`netlify.toml`) run first
386
+ 2. Framework-generated edge functions execute before user-defined functions
387
+ 3. Non-cached edge functions execute before cached (`cache: 'manual'`) functions
388
+ 4. Inline-declared `config` exports override duplicate `netlify.toml` declarations for the same function
389
+ 5. Multiple inline edge functions sort alphabetically by filename
390
+
391
+ ## Caveats
392
+
393
+ - If an edge function returns a response, redirects for that path DO NOT occur
394
+ - Edge functions DO NOT execute for rewritten static routing targets
395
+ - `fetch()` or `new URL()` with the same site origin triggers a new request chain, re-running matching functions.
396
+ Use `context.next()` to continue processing instead of re-triggering functions
397
+ - Can only rewrite requests to same-site URLs (use `fetch()` for external content)
398
+ - Cached edge function responses override existing static files at the same path
399
+ - Function failure behavior depends on the `onError` configuration
400
+
401
+ ## Limits
402
+
403
+ | Resource | Limit |
404
+ |----------|-------|
405
+ | Code size (compressed) | 20 MB |
406
+ | Memory per deployment | 512 MB |
407
+ | CPU time per request | 50 ms (excludes waiting time) |
408
+ | Response header timeout | 40 seconds |
409
+
410
+ **Incompatibilities:**
411
+ - Netlify's split testing feature
412
+ - Custom headers from `_headers` or `netlify.toml` config (use response headers in code instead)
413
+ - Netlify prerendering on paths served by edge functions
414
+ - Not included in Netlify's HIPAA-compliant hosting offering
415
+
416
+ **Restrictions:**
417
+ - Can only rewrite requests to same-site URLs
418
+ - Cached edge functions override existing static files
419
+ - No local caching in development (HTTP cache headers are ignored locally)
420
+ - Multiple framework adapters may generate conflicting edge functions
421
+
422
+ ## Common Errors & Solutions
423
+
424
+ ### "Edge function not found"
425
+
426
+ **Cause:** Function file not in the correct directory or missing `config.path`.
427
+
428
+ **Fix:**
429
+
430
+ 1. Place function in `netlify/edge-functions/` (or configured directory)
431
+ 2. Use `.ts` or `.js` extension
432
+ 3. Ensure the file has a default export
433
+ 4. Ensure the function exports a `config` with a `path` or `pattern`
434
+
435
+ ### "CPU time exceeded" (50ms limit)
436
+
437
+ **Cause:** Function CPU-intensive work exceeds the 50ms CPU time limit.
438
+
439
+ **Fix:**
440
+
441
+ 1. Optimize hot paths — the 50ms limit is CPU time only, not wall-clock time
442
+ 2. Waiting on `fetch()`, `context.next()`, or other I/O does not count against the CPU limit
443
+ 3. For heavy computation, offload to a serverless function instead
444
+
445
+ ### Module import failures
446
+
447
+ **Cause:** Incompatible npm package or incorrect import syntax.
448
+
449
+ **Fix:**
450
+
451
+ 1. Use `node:` prefix for Node.js built-in modules (`node:crypto`, not `crypto`)
452
+ 2. Check that npm packages do not rely on native binaries or unsupported dynamic imports
453
+ 3. For Deno URL imports, ensure the URL is accessible and returns valid ESM
454
+
455
+ ### "CORS errors" in browser
456
+
457
+ **Cause:** Missing CORS headers on function response. NEVER add CORS headers unless explicitly requested by the user.
458
+
459
+ **Fix** (only when user asks for CORS support):
460
+
461
+ ```typescript
462
+ return new Response(JSON.stringify(data), {
463
+ headers: {
464
+ 'Content-Type': 'application/json',
465
+ 'Access-Control-Allow-Origin': '*',
466
+ },
467
+ })
468
+ ```
469
+
470
+ ### Environment variable is undefined
471
+
472
+ **Cause:** Variable not available in the function context.
473
+
474
+ **Fix:**
475
+
476
+ 1. Use `Netlify.env.get('VAR_NAME')` — do not use `process.env` or `Deno.env`
477
+ 2. Ensure the variable is set in Netlify UI with correct scopes (Runtime)
478
+ 3. Deploy to Netlify for env var injection