Netlify Functions
Write, configure, and deploy Netlify serverless functions in TypeScript, JavaScript, or Go. Use this when adding an API endpoint or backend route, adding a contact form handler, wiring auth or Identity signup/login hooks, building streaming or AI-proxy responses, scheduling cron jobs, running long background jobs (batch processing/scraping), reacting to deploy or form events, setting up rate limiting or region/memory config, or reading environment variables and secrets inside a function. Covers file locations, the Request/Context/Response handler shape, path routing, config options, and local testing with netlify dev.
- Skill ID
- netlify/context-and-tools/netlify-functions
- Publisher
- netlify
- Repository
- context-and-tools
- Installs
- 0
- Files
- 1
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
netlify/context-and-tools/netlify-functionsInstalls these files- SKILL.md
What this skill tells the agent
Netlify Functions
Reach for the modern default-handler API (.mts TypeScript). Export a default async handler taking a web Request and a Netlify Context, returning a web Response. Avoid the legacy AWS Lambda handler shape unless writing Go or migrating old code (see Legacy at the end).
File locations
- Default directory:
netlify/functions/(relative to base directory). Keep it outside your publish directory or source files ship as static assets. - A function is one file or a subdirectory whose entry file is named
indexor matches the subdirectory name. All of these create a functionhello: netlify/functions/hello.mtsnetlify/functions/hello/hello.mtsnetlify/functions/hello/index.mts- Use
.mts(TS) /.mjs(JS) for ES modules..cts/.cjsforce CommonJS;.ts/.jsfollow the nearestpackage.json"type".
Minimal function
No config export. Serves at /.netlify/functions/hello.
import type { Context } from "@netlify/functions"
export default async (req: Request, context: Context) => {
return new Response("Hello, world!")
}Install types: npm install @netlify/functions (required for TS types; optional for JS).
Read env vars and secrets with Netlify.env.get():
const apiKey = Netlify.env.get("STRIPE_SECRET_KEY")Never hardcode secrets. For the variable to exist at runtime its scope must include Functions. Variables set in netlify.toml are NOT available to functions. Values are frozen per deploy — change them and redeploy to apply.
Response headers are set in code on the returned Response. [[headers]] in netlify.toml, _headers, and redirect header rules apply ONLY to static CDN responses, not function responses. Do not add CORS headers unless explicitly requested.
Custom path routing
Set config.path to route to custom URLs. When set, the function serves ONLY at that path — not at /.netlify/functions/<name>.
import type { Config, Context } from "@netlify/functions"
export default async (req: Request, context: Context) => {
const { city, country } = context.params
return new Response(`You're visiting ${city} in ${country}!`)
}
export const config: Config = {
path: "/travel-guide/:city/:country",
}- Multiple paths:
path: ["/cats", "/dogs"]. - Patterns:
pathsupports `URLPattern` syntax —path: ["/sale/*", "/item/:sku"]. Named groups land oncontext.params. For the query string usereq.url. excludedPath: carve exceptions, e.g.excludedPath: ["/product/*.css"]withpath: "/product/*".preferStatic: true: let a real static file at the URL win.method: restrict methods, e.g.method: ["GET", "POST"].
Fetchable module shape (alternative)
Equivalent to the bare handler; carries config inline and lets you add event handlers.
import type { NetlifyFunction } from "@netlify/functions"
export default {
fetch: (req, context) => new Response("Hello, world!"),
config: { path: "/hello" },
} satisfies NetlifyFunctionContext object
Second handler argument (or getContext() from @netlify/functions when out of handler scope — throws outside a request; wrap in try/catch).
context.params— named path params.context.geo—city,country.code/name,latitude,longitude,subdivision,timezone,postalCode.context.ip— client IP string.context.cookies—get(name)/set(options)/delete(name|options). Cross-subdomain cookies need a custom domain (netlify.appis on the Public Suffix List).context.site—id,name,url.context.deploy—context,id,published,skewProtectionToken.context.account.id.context.server.region.context.requestId.context.waitUntil(promise)— run work after the response is sent (analytics, logs) without blocking. Billing/log duration counts until the promise settles. Available for functions deployed on/after 2025-03-20.
⚠️ Under netlify dev, context.geo and context.ip are mocked — placeholder values that never change. Don't conclude geo code is broken locally. Exercise branches with netlify dev --geo=mock --country=DE and verify on a real deploy.
Config object
Export const config (or the config property of a Fetchable module):
path/excludedPath—string | string[], must start with/.method— one method or array.preferStatic—boolean.background—boolean(see Background).schedule— cron string (see Scheduled). Mutually exclusive withpath/excludedPath.rateLimit—{ action: 'rate_limit'|'rewrite', aggregateBy: 'domain'|'ip'|[...], to?, windowSize, windowLimit }.memory/vcpu— see below; mutually exclusive.region— airport code; see below.
Integrations
import type { Config } from "@netlify/functions"
import { getDatabase } from "@netlify/database"
const db = getDatabase()
export default async (req: Request) => {
const users = await db.sql`SELECT id, email FROM users LIMIT 10`
return Response.json({ users })
}
export const config: Config = { path: "/users" }Blobs: import { getStore } from "@netlify/blobs"; getStore("uploads").set(key, await req.blob()).
purgeCache() from @netlify/functions invalidates the edge cache from inside a function:
import { purgeCache } from "@netlify/functions"
export default async () => {
await purgeCache({ tags: ["products"] }) // omit tags to purge all
return new Response("Purged!", { status: 202 })
}Streaming responses
Return a ReadableStream as the Response body. Limits: 60s execution, 20 MB response.
export default async (req: Request) => {
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Netlify.env.get("OPENAI_API_KEY")}`,
},
body: JSON.stringify({ model: "gpt-4o-mini", stream: true, messages: [/* ... */] }),
})
return new Response(res.body, { headers: { "content-type": "text/event-stream" } })
}To build a stream manually, new ReadableStream({ start(controller) { controller.enqueue(...); controller.close() } }).
Background functions (long-running)
config.background: true. Client gets an immediate 202; the return value is discarded; runs up to 15 minutes. No streaming. Retries: on invocation error, retry after 1 min, then again 2 min later. Send results somewhere other than the client.
import type { Config } from "@netlify/functions"
export default async (req: Request) => {
// Long-running work. Client already has its 202.
}
export const config: Config = { background: true, path: "/process" }Limits: background payload 256 KB. Legacy -background filename suffix still works but prefer config.background.
Scheduled functions (cron)
config.schedule with a cron expression, executed in UTC. The request body is JSON with next_run (ISO-8601). Inline config is TS/JS only — Go must use netlify.toml.
Always compute the UTC time for the target local hour. E.g. 9 AM ET → "0 13 * * *" UTC (note this shifts by an hour across DST; pick the UTC offset you need). Prefer explicit cron over @daily/@hourly shortcuts, which can't target a specific local hour.
import type { Config } from "@netlify/functions"
export default async (req: Request) => {
const { next_run } = await req.json()
console.log("Next invocation at:", next_run)
}
export const config: Config = {
schedule: "0 13 * * *", // 9 AM ET (EST); UTC
}Via netlify.toml (all languages):
[functions."daily-digest"]
schedule = "0 13 * * *"More from netlify
Use this skill in a project
Open any RiverX project, search the Skills panel for netlify/context-and-tools/netlify-functions, and install it in one click.
