@netlify/agent-runner-cli 2.17.0-PAW-612.0 → 2.18.0-smtp.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,78 @@
1
+ ---
2
+ name: general-backend
3
+ description: Use this skill whenever the task involves server-side code — API endpoints, backend logic, request handlers, WebSockets, server-side AI calls, auth checks, or any code that must run on a server rather than in the browser. Covers Netlify Server, Netlify Functions, Edge Functions, and full-stack framework server routes.
4
+ ---
5
+
6
+ # Server-side code on Netlify
7
+
8
+ Where server-side code goes depends on the project. Work out which case applies before writing code, and for a
9
+ new project, before scaffolding a template.
10
+
11
+ ## New project that needs a Netlify Server: no template
12
+
13
+ The create-mode templates are built on a full-stack framework that deploys its server-side rendering as a Netlify
14
+ Function on every path, so a Netlify Server added to a template project never receives requests. If the project
15
+ needs a Netlify Server, don't scaffold a template. A new project needs one when it has any request-driven backend
16
+ code: API endpoints, data saved and read through the backend, WebSockets, server-side AI calls, or an HTTP server
17
+ framework such as Express, Hono, or Fastify.
18
+
19
+ A site with no backend code (static pages, a marketing or content site, a client-only tool) needs neither a server
20
+ nor functions. Use the templates as usual for it.
21
+
22
+ ## Project with a full-stack framework: use its server routes
23
+
24
+ Full-stack frameworks (such as TanStack Start, Next.js, Astro, SvelteKit, Remix, Nuxt) build
25
+ their own server code into Netlify Functions at deploy time. In these projects, put request handling in the
26
+ framework's server routes or server functions instead of adding a Netlify Server. Call Netlify Database, Blobs, and
27
+ the AI Gateway from those routes. Use Netlify Functions alongside them only for the cases listed under "Only when
28
+ Server can't" below.
29
+
30
+ HTTP server frameworks such as Express, Hono, Fastify, and Koa are different: nothing deploys them for you, so they
31
+ run as a Netlify Server, covered next.
32
+
33
+ ## Otherwise: Netlify Server
34
+
35
+ **Netlify Server** runs one long-running Node.js process (Express, Hono, Fastify, or plain `node:http`) from
36
+ `netlify/server/index.mjs`. Put every request-driven piece of backend code in it:
37
+
38
+ - API endpoints and route handlers
39
+ - Server-rendered pages and form handling in your own routes
40
+ - WebSockets and streaming responses
41
+ - Calls to the Netlify AI Gateway, Netlify Database, and Netlify Blobs
42
+ - Short work that continues after the response (seconds, such as sending an email), registered with `waitUntil`
43
+ - Auth checks, webhooks from third-party services, proxies to external APIs
44
+
45
+ Don't pick Functions just because the task is small or looks like a single endpoint.
46
+
47
+ See the **netlify-server** skill for complete setup instructions and usage examples.
48
+
49
+ ## Only when Server can't: Netlify Functions
50
+
51
+ Use **Netlify Functions** alongside the server only for:
52
+
53
+ - **Scheduled functions**: work that runs on a cron schedule, with no incoming request.
54
+ - **Event handlers**: code triggered by platform events, such as Netlify Forms submissions, Netlify Identity events
55
+ (signup, login, validate), or deploy events.
56
+ - **Background functions**: jobs that can run longer than about a minute and must not be cut off midway, such as
57
+ generating a report or processing an upload (up to 15 minutes). When a server instance is replaced by a deploy or
58
+ scaling event, its background work gets only 90 seconds to finish. The server enqueues the job by calling the
59
+ background function and returns right away.
60
+
61
+ Scheduled functions and event handlers run without an incoming request, which a server can't do, and background
62
+ functions aren't cut off when a server instance is replaced. Everything else stays in the server, including the
63
+ endpoints these functions talk to.
64
+
65
+ See the **netlify-functions** skill for complete setup instructions and usage examples.
66
+
67
+ ## Request and response changes at the edge: Edge Functions
68
+
69
+ Use **Edge Functions** for logic that runs on every request before it reaches the server or the site: rewrites and
70
+ redirects based on geolocation or cookies, A/B tests, header changes, or lightweight auth checks. They run in Deno
71
+ close to visitors and don't replace the server for application logic.
72
+
73
+ See the **netlify-edge-functions** skill for setup instructions.
74
+
75
+ ## Existing projects
76
+
77
+ If an existing project already serves its backend from `netlify/functions/`, keep using that for changes to it.
78
+ Don't migrate an existing backend to a Netlify Server unless asked.
@@ -0,0 +1,610 @@
1
+ ---
2
+ name: netlify-functions
3
+ description: Create and deploy Netlify Functions. Use for scheduled tasks, background jobs that can run longer than about a minute, and platform event handlers (Forms submissions, Identity events, deploy events). For API endpoints and other server-side logic, use the netlify-server skill instead.
4
+ ---
5
+
6
+ # Netlify Functions
7
+
8
+ Netlify Functions are serverless API endpoints and background workers that deploy alongside your site. They use
9
+ standard Web API Request/Response objects with zero configuration required.
10
+
11
+ Server-side code goes in a Netlify Server by default (see the `general-backend` and `netlify-server` skills).
12
+ Write functions only for [Scheduled Functions](#scheduled-functions), [Event Handlers](#event-handlers), and
13
+ [Background Functions](#background-functions) for jobs that can run longer than about a minute, or when
14
+ changing a project that already serves its backend from `netlify/functions/`.
15
+
16
+ ## Quick Start
17
+
18
+ Create a function file in the `netlify/functions/` directory:
19
+
20
+ ```typescript
21
+ // netlify/functions/hello.mts
22
+ export default async (req: Request) => {
23
+ const name = new URL(req.url).searchParams.get('name') || 'world'
24
+ return new Response(`Hello, ${name}!`)
25
+ }
26
+ ```
27
+
28
+ ```javascript
29
+ // netlify/functions/hello.mjs
30
+ export default async (req) => {
31
+ const name = new URL(req.url).searchParams.get('name') || 'world'
32
+ return new Response(`Hello, ${name}!`)
33
+ }
34
+ ```
35
+
36
+ Deploy and invoke at `/.netlify/functions/hello?name=Netlify`.
37
+
38
+ ALWAYS use TypeScript (`.mts`) if other functions in the project are TypeScript or if there are no existing functions.
39
+ ONLY use vanilla JavaScript if there are other `.js` files in the functions directory. DO NOT put global logic outside
40
+ of the exported function unless wrapped in a function definition. ALWAYS use the latest format of a function structure.
41
+
42
+ Serverless functions use Node.js and should attempt to use built-in methods where possible.
43
+
44
+ When adding new npm modules, ensure `node_modules` is in the `.gitignore`. If using TypeScript, ensure types are
45
+ installed from `npm install @netlify/functions`.
46
+
47
+ ## Function Structure & Placement
48
+
49
+ **Default directory:** `netlify/functions/` (relative to site base directory)
50
+
51
+ **Naming conventions:**
52
+ - Use `.mts` (TypeScript, recommended) or `.mjs` (JavaScript) extensions. Naming files with `.mts` enables modern ES module syntax
53
+ - Filename becomes the function name and default URL path
54
+ - One default export per file
55
+
56
+ **Valid function paths:**
57
+ - `netlify/functions/hello.mts`
58
+ - `netlify/functions/hello/index.mts`
59
+ - `netlify/functions/hello/hello.mts`
60
+
61
+ **Custom directory** via `netlify.toml`:
62
+
63
+ ```toml
64
+ [functions]
65
+ directory = "my_functions"
66
+ ```
67
+
68
+ ## Handler Signature
69
+
70
+ ```typescript
71
+ export default async (req: Request, context: Context) => Response | void
72
+ ```
73
+
74
+ The handler receives a standard [Web API Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and a
75
+ Netlify-specific `Context` object. Return a standard
76
+ [Web API Response](https://developer.mozilla.org/en-US/docs/Web/API/Response), or return nothing for a 204 response.
77
+
78
+ ```typescript
79
+ import type { Context } from '@netlify/functions'
80
+
81
+ export default async (req: Request, context: Context) => {
82
+ const body = await req.json()
83
+ return Response.json({ received: body })
84
+ }
85
+ ```
86
+
87
+ ## In-Code Config & Routing
88
+
89
+ Prefer to use in-code configuration via exporting a `config` object. Prefer to provide a friendly path using the config
90
+ object. ONLY serverless functions use `/.netlify/functions/{function_name}` path by default. If you set a specific path
91
+ via config or netlify.toml, it will only be available at that new path.
92
+
93
+ Export a `config` object to configure routing and behavior:
94
+
95
+ ```typescript
96
+ import type { Config } from '@netlify/functions'
97
+
98
+ export default async (req: Request, context: Context) => {
99
+ const { name } = context.params
100
+ return Response.json({ pet: name })
101
+ }
102
+
103
+ export const config: Config = {
104
+ path: '/api/pets/:name',
105
+ }
106
+ ```
107
+
108
+ **Config properties:**
109
+
110
+ | Property | Type | Description |
111
+ |----------|------|-------------|
112
+ | `path` | `string \| string[]` | Custom URL path(s) with route parameters |
113
+ | `excludedPath` | `string \| string[]` | Paths to exclude from matching |
114
+ | `preferStatic` | `boolean` | If `true`, static file takes precedence over function |
115
+ | `method` | `string \| string[]` | HTTP method(s) to match (`GET`, `POST`, etc.) |
116
+ | `schedule` | `string` | Cron expression for scheduled functions |
117
+ | `background` | `boolean` | If `true`, run in background mode (fire-and-forget) |
118
+
119
+ `path` and `excludedPath` support substring patterns or the URLPattern syntax from the web platform.
120
+
121
+ **Route parameters:**
122
+
123
+ ```typescript
124
+ // :param - named parameter
125
+ export const config: Config = { path: '/api/users/:id' }
126
+ // context.params.id = '123' for /api/users/123
127
+
128
+ // * - wildcard
129
+ export const config: Config = { path: '/api/*' }
130
+ // Matches /api/anything/here
131
+ ```
132
+
133
+ **Multiple paths:**
134
+
135
+ ```typescript
136
+ export const config: Config = {
137
+ path: ['/api/pets/:name', '/api/animals/:name'],
138
+ }
139
+ ```
140
+
141
+ ## Context Object
142
+
143
+ The `context` parameter provides Netlify-specific metadata:
144
+
145
+ ### `context.ip`
146
+
147
+ Client IP address as a string.
148
+
149
+ ### `context.geo`
150
+
151
+ Geolocation data for the client:
152
+
153
+ ```typescript
154
+ context.geo.city // string - city name
155
+ context.geo.country // { code: string, name: string } - ISO 3166
156
+ context.geo.latitude // number
157
+ context.geo.longitude // number
158
+ context.geo.subdivision // { code: string, name: string } - ISO 3166
159
+ context.geo.timezone // string - IANA timezone
160
+ context.geo.postalCode // string
161
+ ```
162
+
163
+ ### `context.site`
164
+
165
+ Site metadata:
166
+
167
+ ```typescript
168
+ context.site.id // string - unique site ID
169
+ context.site.name // string - site subdomain name
170
+ context.site.url // string - primary site URL
171
+ ```
172
+
173
+ ### `context.deploy`
174
+
175
+ Deploy metadata:
176
+
177
+ ```typescript
178
+ context.deploy.context // string - deploy context (e.g., 'production', 'deploy-preview')
179
+ context.deploy.id // string - unique deploy ID
180
+ context.deploy.published // boolean - is this the published deploy?
181
+ ```
182
+
183
+ ### `context.account`
184
+
185
+ ```typescript
186
+ context.account.id // string - team/account ID
187
+ ```
188
+
189
+ ### `context.requestId`
190
+
191
+ Unique request identifier string.
192
+
193
+ ### `context.params`
194
+
195
+ Route parameters from `config.path`:
196
+
197
+ ```typescript
198
+ // For path '/api/users/:id' and request '/api/users/42':
199
+ context.params.id // '42'
200
+ ```
201
+
202
+ ### `context.cookies`
203
+
204
+ Simplified cookie interface:
205
+
206
+ ```typescript
207
+ // Read
208
+ const session = context.cookies.get('session')
209
+
210
+ // Set
211
+ context.cookies.set({ name: 'session', value: 'abc123', path: '/', httpOnly: true })
212
+
213
+ // Delete
214
+ context.cookies.delete('session')
215
+ ```
216
+
217
+ ### `context.server`
218
+
219
+ ```typescript
220
+ context.server.region // string - e.g., 'us-east-1'
221
+ ```
222
+
223
+ ### `context.user`
224
+
225
+ The user who signed in through Netlify's SSO to reach a private project or a site protected by Netlify team login.
226
+ `undefined` when the request didn't come from such a user. Netlify verifies the visitor and sets this itself, so it
227
+ can't be spoofed with request headers.
228
+
229
+ ```typescript
230
+ context.user.id // string - user ID
231
+ context.user.email // string - user email address
232
+ context.user.expiresAt // Date - when the user's access expires
233
+ ```
234
+
235
+ ```typescript
236
+ // netlify/functions/whoami.mts
237
+ import type { Config, Context } from '@netlify/functions'
238
+
239
+ export default async (req: Request, context: Context) => {
240
+ if (!context.user) return new Response('Unauthorized', { status: 401 })
241
+ return Response.json({
242
+ id: context.user.id,
243
+ email: context.user.email,
244
+ expiresAt: context.user.expiresAt.toISOString(),
245
+ })
246
+ }
247
+
248
+ export const config: Config = { path: '/whoami' }
249
+ ```
250
+
251
+ This is the Netlify team member viewing the site, not a Netlify Identity user; for end-user accounts on your site use
252
+ the `netlify-identity` skill. See the
253
+ [changelog post](https://www.netlify.com/changelog/2026-10-07-context-user-in-functions/) and the
254
+ [Functions API docs](https://docs.netlify.com/build/functions/api/#user).
255
+
256
+ ### `context.waitUntil(promise)`
257
+
258
+ Extends execution after the response is sent. Use for analytics, logging, or cleanup tasks.
259
+
260
+ ```typescript
261
+ export default async (req: Request, context: Context) => {
262
+ context.waitUntil(
263
+ fetch('https://analytics.example.com/track', {
264
+ method: 'POST',
265
+ body: JSON.stringify({ path: new URL(req.url).pathname }),
266
+ }),
267
+ )
268
+ return new Response('OK')
269
+ }
270
+ ```
271
+
272
+ ## Global `Netlify` Object
273
+
274
+ Available in all function contexts:
275
+
276
+ ### `Netlify.env`
277
+
278
+ Access environment variables:
279
+
280
+ ```typescript
281
+ const apiKey = Netlify.env.get('API_KEY') // string | undefined
282
+ const exists = Netlify.env.has('API_KEY') // boolean
283
+ const all = Netlify.env.toObject() // Record<string, string>
284
+
285
+ // Scoped to current invocation only
286
+ Netlify.env.set('TEMP_VAR', 'value')
287
+ Netlify.env.delete('TEMP_VAR')
288
+ ```
289
+
290
+ ALWAYS use `Netlify.env.get()` instead of `process.env` when reading environment variables in Netlify Functions. It
291
+ provides consistent behavior across all function contexts.
292
+
293
+ ### `Netlify.context`
294
+
295
+ Same as the `context` parameter in the handler. Available from outside the handler scope (e.g., imported modules).
296
+ Returns `null` outside a function invocation.
297
+
298
+ ## Background Functions
299
+
300
+ Background functions operate the same as standard serverless functions and are syntactically the same. They run for up
301
+ to 15 minutes without blocking the client. The client receives an immediate 202 response.
302
+
303
+ Enable background mode by setting `background: true` in the function's config:
304
+
305
+ ```typescript
306
+ // netlify/functions/process-data.mts
307
+ import type { Config, Context } from '@netlify/functions'
308
+
309
+ export default async (req: Request, context: Context) => {
310
+ const data = await req.json()
311
+
312
+ // Long-running work (up to 15 minutes)
313
+ for (const item of data.items) {
314
+ await processItem(item)
315
+ }
316
+
317
+ // Return value is ignored - client already received 202
318
+ }
319
+
320
+ export const config: Config = {
321
+ background: true,
322
+ }
323
+ ```
324
+
325
+ ```javascript
326
+ // netlify/functions/process-data.mjs
327
+ export default async (req, context) => {
328
+ const data = await req.json()
329
+
330
+ for (const item of data.items) {
331
+ await processItem(item)
332
+ }
333
+ }
334
+
335
+ export const config = {
336
+ background: true,
337
+ }
338
+ ```
339
+
340
+ **Key behavior:**
341
+ - Client receives `202 Accepted` immediately
342
+ - Function continues executing for up to 15 minutes
343
+ - Response body is ignored
344
+ - No streaming support
345
+ - Cannot use `config.schedule`
346
+ - Retry logic: first retry after 1 minute, second retry after 2 minutes on failure
347
+ - Any data background functions produce should be stored in Netlify Blobs or a database
348
+
349
+ **Legacy filename convention:** Naming the file with a `-background` suffix (e.g., `process-data-background.mts`) is still
350
+ supported and continues to work. New functions should prefer the `config.background` property above.
351
+
352
+ Invoke like any function: `POST /.netlify/functions/process-data` (or use the `path` config property).
353
+
354
+ ## Scheduled Functions
355
+
356
+ Run functions on a cron schedule. Use for periodic tasks like data sync, cleanup, or report generation.
357
+
358
+ ```typescript
359
+ // netlify/functions/daily-cleanup.mts
360
+ import type { Config } from '@netlify/functions'
361
+
362
+ export default async (req: Request) => {
363
+ const { next_run } = await req.json()
364
+ console.log('Running cleanup. Next run:', next_run)
365
+
366
+ await performCleanup()
367
+ }
368
+
369
+ export const config: Config = {
370
+ schedule: '@daily',
371
+ }
372
+ ```
373
+
374
+ ```javascript
375
+ // netlify/functions/daily-cleanup.mjs
376
+ export default async (req) => {
377
+ const { next_run } = await req.json()
378
+ console.log('Running cleanup. Next run:', next_run)
379
+
380
+ await performCleanup()
381
+ }
382
+
383
+ export const config = {
384
+ schedule: '@daily',
385
+ }
386
+ ```
387
+
388
+ The minimum interval is 1 minute. Scheduled functions do not return response bodies.
389
+
390
+ **Cron syntax** (UTC timezone):
391
+
392
+ ```
393
+ ┌───────────── minute (0-59)
394
+ │ ┌───────────── hour (0-23)
395
+ │ │ ┌───────────── day of month (1-31)
396
+ │ │ │ ┌───────────── month (1-12)
397
+ │ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
398
+ │ │ │ │ │
399
+ * * * * *
400
+ ```
401
+
402
+ **Supported extensions:**
403
+
404
+ | Extension | Equivalent | Description |
405
+ |-----------|------------|-------------|
406
+ | `@yearly` | `0 0 1 1 *` | January 1st at midnight |
407
+ | `@monthly` | `0 0 1 * *` | 1st of each month at midnight |
408
+ | `@weekly` | `0 0 * * 0` | Sunday at midnight |
409
+ | `@daily` | `0 0 * * *` | Every day at midnight |
410
+ | `@hourly` | `0 * * * *` | Every hour at minute 0 |
411
+
412
+ Cron syntax supports extensions defined in the RFC except for `@reboot` and `@annually`.
413
+
414
+ **Alternative:** Configure schedule in `netlify.toml` instead of in-code config. ONLY do this for consistency or if
415
+ explicitly asked to keep all schedules in one place:
416
+
417
+ ```toml
418
+ [functions."daily-cleanup"]
419
+ schedule = "@daily"
420
+ ```
421
+
422
+ **Key behavior:**
423
+ - Scheduled functions only run on **published deploys** (not deploy previews or branch deploys)
424
+ - Request body is `{ "next_run": "<ISO-8601 timestamp>" }`
425
+ - 30-second execution time limit (use background functions for longer tasks)
426
+ - No direct URL invocation in production
427
+
428
+ **Local testing:**
429
+
430
+ ```bash
431
+ netlify functions:invoke daily-cleanup
432
+ ```
433
+
434
+ ## Region
435
+
436
+ Functions deploy to `cmh` (Ohio) by default. This is a deliberate choice: US East is centrally located for an international audience, has a broad provider ecosystem, and gives most projects the lowest overall latency without any configuration.
437
+
438
+ Do NOT override `config.region` unless the user has stated a specific reason — for example, a database or backend service in another region with measurable roundtrip savings, a data-residency requirement, or an audience concentrated in one region whose compute dependencies (database, backend services) also live in that region.
439
+
440
+ Two constraints to be aware of before adding `config.region`:
441
+
442
+ - A function runs in exactly one region. Don't try to deploy the same function to multiple regions — if the user wants geo-routing, route between distinct functions with an edge function instead.
443
+ - For framework adapter–generated functions (Next.js, Astro, Nuxt, etc.) the region must be set site-wide in the Netlify UI, not via `config.region` in code. The generated files can't carry per-function config.
444
+
445
+ See [Region](https://docs.netlify.com/build/functions/configuration#region) for the full list of supported regions and details.
446
+
447
+ ## Memory or vCPU
448
+
449
+ Functions run with 1024 MB of memory and a proportional amount of compute by default. The default fits most workloads, and raising it has a direct cost impact: function billing scales linearly with the configured size.
450
+
451
+ Do NOT set `config.memory` or `config.vcpu` speculatively. Only reach for them when:
452
+
453
+ - The workload is known to be memory- or compute-intensive (AI inference, image/PDF manipulation, large payload processing, CPU-bound work).
454
+ - The function is hitting out-of-memory errors or timeouts caused by the function's own work, rather than by waiting on an external service or database.
455
+
456
+ `memory` and `vcpu` configure the same underlying resource and are mutually exclusive — set one, not both. See [Memory or vCPU](https://docs.netlify.com/build/functions/configuration#memory-or-vcpu) for accepted values and the exact mapping.
457
+
458
+ ## Response Streaming
459
+
460
+ Synchronous functions can stream responses for large payloads (up to 20 MB):
461
+
462
+ ```typescript
463
+ export default async (req: Request) => {
464
+ const stream = new ReadableStream({
465
+ start(controller) {
466
+ controller.enqueue(new TextEncoder().encode('Hello '))
467
+ controller.enqueue(new TextEncoder().encode('World'))
468
+ controller.close()
469
+ },
470
+ })
471
+
472
+ return new Response(stream, {
473
+ headers: { 'Content-Type': 'text/plain' },
474
+ })
475
+ }
476
+ ```
477
+
478
+ ## Event Handlers
479
+
480
+ A function can subscribe to platform events by exporting an object instead of a function as its default. Each event has
481
+ a named handler property.
482
+
483
+ ```typescript
484
+ import type { DeploySucceededEvent, UserSignupEvent } from '@netlify/functions'
485
+
486
+ export default {
487
+ deploySucceeded(event: DeploySucceededEvent) {
488
+ console.log(`Deploy ${event.deploy.id} succeeded`)
489
+ },
490
+
491
+ userSignup(event: UserSignupEvent) {
492
+ return {
493
+ user: {
494
+ ...event.user,
495
+ appMetadata: { ...event.user.appMetadata, roles: ['member'] },
496
+ },
497
+ }
498
+ },
499
+ }
500
+ ```
501
+
502
+ A single function can declare multiple handlers; multiple functions can also subscribe to the same event — both will run.
503
+
504
+ The default export can also be an object with a `fetch` method instead of a bare function. Prefer the bare default export shown in [Handler Signature](#handler-signature); reach for the object form only if (1) other functions in the project already use it, or (2) the same function handles HTTP requests AND subscribes to one or more platform events:
505
+
506
+ ```typescript
507
+ export default {
508
+ fetch(req: Request, context: Context) {
509
+ return new Response('Hello')
510
+ },
511
+ }
512
+ ```
513
+
514
+ **Available handlers:**
515
+
516
+ | Handler | Trigger |
517
+ |---|---|
518
+ | `fetch` | HTTP request (equivalent to a bare function default export) |
519
+ | `deployBuilding`, `deploySucceeded`, `deployFailed`, `deployDeleted`, `deployLocked`, `deployUnlocked` | Deploy lifecycle |
520
+ | `userSignup`, `userLogin`, `userValidate`, `userModified`, `userDeleted` | Identity lifecycle |
521
+ | `formSubmitted` | Form submission verified |
522
+
523
+ ### Identity handlers: deny an action
524
+
525
+ `userSignup`, `userLogin`, `userValidate`, and `userModified` can reject the action by calling `event.deny()`. The end user
526
+ receives a 401. Do NOT throw — `event.deny()` is the canonical denial mechanism and does not produce an error in observability.
527
+
528
+ ```typescript
529
+ import type { UserLoginEvent } from '@netlify/functions'
530
+
531
+ export default {
532
+ userLogin(event: UserLoginEvent) {
533
+ if (!event.user.email?.endsWith('@example.com')) {
534
+ return event.deny()
535
+ }
536
+ },
537
+ }
538
+ ```
539
+
540
+ If multiple functions subscribe to the same event, the first to call `event.deny()` aborts the chain.
541
+
542
+ ## Limits
543
+
544
+ | Resource | Limit |
545
+ |----------|-------|
546
+ | Memory | 1024 MB |
547
+ | Synchronous execution time | 60 seconds |
548
+ | Scheduled execution time | 30 seconds |
549
+ | Background execution time | 15 minutes |
550
+ | Buffered request/response payload | 6 MB |
551
+ | Background function payload | 256 KB |
552
+ | Streaming response payload | 20 MB |
553
+ | Functions per site | Unlimited |
554
+
555
+ ## Common Errors & Solutions
556
+
557
+ ### "Function not found" (404)
558
+
559
+ **Cause:** Function file not in the correct directory or wrong extension.
560
+
561
+ **Fix:**
562
+
563
+ 1. Place function in `netlify/functions/` (or configured directory)
564
+ 2. Use `.mts` or `.mjs` extension (not `.ts` or `.js`)
565
+ 3. Ensure the file has a default export
566
+
567
+ ### "Function timeout" (502 or 504)
568
+
569
+ **Cause:** Function exceeds execution time limit.
570
+
571
+ **Fix:**
572
+
573
+ 1. Synchronous functions: optimize to complete within 60 seconds
574
+ 2. For long-running tasks, use background functions (`-background` suffix, 15-min limit)
575
+ 3. For periodic tasks, use scheduled functions
576
+
577
+ ### "Payload too large" (413)
578
+
579
+ **Cause:** Request or response exceeds 6 MB limit.
580
+
581
+ **Fix:**
582
+
583
+ 1. Reduce payload size
584
+ 2. For large file handling, use Netlify Blobs for storage and pass references instead of raw data
585
+ 3. Use streaming responses for large outputs (up to 20 MB)
586
+
587
+ ### "CORS errors" in browser
588
+
589
+ **Cause:** Missing CORS headers on function response. NEVER add CORS headers unless explicitly requested by the user.
590
+
591
+ **Fix** (only when user asks for CORS support):
592
+
593
+ ```typescript
594
+ return new Response(JSON.stringify(data), {
595
+ headers: {
596
+ 'Content-Type': 'application/json',
597
+ 'Access-Control-Allow-Origin': '*',
598
+ },
599
+ })
600
+ ```
601
+
602
+ ### Environment variable is undefined
603
+
604
+ **Cause:** Variable not available in the function context.
605
+
606
+ **Fix:**
607
+
608
+ 1. Use `Netlify.env.get('VAR_NAME')` instead of `process.env.VAR_NAME`
609
+ 2. Ensure the variable is set in Netlify UI with correct scopes (Runtime)
610
+ 3. Deploy to Netlify or use the Vite plugin for env var injection