---
name: netlify-functions
description: Create and deploy Netlify Functions including standard, background, and scheduled functions. Use when implementing API endpoints, server-side logic, background processing, or scheduled tasks.
---

# Netlify Functions

Netlify Functions are serverless API endpoints and background workers that deploy alongside your site. They use
standard Web API Request/Response objects with zero configuration required.

## Quick Start

Create a function file in the `netlify/functions/` directory:

```typescript
// netlify/functions/hello.mts
export default async (req: Request) => {
  const name = new URL(req.url).searchParams.get('name') || 'world'
  return new Response(`Hello, ${name}!`)
}
```

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

Deploy and invoke at `/.netlify/functions/hello?name=Netlify`.

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

Serverless functions use Node.js and should attempt to use built-in methods where possible.

When adding new npm modules, ensure `node_modules` is in the `.gitignore`. If using TypeScript, ensure types are
installed from `npm install @netlify/functions`.

## Function Structure & Placement

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

**Naming conventions:**
- Use `.mts` (TypeScript, recommended) or `.mjs` (JavaScript) extensions. Naming files with `.mts` enables modern ES module syntax
- Filename becomes the function name and default URL path
- One default export per file

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

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

```toml
[functions]
directory = "my_functions"
```

## Handler Signature

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

The handler receives a standard [Web API Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and a
Netlify-specific `Context` object. Return a standard
[Web API Response](https://developer.mozilla.org/en-US/docs/Web/API/Response), or return nothing for a 204 response.

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

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

## 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. ONLY serverless functions use `/.netlify/functions/{function_name}` path by default. If you set a specific path
via config or netlify.toml, it will only be available at that new path.

Export a `config` object to configure routing and behavior:

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

export default async (req: Request, context: Context) => {
  const { name } = context.params
  return Response.json({ pet: name })
}

export const config: Config = {
  path: '/api/pets/:name',
}
```

**Config properties:**

| Property | Type | Description |
|----------|------|-------------|
| `path` | `string \| string[]` | Custom URL path(s) with route parameters |
| `excludedPath` | `string \| string[]` | Paths to exclude from matching |
| `preferStatic` | `boolean` | If `true`, static file takes precedence over function |
| `method` | `string \| string[]` | HTTP method(s) to match (`GET`, `POST`, etc.) |
| `schedule` | `string` | Cron expression for scheduled functions |
| `background` | `boolean` | If `true`, run in background mode (fire-and-forget) |

`path` and `excludedPath` support substring patterns or the URLPattern syntax from the web platform.

**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/pets/:name', '/api/animals/:name'],
}
```

## Context Object

The `context` parameter provides Netlify-specific metadata:

### `context.ip`

Client IP address as a string.

### `context.geo`

Geolocation data for the client:

```typescript
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`

Site metadata:

```typescript
context.site.id   // string - unique site ID
context.site.name // string - site subdomain name
context.site.url  // string - primary site URL
```

### `context.deploy`

Deploy metadata:

```typescript
context.deploy.context   // string - deploy context (e.g., 'production', 'deploy-preview')
context.deploy.id        // string - unique deploy ID
context.deploy.published // boolean - is this the published deploy?
```

### `context.account`

```typescript
context.account.id // string - team/account ID
```

### `context.requestId`

Unique request identifier string.

### `context.params`

Route parameters from `config.path`:

```typescript
// For path '/api/users/:id' and request '/api/users/42':
context.params.id // '42'
```

### `context.cookies`

Simplified cookie interface:

```typescript
// Read
const session = context.cookies.get('session')

// Set
context.cookies.set({ name: 'session', value: 'abc123', path: '/', httpOnly: true })

// Delete
context.cookies.delete('session')
```

### `context.server`

```typescript
context.server.region // string - e.g., 'us-east-1'
```

### `context.waitUntil(promise)`

Extends execution after the response is sent. Use for analytics, logging, or cleanup tasks.

```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')
}
```

## Global `Netlify` Object

Available in all 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')
```

ALWAYS use `Netlify.env.get()` instead of `process.env` when reading environment variables in Netlify Functions. It
provides consistent behavior across all function contexts.

### `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.

## Background Functions

Background functions operate the same as standard serverless functions and are syntactically the same. They run for up
to 15 minutes without blocking the client. The client receives an immediate 202 response.

Enable background mode by setting `background: true` in the function's config:

```typescript
// netlify/functions/process-data.mts
import type { Config, Context } from '@netlify/functions'

export default async (req: Request, context: Context) => {
  const data = await req.json()

  // Long-running work (up to 15 minutes)
  for (const item of data.items) {
    await processItem(item)
  }

  // Return value is ignored - client already received 202
}

export const config: Config = {
  background: true,
}
```

```javascript
// netlify/functions/process-data.mjs
export default async (req, context) => {
  const data = await req.json()

  for (const item of data.items) {
    await processItem(item)
  }
}

export const config = {
  background: true,
}
```

**Key behavior:**
- Client receives `202 Accepted` immediately
- Function continues executing for up to 15 minutes
- Response body is ignored
- No streaming support
- Cannot use `config.schedule`
- Retry logic: first retry after 1 minute, second retry after 2 minutes on failure
- Any data background functions produce should be stored in Netlify Blobs or a database

**Legacy filename convention:** Naming the file with a `-background` suffix (e.g., `process-data-background.mts`) is still
supported and continues to work. New functions should prefer the `config.background` property above.

Invoke like any function: `POST /.netlify/functions/process-data` (or use the `path` config property).

## Scheduled Functions

Run functions on a cron schedule. Use for periodic tasks like data sync, cleanup, or report generation.

```typescript
// netlify/functions/daily-cleanup.mts
import type { Config } from '@netlify/functions'

export default async (req: Request) => {
  const { next_run } = await req.json()
  console.log('Running cleanup. Next run:', next_run)

  await performCleanup()
}

export const config: Config = {
  schedule: '@daily',
}
```

```javascript
// netlify/functions/daily-cleanup.mjs
export default async (req) => {
  const { next_run } = await req.json()
  console.log('Running cleanup. Next run:', next_run)

  await performCleanup()
}

export const config = {
  schedule: '@daily',
}
```

The minimum interval is 1 minute. Scheduled functions do not return response bodies.

**Cron syntax** (UTC timezone):

```
 ┌───────────── minute (0-59)
 │ ┌───────────── hour (0-23)
 │ │ ┌───────────── day of month (1-31)
 │ │ │ ┌───────────── month (1-12)
 │ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
 │ │ │ │ │
 * * * * *
```

**Supported extensions:**

| Extension | Equivalent | Description |
|-----------|------------|-------------|
| `@yearly` | `0 0 1 1 *` | January 1st at midnight |
| `@monthly` | `0 0 1 * *` | 1st of each month at midnight |
| `@weekly` | `0 0 * * 0` | Sunday at midnight |
| `@daily` | `0 0 * * *` | Every day at midnight |
| `@hourly` | `0 * * * *` | Every hour at minute 0 |

Cron syntax supports extensions defined in the RFC except for `@reboot` and `@annually`.

**Alternative:** Configure schedule in `netlify.toml` instead of in-code config. ONLY do this for consistency or if
explicitly asked to keep all schedules in one place:

```toml
[functions."daily-cleanup"]
schedule = "@daily"
```

**Key behavior:**
- Scheduled functions only run on **published deploys** (not deploy previews or branch deploys)
- Request body is `{ "next_run": "<ISO-8601 timestamp>" }`
- 30-second execution time limit (use background functions for longer tasks)
- No direct URL invocation in production

**Local testing:**

```bash
netlify functions:invoke daily-cleanup
```

## Region

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.

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.

Two constraints to be aware of before adding `config.region`:

- 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.
- 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.

See [Region](https://docs.netlify.com/build/functions/configuration#region) for the full list of supported regions and details.

## Memory or vCPU

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.

Do NOT set `config.memory` or `config.vcpu` speculatively. Only reach for them when:

- The workload is known to be memory- or compute-intensive (AI inference, image/PDF manipulation, large payload processing, CPU-bound work).
- 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.

`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.

## Response Streaming

Synchronous functions can stream responses for large payloads (up to 20 MB):

```typescript
export default async (req: Request) => {
  const stream = new ReadableStream({
    start(controller) {
      controller.enqueue(new TextEncoder().encode('Hello '))
      controller.enqueue(new TextEncoder().encode('World'))
      controller.close()
    },
  })

  return new Response(stream, {
    headers: { 'Content-Type': 'text/plain' },
  })
}
```

## Event Handlers

A function can subscribe to platform events by exporting an object instead of a function as its default. Each event has
a named handler property.

```typescript
import type { DeploySucceededEvent, UserSignupEvent } from '@netlify/functions'

export default {
  deploySucceeded(event: DeploySucceededEvent) {
    console.log(`Deploy ${event.deploy.id} succeeded`)
  },

  userSignup(event: UserSignupEvent) {
    return {
      user: {
        ...event.user,
        appMetadata: { ...event.user.appMetadata, roles: ['member'] },
      },
    }
  },
}
```

A single function can declare multiple handlers; multiple functions can also subscribe to the same event — both will run.

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:

```typescript
export default {
  fetch(req: Request, context: Context) {
    return new Response('Hello')
  },
}
```

**Available handlers:**

| Handler | Trigger |
|---|---|
| `fetch` | HTTP request (equivalent to a bare function default export) |
| `deployBuilding`, `deploySucceeded`, `deployFailed`, `deployDeleted`, `deployLocked`, `deployUnlocked` | Deploy lifecycle |
| `userSignup`, `userLogin`, `userValidate`, `userModified`, `userDeleted` | Identity lifecycle |
| `formSubmitted` | Form submission verified |

### Identity handlers: deny an action

`userSignup`, `userLogin`, `userValidate`, and `userModified` can reject the action by calling `event.deny()`. The end user
receives a 401. Do NOT throw — `event.deny()` is the canonical denial mechanism and does not produce an error in observability.

```typescript
import type { UserLoginEvent } from '@netlify/functions'

export default {
  userLogin(event: UserLoginEvent) {
    if (!event.user.email?.endsWith('@example.com')) {
      return event.deny()
    }
  },
}
```

If multiple functions subscribe to the same event, the first to call `event.deny()` aborts the chain.

## Limits

| Resource | Limit |
|----------|-------|
| Memory | 1024 MB |
| Synchronous execution time | 60 seconds |
| Scheduled execution time | 30 seconds |
| Background execution time | 15 minutes |
| Buffered request/response payload | 6 MB |
| Background function payload | 256 KB |
| Streaming response payload | 20 MB |
| Functions per site | Unlimited |

## Common Errors & Solutions

### "Function not found" (404)

**Cause:** Function file not in the correct directory or wrong extension.

**Fix:**

1. Place function in `netlify/functions/` (or configured directory)
2. Use `.mts` or `.mjs` extension (not `.ts` or `.js`)
3. Ensure the file has a default export

### "Function timeout" (502 or 504)

**Cause:** Function exceeds execution time limit.

**Fix:**

1. Synchronous functions: optimize to complete within 60 seconds
2. For long-running tasks, use background functions (`-background` suffix, 15-min limit)
3. For periodic tasks, use scheduled functions

### "Payload too large" (413)

**Cause:** Request or response exceeds 6 MB limit.

**Fix:**

1. Reduce payload size
2. For large file handling, use Netlify Blobs for storage and pass references instead of raw data
3. Use streaming responses for large outputs (up to 20 MB)

### "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')` instead of `process.env.VAR_NAME`
2. Ensure the variable is set in Netlify UI with correct scopes (Runtime)
3. Deploy to Netlify or use the Vite plugin for env var injection
