netlify-edge-functions
Guide for writing Netlify Edge Functions. Use when building middleware, geolocation-based logic, request/response manipulation, authentication checks, A/B testing, or any low-latency edge compute. Covers Deno runtime, context.next() middleware pattern, geolocation, and when to choose edge vs serverless.
How do I install this agent skill?
npx skills add https://github.com/netlify/context-and-tools --skill netlify-edge-functionsIs this agent skill safe to install?
- Gen Agent Trust Hubpass
This skill provides a comprehensive and legitimate technical guide for developing Netlify Edge Functions. It covers syntax, configuration, middleware patterns, and the Deno runtime environment without any detected security risks.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
- Runlayerpass
1 file scanned · No issues
What does this agent skill do?
Netlify Edge Functions
Edge functions run on Netlify's globally distributed edge network (Deno runtime), providing low-latency responses close to users.
Check the framework adapter first
For framework projects, check the framework reference (the netlify-frameworks skill) before hand-writing an edge function — framework adapters emit their own edge middleware, so the behavior you need may already be generated. A custom edge function that duplicates adapter-generated middleware causes conflicts.
Syntax
import type { Config, Context } from "@netlify/edge-functions";
export default async (req: Request, context: Context) => {
return new Response("Hello from the edge!");
};
export const config: Config = {
path: "/hello",
};
Place files in netlify/edge-functions/. Uses .ts, .js, .tsx, or .jsx extensions.
Config Object
export const config: Config = {
path: "/api/*", // URLPattern path(s)
excludedPath: "/api/public/*", // Exclusions
method: ["GET", "POST"], // HTTP methods
onError: "bypass", // "fail" (default), "bypass", or "/error-page"
cache: "manual", // Enable response caching
};
Scope path narrowly — path: "/*" intercepts every request, including static assets. A /* match runs the edge function on every CSS, JS, image, and font request, not just your HTML pages — adding latency to each asset and billing an edge invocation for it. Match only the routes you need (e.g. path: "/", path: "/app/*"), or keep a broad path but exclude static assets with excludedPath (e.g. excludedPath: ["/*.css", "/*.js", "/*.png", "/*.woff2"]).
Cache headers on an edge response do nothing without cache: "manual". Setting Cache-Control (or any cache header) on the Response an edge function returns has no effect unless the function also opts in with config.cache = "manual". It's both or neither: without the flag the response is never cached, no matter what headers you set.
Declaring edge functions: inline config vs netlify.toml
An edge function runs only if it is bound to a path. Bind it either with an inline export const config = { path: ... } in the function file (shown above), or with an [[edge_functions]] entry in netlify.toml that names the file:
[[edge_functions]]
path = "/admin/*"
function = "auth" # runs netlify/edge-functions/auth.ts
A file in netlify/edge-functions/ with no path binding still deploys, but silently never runs. There is no build error and no warning — nothing routes a request to it, so it is never invoked. If an edge function "isn't doing anything," first confirm it declares a path inline or has a matching [[edge_functions]] entry.
Chaining multiple edge functions on one path
When several edge functions match the same path, they run as a chain in this order:
- Functions declared in
netlify.tomlrun first, in the order they appear in the file (top to bottom). - Functions declared inline (via
export const config) run next, in alphabetical order by filename. - Functions configured for caching (
cache: "manual") always run after non-caching ones.
To guarantee a specific order (e.g. an auth gate that must run before a personalization rewrite), declare the functions in netlify.toml in the order you want — don't depend on inline config, whose order is alphabetical by filename and easy to get wrong. Declaring the same function both inline and in netlify.toml merges them into an inline declaration (inline config wins), which forfeits the deterministic netlify.toml ordering.
Edge functions run before redirects
In Netlify's request chain, edge functions execute before redirect and rewrite rules ([[redirects]], _redirects). Two consequences bite often:
- An edge function is matched against the original requested URL, not a redirect/rewrite destination. Scope its
pathto the URL the client actually requests — an edge function declared on the target of a rewrite will not fire for requests that only reach that target via the rewrite. - If an edge function returns a
Response, the request chain stops there and redirect rules for that path never run. Returncontext.next()(orundefined) if you want redirects to still apply.
Middleware Pattern
Use context.next() to invoke the next handler in the chain and optionally modify the response:
export default async (req: Request, context: Context) => {
// Before: modify request or short-circuit
if (!isAuthenticated(req)) {
return new Response("Unauthorized", { status: 401 });
}
// Continue to origin/next function
const response = await context.next();
// After: modify response
response.headers.set("x-custom-header", "value");
return response;
};
Return undefined to pass through without modification:
export default async (req: Request, context: Context) => {
if (!shouldHandle(req)) return; // continues to next handler
return new Response("Handled");
};
Geolocation and IP
export default async (req: Request, context: Context) => {
const { city, country, subdivision, timezone } = context.geo;
const ip = context.ip;
if (country?.code === "DE") {
return Response.redirect(new URL("/de", req.url));
}
};
Local dev with mocked geo: netlify dev --geo=mock --country=US
Cookies
Read and write cookies through the context.cookies helper instead of hand-parsing the Cookie header or building Set-Cookie strings:
export default async (req: Request, context: Context) => {
const bucket = context.cookies.get("bucket"); // read from the request
context.cookies.set({ name: "bucket", value: "a" }); // set on the response
context.cookies.delete("legacy_session"); // tell the client to delete it
return context.next();
};
cookies.get(name)— reads a named cookie from the incoming request.cookies.set(options)— sets a cookie on the outgoing response (same option shape as the webCookieStore.setstandard).cookies.delete(name)— instructs the client to delete the cookie.
Environment Variables
Use Netlify.env (not process.env or Deno.env):
const secret = Netlify.env.get("API_SECRET");
Module Support
- Node.js builtins:
import { randomBytes } from "node:crypto"; - npm packages: Install via npm and import by name
- Deno modules: URL imports (e.g.,
import X from "https://esm.sh/package")
For URL imports, use an import map:
// import_map.json
{ "imports": { "html-rewriter": "https://ghuc.cc/worker-tools/html-rewriter/index.ts" } }
# netlify.toml
[functions]
deno_import_map = "./import_map.json"
When to Use Edge vs Serverless
| Use Edge Functions for | Use Serverless Functions for |
|---|---|
| Low-latency responses | Long-running operations (up to 15 min) |
| Request/response manipulation | Complex Node.js dependencies |
| Geolocation-based logic | Database-heavy operations |
| Auth checks and redirects | Background/scheduled tasks |
| A/B testing, personalization | Tasks needing > 512 MB memory |
Limits
| Resource | Limit |
|---|---|
| CPU time | 50 ms per request |
| Memory | 512 MB per deployed set |
| Response header timeout | 40 seconds |
| Code size | 20 MB compressed |
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/netlify/context-and-tools/netlify-edge-functions">View netlify-edge-functions on skillZs</a>