netlify/context-and-tools/netlify-functions
Claude Code and agent skill by netlify/context-and-tools
- community
Checking that the SKILL.md file is still in the repository.
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, identifying which Netlify user is calling a private or team-login-protected app, 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.
Install
npx vskill@latest install netlify/context-and-tools/netlify-functionsSKILL.md
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".
⚠️ Same-name JS and TS files: the JS one wins. If the functions directory has e.g. items.js and items.ts, the JavaScript function is deployed and the TypeScript file is silently ignored — edits to the .ts have no effect. Keep one file per function name; delete or rename the stale .js when moving a function to TypeScript.
Minimal function
No config export. Serves at /.netlify/functions/hello.
Install types: npm install @netlify/functions (required for TS types; optional for JS).
Read env vars and secrets with Netlify.env.get():
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>.
- 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 default function; carries config inline and lets you add event handlers.
Context 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.user— the Netlify user who logged in to a private or team-login-protected project:id,email(may beundefined),expiresAt(Date). See below.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.
⚠️ context.geo/context.ip are mocked under netlify dev — test with netlify dev --geo=mock --country=DE.
Who is calling: context.user
⚠️ context.user is often undefined: public projects, basic password protection, local netlify dev, and functions deployed before October 5, 2026 (redeploy to enable). Always handle it; key records on id (email may be undefined).
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
Blobs: import { getStore } from "@netlify/blobs"; getStore("uploads").set(key, await req.blob()).
AI Gateway: construct new OpenAI() with no API key — on credit-based plans AI Gateway sets the provider env vars automatically; then await client.responses.create({ model: "gpt-6-luna", input: [...] }).
Cache: await purgeCache({ tags: ["products"] }) from @netlify/functions invalidates the edge cache (omit tags to purge all).
Streaming responses
Return a ReadableStream as the Response body. Limits: 60s execution, 20 MB response.
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.
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.
Via netlify.toml (all languages):
Constraints: 30s limit (use background for longer); only fire on published deploys (not Deploy Previews/branch deploys — invoke manually with Run now); no URL invocation; no streaming; no request payloads/POST data; incompatible with Split Testing. All extensions supported except @reboot and @annually.
Platform-event functions
Export a default object with handlers named after events. They always run in the background — no response to a client. Combine with fetch in the same function. Every handler is fully typed; import event types from @netlify/functions.
Deploy events (event.deploy, event.site; return void): deployBuilding, deploySucceeded, deployFailed, deployDeleted, deployLocked, deployUnlocked.
Identity events (event.user, only id guaranteed):
| Handler | Can deny? | Can mutate? | |
|---|---|---|---|
userValidate | Yes | Yes | |
userSignup | Yes | Yes | |
userLogin | Yes | Yes | |
userModified | Yes | Yes | |
userDeleted | No | No |
- Deny: call
event.deny()inside the handler → end user gets401. First function to deny aborts the chain. - Mutate: return
{ user: {...} }to persist changes; returnundefinedto pass through.
Form events: formSubmitted → event.data (object keyed by field name). Return void.
Multiple functions can handle the same event (all run). Netlify signs each event (JWS) and verifies before invoking, blocking external requests. Legacy filename convention (file named after the event, payload via await req.json() → payload) still works but prefer typed handlers.
Region
⚠️ Do NOT override config.region without a stated reason; the cmh (US East, Ohio) default is deliberate.
When justified — e.g. an EU-resident database:
Airport codes (self-serve): cmh, dub, fra, gru, iad, lhr, nrt, pdx, sfo, sin, syd, yul. Support-assisted: cdg, mxp. Each function runs in exactly one region (no multi-region geo-routing). Region selection needs Pro/Enterprise. Framework-adapter-generated functions can't take export const config — set region at project level in the UI under Cloud compute > Functions > Region. After changing region, redeploy. Function-level region beats the site-level UI setting.
Memory / vCPU
⚠️ Do NOT set config.memory or config.vcpu speculatively — billing scales linearly with size.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/netlify-functions/SKILL.md
Popularity
- Repository stars
- 10
- Repository forks
- 4
- 7-day trend score
- 12
Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 23 Mar 2026, 7 months ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/netlify/context-and-tools/netlify-functions)