· 1 observation
Technical updates · 1
Package or technical metadata updated
View technical comparison →Netlify v1.6.0
Build and deploy on Netlify
From the developer
Not specified in the available declarations: limitations, pricing & account requirements.
Deploy this site to Netlify and send me the live URL
Developer declarations; not independently tested. Listing last checked Nov 7, 2026. Package observed Nov 7, 2026.
From the marketplace listing
Gives the agent factual reference for Netlify platform primitives — functions, edge functions, blobs, database, identity, image CDN, forms, config, caching, frameworks, AI Gateway, and deployment — plus the official hosted Netlify MCP server, which lets the agent create and manage Netlify projects, deploys, and environment variables after you sign in to Netlify.
Language: English · Automatically detected from descriptions.
Exact text from the indicated source. A mention alone does not establish support for your task.
Gives the agent factual reference for Netlify platform primitives — functions, edge functions, blobs, database, identity, image CDN, forms, config, caching, frameworks, AI Gateway, and deployment — plus the official hosted Netlify MCP server, which lets the agent create and manage Netlify projects, deploys, and environment variables after you sign in to Netlify.
· 1 observation
Package or technical metadata updated
View technical comparison →· 2 observations
Package or technical metadata updated
View technical comparison →· 14 observations
Full description changed from “Guide for using Netlify Image CDN for image optimization and transformation. Use when serving optimized images, creating responsive image markup, setting up...” to “Transforms images on demand via Netlify Image CDN's /.netlify/images endpoint with query parameters for...
View comparison →Full description changed from “Use when the task involves authentication, user signups, logins, password recovery, OAuth providers, role-based access control, or protecting routes and...” to “Add user authentication to a Netlify site with @netlify/identity — signup/login/logout, Google/GitHub/GitLab/Bitbucket OAuth,...
View comparison →Full description changed from “Guide for writing Netlify serverless functions. Use when creating API endpoints, background processing, scheduled tasks, or any server-side logic using...” to “Write, configure, and deploy Netlify serverless functions in TypeScript, JavaScript, or Go. Use this when adding an API...
View comparison →Full description changed from “Guide for deploying web frameworks on Netlify. Use when setting up a framework project (Vite/React, Astro, TanStack Start, Next.js, Nuxt, SvelteKit, Remix)...” to “Deploy and configure web frameworks on Netlify — build settings and SSR/edge adapters plus local platform emulation and...
View comparison →Full description changed from “Guide for using Netlify Forms for HTML form handling. Use when adding contact forms, feedback forms, file upload forms, or any form that should be collected...” to “Serverless form handling on Netlify-hosted sites — detects HTML forms at deploy time, stores submissions, filters spam,...
View comparison →Full description changed from “Guide for writing Netlify Edge Functions. Use when building middleware, geolocation-based logic, request/response manipulation, authentication checks, A/B...” to “Write and configure Netlify Edge Functions — TypeScript/JavaScript handlers running in a Deno runtime at the network edge....
View comparison →Full description changed from “Deploy projects to Netlify with the Netlify CLI. Use when the user wants to link a repo, validate deploy settings, run a deploy, or choose between preview...” to “Create, configure, and manage Netlify deploys from code — reach for this when setting up Git continuous deployment, running...
View comparison →Full description changed from “Reference for netlify.toml configuration. Use when configuring build settings, redirects, rewrites, headers, deploy contexts, environment variables, or any...” to “Configure Netlify builds and routing via netlify.toml, _redirects, and _headers. Use when setting a build command or...
View comparison →Full description changed from “Guide for controlling caching on Netlify's CDN. Use when configuring cache headers, setting up stale-while-revalidate, implementing on-demand cache purge, or...” to “Cache dynamic and static responses on Netlify's CDN from Functions, Edge Functions, and proxies. Use when you add...
View comparison →Full description changed from “Guide for using Netlify Blobs object storage. Use when storing files, images, documents, or simple key-value data without a full database. Covers getStore(),...” to “Store and retrieve unstructured objects, files, and cache-like state on Netlify with the @netlify/blobs module. Use when...
View comparison →Full description changed from “Guide for using Netlify AI Gateway to access AI models. Use when adding AI capabilities or selecting/changing AI models. Must be read before choosing a...” to “Use Netlify AI Gateway to call OpenAI, Anthropic Claude, Google Gemini, TypeSafe (Jev), or OpenRouter-hosted models...
View comparison →Full description changed from “Build and deploy on Netlify” to “Netlify platform skills — functions, edge functions, blobs, database, identity, image CDN, forms, config, CLI, frameworks, caching, AI gateway, and...”. Brand color changed from “#00AD9F” to “#05BDBA”. Capabilities updated; open the comparison for...
View comparison →Full description changed from “Build and deploy on Netlify” to “Netlify platform skills — functions, edge functions, blobs, database, identity, image CDN, forms, config, CLI, frameworks, caching, AI gateway, and...”. Brand color changed from “#00AD9F” to “#05BDBA”. Capabilities updated; open the comparison for...
View comparison →Package or technical metadata updated
View technical comparison →· 12 observations
Supporting file metadata differs
View technical comparison →Find competitors to follow as you develop your plugin.
Val Town helps users create, inspect, run, and deploy apps; manage files, branches, schedules, environment variables, SQLite data, and bl...
Shared terms: deploy, blob, environment, variable
Build and deploy web apps and AI agents on Vercel.
Shared terms: deploy, mcp, project, agent
Build production MCP servers and apps with Noodle Seed: author one TypeScript server.ts with the noodle CLI, validate and test locally, t...
Shared terms: deploy, cli, hosted, server
Catalyst by Zoho is a full-stack development platform for building web applications, APIs, automation workflows, and AI-powered software....
Shared terms: deploy, database, mcp, project
Build and deploy your apps with Prisma.
Shared terms: deploy, database, environment, server
--- name: netlify-access-control description: 'Picks the right Netlify site-protection layer and disambiguates the three unrelated "auth" concepts users conflate — app-user login (Netlify Identity), site-load gating (Password Protection / project visibility), and dashboard SAML SSO. Use it when asked to password-protect a site or Deploy Preview, make a project private/public, restrict a site to your team, require SSO to view a site, set up company-wide app SSO, or invite users to a private project. Also use it for SSO-session symptoms on protected sites: "logged out mid-session", 401s after about an hour, or token expiry/refresh questions. Not for wiring auth code — route app-login setup to netlify-identity.' --- # Netlify access control — pick the protection layer This skill routes you to the correct protection layer. It does not teach each one. **These settings have no public API, CLI command, or MCP tool.** Never curl `api.netlify.com` or read local auth tokens to inspect or change them — give the user the dashboard path and checklist. On failure, report what you tried and stop. ## First: disambiguate "auth" — three unrelated layers Users constantly conflate these. Identify which one is meant before recommending anything. 1. **Netlify Identity** — "who is this user *inside my app*." Issues `nf_jwt`. → route to the **netlify-identity** skill; not covered here. 2. **Password Protection / project visibility** — "can this request load the site at all." Covered here. 3. **Team/Org SAML SSO** — "can you log in to the Netlify *dashboard*." Gates dashboard access; also underlies team-login site protection. Sessions are separate. The same provider (e.g. Google) can appear twice unrelated — Identity OAuth for app users vs. SAML IdP for team members. **Double-login footgun:** a Password-Protection/team-login perimeter session and an Identity app session have **no bridge** — no shared cookie, no header forwarding, no JWT exchange. Don't try to wire them together. For the combined layered pattern and its tradeoffs, see `references/two-layer-pattern.md`. **Want company-wide app SSO with a single sign-in (no double login)?** Recommend the **Auth0 extension** federating to the corporate IdP *before* the two-layer stack. ## Decision guide (this skill's job) - Restrict entire site to your team, invite by email → **Private project** (Credit-based) or **Team login protection** (Password Protection). - Share with anyone holding one shared password → **Basic password protection** (Pro) or **Password** visibility (Pro, Credit-based). - Keep production public, protect previews only → scope **Previews only** / **Non-production deploys only**. - Require SSO to *view a site* → Organization/Team SSO with **Only SSO allowed (strict)**, then Password Protection with **Team login protection**. - Protect specific pages/sections with multiple passwords → **Basic authentication with custom HTTP headers** (formerly Selective password protection): https://docs.netlify.com/manage/security/secure-access-to-sites/basic-authentication-with-custom-http-headers/ - Authenticate your own end users → **Netlify Identity** / **OAuth provider tokens** / **Role-based access control with JWT** → route to netlify-identity. - Block malicious/automated traffic or AI crawlers → **Advanced Web Security** (WAF / Firewall Traffic Rules / rate limiting) or **User Agent Blocker** extension: https://docs.netlify.com/build/build-with-ai/block-ai-crawlers/ ## Key distinction: Private vs Password - **Private** already requires Netlify credentials — no shared password. Invite by email; recommended for team-only access. - **Password** = one universal shared password anyone can use (including managing team members, who must also enter it). No SSO. - **Team login protection** = same mechanism as Private: a visitor must log in as a member of your Netlify team, and **Reviewers** you invite can get in too (unlimited and not counted toward the member count on legacy plans; Pro or h
Preview of the first 4,000 characters. Read full instructions →
Referenced files: 1 — file references are metadata, not package contents.
--- name: netlify-agent-runner description: Run AI agent tasks remotely on Netlify using Claude, Codex, or Gemini. Use when the user wants to run an AI agent on their site, get a second opinion from another model, or delegate development tasks to run remotely against their repo. --- # Netlify Agent Runner Run AI coding agents (Claude, Codex, Gemini) remotely on Netlify infrastructure to automate development tasks on your site. ## Prerequisites - The site must be **linked to a Netlify project** (via `netlify link` or `netlify init`). - **Or skip linking entirely:** pass `--project <name>` (a project ID or name) directly to `netlify agents:create` to target any Netlify site without linking first. - The Netlify CLI must be installed and authenticated - Agent runs **consume plan credits**. If the account has no available credits — or the agent/AI usage limit has been reached — `netlify agents:create` is **blocked** and the run won't start. That's an account/plan-state issue to surface to the user, not something to work around. ## Use only documented CLI surfaces Interact with agent tasks only through the documented `netlify agents:*` commands (plus `netlify --help` and the public CLI reference). Do **not** go around the CLI: - **Do not curl `https://api.netlify.com/...`** to fetch, create, or stop a task — the endpoint shapes are not part of the public contract. - **Do not run `netlify api <method>`** as a recovery hatch when a documented command fails. - **Do not read auth tokens** out of `~/Library/Preferences/netlify/config.json` (or anywhere on disk) to authenticate side-channel calls. If a documented command fails, report the exact error and context to the user and stop — don't invent an undocumented way to reach the task. ## How Agent Tasks Run Read this before creating a task — agent tasks behave differently from running an agent locally, and the differences are easy to miss. - **Remote, not local.** Tasks run on Netlify infrastructure, not on your machine. They operate on the site's **connected repository**, not your local working tree. The remote agent only sees what has been pushed to the remote — it cannot see uncommitted or unpushed changes. - **Branch-based.** By default a task runs against the production branch (`main` or `master`). To choose a different *base* branch for the agent to start from, use `-b <branch>` and make sure that branch has been **pushed to the remote first**, or the agent will be working from code that doesn't exist remotely. `-b` sets the base (starting) branch — not where the results are written (see the next bullet). - **Output lands on a new branch — not in place.** The agent does **not** commit its changes onto the base branch you selected. It pushes its work to a **new branch** with its own **Deploy Preview**, so your existing branch (or `main`) is never overwritten. Review the task's results on that new branch / Deploy Preview — don't expect the base branch to change directly. - **Asynchronous.** `netlify agents:create` returns as soon as the task is queued — it does **not** block until the work is finished. When the command returns, the task is still running remotely. - **No webhooks or callbacks.** Nothing notifies you when a task changes state or completes. To find out what's happening, you have to **poll** with `netlify agents:show <task-id>` or `netlify agents:list`. - **Statuses are terminal or not.** A task moves through `new` → `running` → one of `done`, `error`, or `cancelled`. Keep polling until the status is one of those last three before you act on the results. ### Typical workflow 1. **Create** a task: `netlify agents:create "<prompt>" -a <agent>`. Note the task ID it returns (use `--json` to capture it reliably). 2. **Poll** for status: `netlify agents:show <task-id>`. Repeat periodically — there is no completion notification — until the status is `done`, `error`, or `cancelled`. 3. **Review** the results once the task reaches `done` (or inspect the failure on `e
Preview of the first 4,000 characters. Read full instructions →
---
name: netlify-ai-gateway
description: Use Netlify AI Gateway to call OpenAI, Anthropic Claude, Google Gemini, TypeSafe (Jev), or OpenRouter-hosted models (xAI/DeepSeek/Meta/Mistral/Qwen) from Netlify Functions or Edge Functions without managing provider accounts or API keys. Reach for this when adding an AI feature to a Netlify app — a chatbot, text summarizer, image generator, joke/content generator, form-submission routing or analysis, or any LLM call — or when wiring the OpenAI/Anthropic/Gemini/TypeSafe/OpenRouter SDK into a Netlify Function, choosing which env vars to use, streaming long generations, or debugging why gateway calls fail at build time or return 401.
---
# Netlify AI Gateway
Call AI models from Netlify compute using the provider's official SDK. The gateway injects provider credentials automatically — instantiate the SDK with no args and it works.
**Use the provider SDK with injected env credentials.** Do not hand-roll a raw `fetch()` against the gateway URL, and do not wire calls to `NETLIFY_AI_GATEWAY_KEY` / `NETLIFY_AI_GATEWAY_URL` as your default path — those are for third-party/unsupported libraries only (see below).
## Footguns (read first)
- **Not browser-callable.** Gateway calls belong in Functions or Edge Functions — never in client-side code. The browser has no injected credentials.
- **Runtime-only credentials.** Never call the gateway from build scripts, prerender/SSG, or build plugins — those get no credentials and fail. Do AI work at request time; cache to Netlify Blobs if output must look precomputed.
- **60-second sync timeout.** A gateway call in a synchronous function is bound by the 60s function timeout. Stream long generations (SDK streaming + `ReadableStream`), or use a background function that persists output for the client to fetch. Never leave a slow generation unstreamed.
- **Requires one production deploy.** The gateway does not activate until a project has at least one production deploy. Even for local dev, run `netlify deploy --prod` once first.
- **Don't hardcode model lists.** Available models change. Check the live providers endpoint (`https://api.netlify.com/api/v1/ai-gateway/providers/detailed`) rather than baking in a static list.
- **OpenRouter SDK needs 1.2.43+.** Earlier versions ignore `OPENROUTER_BASE_URL`, call openrouter.ai directly, and fail with `401 Missing Authentication header`.
## Where code goes
Write normal Function/handler code — there is no AI-specific file type. A function at `netlify/functions/joke.js` exporting `config = { path: "/api/joke" }` is served at `/api/joke` under both `netlify dev` and production.
## Provider SDKs (instantiate with no args)
The gateway injects each provider's own env vars, so the official SDK works with zero config.
Anthropic Claude:
```js
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic(); // uses ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello!' }]
});
```
OpenAI:
```js
import OpenAI from 'openai';
const openai = new OpenAI(); // uses OPENAI_API_KEY, OPENAI_BASE_URL
const completion = await openai.chat.completions.create({
model: 'gpt-5',
messages: [{ role: 'user', content: 'Hello!' }]
});
```
Google Gemini:
```js
import { GoogleGenAI } from '@google/genai';
const genAI = new GoogleGenAI({}); // uses GEMINI_API_KEY, GOOGLE_GEMINI_BASE_URL
const result = await genAI.models.generateContent({
model: 'gemini-2.5-pro',
contents: 'Hello!'
});
```
TypeSafe (Jev) — structured decisions (e.g. routing/classifying form submissions):
```ts
import type { Config, Context } from '@netlify/functions';
import { choice, TypeSafeClient } from '@typesafe-ai/sdk';
export default async (req: Request, context: Context) => {
const body = await req.json().catch(() => undefined);
if (body === undefined)
return Response.json({ error: 'Request body muPreview of the first 4,000 characters. Read full instructions →
---
name: netlify-blobs
description: Store and retrieve unstructured objects, files, and cache-like state on Netlify with the @netlify/blobs module. Use when persisting user file uploads (images/documents), caching computed output from functions or Background Functions, serving downloadable assets, storing JSON blobs keyed by ID, or seeding deploy-specific data. Reach for this for key/value or object storage from Functions, Edge Functions, or Build Plugins — not for per-user, transactional, or relational data (use Netlify DB for that). Triggers include "save an uploaded file", "cache API results", "store generated site map", "key/value store for a function", or "file uploads without a database".
---
# Netlify Blobs
Modern syntax — import from `@netlify/blobs` and open a store, then call methods on the handle:
```ts
import { getStore, getDeployStore, listStores } from "@netlify/blobs";
import type { Context } from "@netlify/functions"; // or "@netlify/edge-functions"
```
In Functions, Edge Functions, and Build Plugins, `siteID`, `deployID`, `token` (and `region` for `getDeployStore`) are injected automatically. Install with `npm install @netlify/blobs`.
**Not a database.** For dynamic, per-user, transactional, or relational data, use Netlify DB. Blobs is for objects, files, and cache-like state, optimized for frequent reads and infrequent writes.
**Store scope is a footgun — read this first.** `getStore` opens a **site-wide store shared across ALL deploy contexts**: code on a deploy preview reads, overwrites, and deletes production data. Never run destructive tests or seed throwaway data from a preview against a site-wide store. Use `getDeployStore()` or a context-specific store name for isolation.
## Choosing a store type
- `getStore(name)` — site-wide, shared across all deploys. Data persists across deploys; previews see production data.
- `getDeployStore(name)` — deploy-specific, scoped to one deploy. Kept in sync on rollback, cleaned up on deploy deletion. Use for isolation and for anything a failed deploy must not corrupt.
- **Build plugins can READ from any of the site's stores, but WRITE only to deploy-specific stores** (`getDeployStore`). File-based uploads also write only to deploy-specific stores.
## Core writes and reads
```ts
const uploads = getStore("file-uploads");
// set: value is ArrayBuffer | Blob | string
await uploads.set(key, file, { metadata: { country: "Spain" } });
// setJSON: any JSON-serializable value
await uploads.setJSON(key, { hello: "world" });
// get: returns value or null. type: text (default) | json | arrayBuffer | blob | stream
const entry = await uploads.get(key); // string
const obj = await uploads.get(key, { type: "json" });
if (entry === null) { /* 404 */ }
```
`set`/`setJSON` overwrite an existing key. Both return `{ modified, etag }` (`etag` omitted when no new entry was generated).
### Persisting a user upload (Function)
```ts
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";
import { v4 as uuid } from "uuid";
export default async (req: Request, context: Context) => {
const form = await req.formData();
const file = form.get("file") as File;
const key = uuid();
const uploads = getStore("file-uploads");
await uploads.set(key, file, { metadata: { country: context.geo.country.name } });
return new Response("Submission saved");
};
```
Edge functions are identical except `import type { Context } from "@netlify/edge-functions";`.
### Reading (Function)
```ts
export default async (req: Request, context: Context) => {
const { key } = context.params;
const uploads = getStore("file-uploads");
const entry = await uploads.get(key);
if (entry === null) return new Response(`Not found: ${key}`, { status: 404 });
return new Response(entry);
};
```
## Metadata and conditional reads
```ts
// getWithMetadata: data + metadata + etag; supports conditional reads
const { data, etag, metadata } = await uploads.getWPreview of the first 4,000 characters. Read full instructions →
---
name: netlify-caching
description: Cache dynamic and static responses on Netlify's CDN from Functions, Edge Functions, and proxies. Use when you add caching or cache-control headers to a function response, tune cache TTL or stale-while-revalidate, set up the durable cache, vary a cache key by query/header/cookie/country/language, purge or invalidate the cache by site or cache tag, use the programmatic Cache API (caches.open/match/put) or @netlify/cache helpers (fetchWithCache/cacheHeaders/getCacheStatus), speed up an expensive API call, add ISR or on-demand revalidation, or debug why a response is or isn't cached via the Cache-Status header.
---
# Netlify caching
## Cache-control header to reach for
Dynamic responses (Functions, Edge Functions, proxies) are **NOT cached by default** — you must opt in. Set `Netlify-CDN-Cache-Control` on the response:
```ts
import type { Context } from "@netlify/functions";
export default async (req: Request, context: Context) => {
return new Response("Hello world", {
headers: {
'Netlify-CDN-Cache-Control': 'public, durable, max-age=60, stale-while-revalidate=120'
}
});
};
```
Header choice (most specific wins; `CDN-Cache-Control`/`Cache-Control` always pass downstream):
- `Netlify-CDN-Cache-Control` — Netlify CDN only. **Reach for this.**
- `CDN-Cache-Control` — all CDNs that support it.
- `Cache-Control` — any CDN or the browser.
**Legacy path to avoid:** On-demand Builders do **not** support these headers or `Netlify-Vary` — they use a TTL pattern and key on URL path only. Don't reach for ODBs in new code.
## Footguns (read first)
- **Only `GET` is cached.** POST/PUT/etc. are never cached regardless of headers — expose cacheable data on a GET route (inputs in the URL or query string).
- **`netlify dev` does not emulate the CDN cache.** A local cache miss every time is expected. Verify caching on a deployed URL (Deploy Preview or production) via its `Cache-Status` header.
- **Without `Netlify-Vary: query=...`, the full query string is the cache key** — every distinct query string (`utm_*`, `fbclid`, …) is a separate cache entry. Enumerate only the params that change the response.
- **Static assets are fresh for up to a year** — a shorter `max-age` is ignored. They change only on a new deploy or manual purge.
- **basic-auth on ANY page disables caching for the ENTIRE site.**
- **`durable` is serverless-only** — it has no effect on Edge Function responses.
- Never opt sensitive content out of automatic invalidation — it can stay publicly cached after deploys/firewall changes.
## Directives
- `public` cache it / `private` browser-only, not Netlify's shared cache / `no-store` don't cache.
- `s-maxage=N` seconds in Netlify's shared cache (overrides `max-age` there).
- `max-age=N` seconds in any cache.
- `stale-while-revalidate=N` serve stale for N seconds after expiry while revalidating in background.
- `durable` (serverless only) store in Netlify's durable cache so other edge nodes reuse it instead of re-invoking the function.
Defaults when no header is set — static: `Netlify-CDN-Cache-Control: public, s-maxage=31536000, must-revalidate`; dynamic: `Cache-Control: public, max-age=0, must-revalidate`.
## Cache key variation — `Netlify-Vary`
Comma-delimited instructions on the response; pipe-delimited value lists:
```
Netlify-Vary: query=item_id|page, country=es+de|us, cookie=ab_test|is_logged_in
```
- `query=a|b` subset, or bare `query` for all params. Keys case-sensitive; param order irrelevant.
- `header=Device-Type|App-Version` — custom + most standard headers.
- `language=en|es+pt` — `+` groups; checked against `Accept-Language` with quality weighting.
- `country=us|es+pt` — GeoIP, ISO 3166-1 two-letter codes; `+` groups.
- `cookie=ab_test|is_logged_in` — target specific keys, not the whole `Cookie` header.
**Cannot vary by header on:** `Accept*`, `Cache-Control`, `Connection`, `Content-Length`, `Cookie`, `Host`, `If-*`, `Range`, `Referer`, `Upgrade`, `UserPreview of the first 4,000 characters. Read full instructions →
---
name: netlify-cli-and-deploy
description: Guide for using the Netlify CLI and deploying sites. Use when installing the CLI, linking sites, deploying (Git-based or manual), managing environment variables, or running local development. Covers netlify dev, netlify deploy, Git vs non-Git workflows, and environment variable management.
---
# Netlify CLI and Deployment
## Installation
```bash
npm install -g netlify-cli # Global (for local dev)
npm install netlify-cli -D # Local (for CI)
```
Requires Node.js 18.14.0+.
## Authentication
```bash
netlify login # Opens browser for OAuth
netlify status # Check auth + linked site status
```
For CI, set `NETLIFY_AUTH_TOKEN` environment variable instead.
## Linking a Site
Check if already linked with `netlify status`. If not:
```bash
# Interactive
netlify link
# By Git remote (if using Git)
netlify link --git-remote-url https://github.com/org/repo
# Create new site
netlify init # With Git CI/CD setup
netlify init --manual # Without Git CI/CD
```
Site ID is stored in `.netlify/state.json`. Add `.netlify` to `.gitignore`.
## Deploying
### Git-Based Deploys (Continuous Deployment)
Set up with `netlify init`. Automatic deploys trigger on push/PR:
- Push to production branch → production deploy
- Open PR → deploy preview with unique URL
- Push to other branches → branch deploy
Build runs on Netlify's servers. Configure build settings in `netlify.toml`.
### Manual / Local Deploys (No Git Required)
Build locally, then upload:
```bash
netlify deploy # Draft deploy (preview URL)
netlify deploy --prod # Production deploy
netlify deploy --dir=dist # Specify output directory
```
This works without Git — useful for prototypes, local-only projects, or CI pipelines.
## Local Development
### Option 1: netlify dev
```bash
netlify dev
```
Wraps your framework's dev server and provides:
- Environment variable injection
- Functions and edge functions
- Redirects and headers processing
### Option 2: Netlify Vite Plugin (Vite-based projects)
For projects using Vite (React SPA, TanStack Start, SvelteKit, Remix), the Vite plugin provides Netlify platform primitives directly in the framework's dev server:
```bash
npm install @netlify/vite-plugin
```
```typescript
// vite.config.ts
import netlify from "@netlify/vite-plugin";
export default defineConfig({ plugins: [netlify()] });
```
Then run your normal dev command (`npm run dev`) — no `netlify dev` wrapper needed. This gives you access to Blobs, DB, Functions, and environment variables during development.
See the **netlify-frameworks** skill for framework-specific local dev guidance.
## Environment Variables
### CLI Management
```bash
# Set
netlify env:set API_KEY "value"
netlify env:set API_KEY "value" --secret # Hidden from logs
netlify env:set API_KEY "value" --context production # Context-specific
# Get
netlify env:get API_KEY
# List
netlify env:list
netlify env:list --plain > .env # Export to file
# Import from file
netlify env:import .env
# Delete
netlify env:unset API_KEY
```
### Context Scoping
Variables can be scoped to deploy contexts:
```bash
netlify env:set API_URL "https://api.prod.com" --context production
netlify env:set API_URL "https://api.staging.com" --context deploy-preview
netlify env:set DEBUG "true" --context branch:feature-x
```
### Accessing in Code
- **Server-side (Functions)**: Use `Netlify.env.get("VAR")` (preferred) or `process.env.VAR`
- **Client-side (Vite)**: Only `VITE_`-prefixed vars via `import.meta.env.VITE_VAR`
- **Client-side (Astro)**: Only `PUBLIC_`-prefixed vars via `import.meta.env.PUBLIC_VAR`
**Never use `VITE_` or `PUBLIC_` prefix for secrets** — these are exposed to the browser.
## Useful Commands
| Command | Description |
|---|---|
| `netlify status` | Auth and site link status |
| `netlify dev` | Start local dev server |
| `netlify build` | Run build locally (mimics Netlify environment) |
| `nePreview of the first 4,000 characters. Read full instructions →
Referenced files: 4 — file references are metadata, not package contents.
---
name: netlify-config
description: Configure Netlify builds and routing via netlify.toml, _redirects, and _headers. Use when setting a build command or publish directory, adding redirects or rewrites or proxies, adding an SPA fallback rewrite, setting custom response headers or basic auth, managing environment variables and secrets, scoping vars per deploy context, marking a var as secret, disabling secret scanning, configuring functions bundling, ignoring builds, or wiring up a monorepo or JavaScript SPA on Netlify.
---
# Netlify configuration
Config lives in three files at the repo **root** (or the base/package directory for monorepos):
- `netlify.toml` — build, contexts, plugins, functions, redirects, headers, dev.
- `_redirects` — plain-text redirect/rewrite rules, saved to the **publish directory**, no extension.
- `_headers` — plain-text response headers, saved to the **publish directory**.
`netlify.toml` values **take precedence over the Netlify UI** when they conflict. Paths in `netlify.toml` are absolute relative to the **base directory** (root `/` by default).
## Modern vs legacy syntax to reach for
- Functions bundler: use `node_bundler = "esbuild"`. `zisi` is the legacy JS default; TypeScript always uses `esbuild`.
- Temporary redirect: use `status = 302`. `307` is **unsupported**.
- Gatsby Image CDN: use `NETLIFY_IMAGE_CDN`, not the deprecated `GATSBY_CLOUD_IMAGE_CDN`.
- Injecting env values into TOML: `key = "$VAR"` is **NOT supported** (except `signed` in proxy redirects). Use a build-command `sed` substitution or a build plugin (see below).
## `netlify.toml` build + contexts
```toml
[build]
base = "frontend"
publish = "dist"
command = "npm run build"
environment = { NODE_VERSION = "18" }
[context.production]
publish = "output/"
command = "make publish"
[context.deploy-preview]
publish = "dist/"
[context."feat/branch"] # quote names with special characters
command = "npm run preview"
```
`[build]` runs in **Bash**. Context-aware keys include `[build]` and `[[plugins]]` — but **NOT** `[[redirects]]` or `[[headers]]` (those are always global). Precedence, least→most specific: UI < toml < any-context property < `[context.<name>]` < `[context.branchname]`.
## Redirects and rewrites
`_redirects` rules are processed **first**, then `netlify.toml`; within each, the **first matching rule top-to-bottom wins** — list specific rules before general ones. Edge functions run before redirects.
SPA history-`pushState` fallback (required for clean URLs):
```
/* /index.html 200
```
```toml
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
```
`_redirects` syntax — `from to [status] [conditions]`, `#` comments, paths case-sensitive, URL-encode special chars:
```
/home / 301
/my-redirect / 302
/ecommerce /store-closed 404 # custom 404 for a path
/pass-through /index.html 200 # rewrite
/best-pets/dogs /best-pets/cats.html 200! # force/shadow (! or force=true)
/news/* /blog/:splat # splat
/news/:month/:date/:year/:slug /blog/:year/:month/:date/:slug # placeholders
/store id=:id /blog/:id 301 # query params
/ /anz 302 Country=au,nz # no spaces in value list
/israel/* /israel/he/:splat 302 Language=he
/* /legacy/:splat 200 Cookie=is_legacy,my_other_cookie
```
`[[redirects]]` keywords: `from`, `to`, `status` (default `301`), `force` (default `false`; `!`/shadow), `query` (`query = {path = ":path"}`), `conditions` (`{Language, Country, Role, Cookie}`), `headers` (proxy request headers), `signed` (env var name for signed proxies).
**Gotchas:**
- You **cannot** add/remove a trailing slash with a redirect — CDN normalizes URLs first; a `/x/ → /x 301!` rule loops infinitely. Rely on Pretty URLs (default on).
- Splat asterisks work only at the **end** of a segment (`/jobs/*`), not mid-path (`/jobs/*.html` invalid). Placeholders (`:x`) only at the start of a segment; canPreview of the first 4,000 characters. Read full instructions →
---
name: netlify-database
description: Zero-config Postgres for Netlify apps via @netlify/database — querying data from Functions/Edge Functions, writing schema migrations, setting up Drizzle ORM, local dev with netlify dev, database branches for deploy previews, and migrating an existing Postgres project onto Netlify. Use when adding a database, building a contact form or CRUD API, writing SQL migrations, wiring up Drizzle, running netlify database commands, testing with a local Postgres, or switching from Neon/Supabase/RDS to Netlify Database.
---
# Netlify Database
Zero-config managed Postgres. Install `@netlify/database`, write migrations under `netlify/database/migrations/`, deploy — Netlify provisions the DB and applies migrations automatically. Queryable from Functions, Edge Functions, Builds, and Agent Runners.
## Modern client (reach for this)
```ts
import { getDatabase } from "@netlify/database";
const db = getDatabase(); // auto-selects connection for the runtime
const userId = 42;
const users = await db.sql`SELECT * FROM users WHERE id = ${userId}`; // auto-parameterized
```
Own driver / ORM instead:
```ts
import { getConnectionString } from "@netlify/database";
const connectionString = getConnectionString(); // correct branch for this env
```
**Legacy — do NOT use for new code:** `import { neon } from "@netlify/neon"`. Superseded by `@netlify/database`. Replace `neon()` calls with the Drizzle `netlify-db` adapter or a Postgres driver via `getConnectionString()`. The legacy env var `NETLIFY_DATABASE_URL` is replaced by `NETLIFY_DB_URL`.
## Where things go
| What | Location |
|------|----------|
| Migrations | `netlify/database/migrations/` (SQL files or subdirs with `migration.sql`) |
| Query code | Functions (`netlify/functions/`), Edge Functions |
| Drizzle schema | `db/schema.ts` (convention) |
| Drizzle client | `db/index.ts` (convention) |
| Connection string | `NETLIFY_DB_URL` env var, or `getConnectionString()` |
## Querying
`getDatabase(options?)` returns a client with `sql` and `pool`. `options.connectionString` overrides the auto-provisioned one; `options.debug` enables logging.
```ts
const db = getDatabase();
const active = await db.sql`SELECT * FROM users WHERE active = ${true}`;
await db.sql`INSERT INTO users (name, email) VALUES (${"Ada"}, ${"ada@example.com"})`;
await db.sql`UPDATE users SET name = ${"Ada Lovelace"} WHERE id = ${1}`;
await db.sql`DELETE FROM users WHERE id = ${1}`;
// Type the rows
interface User { id: number; name: string; email: string; }
const typed = await db.sql<User>`SELECT * FROM users`;
// Stream
for await (const row of db.sql`SELECT * FROM users`.stream()) { /* ... */ }
for await (const chunk of db.sql`SELECT * FROM users`.chunked(100)) { /* ... */ }
```
`SQLTemplate` methods: `execute()` → `Promise<T[]>`, `stream()` → `AsyncGenerator<T>`, `chunked(n)` → `AsyncGenerator<T[]>`, `toSQL()` → raw SQL + params without executing.
`sql` helpers:
- `sql.identifier(value)` — safe table/column name. String, string[], or `{ schema, table, column, as }`.
- `sql.values(rows)` — bulk-insert values list from a 2D array.
- `sql.default` — the SQL `DEFAULT` keyword.
- `sql.raw(value)` — **injects unparameterized SQL; bypasses injection protection. Only for trusted constants (e.g. `"DESC"`), never user input.**
- `sql.unsafe(query, params?, { rowMode })` — raw query string with `$1` params; `rowMode` is `"array"` or `"object"`.
### Transactions — use `pool`
`db.pool` is a [`pg.Pool`](https://node-postgres.com/apis/pool). `BEGIN`/queries/`COMMIT` must run on the same connection:
```ts
const client = await db.pool.connect();
try {
await client.query("BEGIN");
await client.query("INSERT INTO users (name, email) VALUES ($1, $2)", ["Ada", "ada@example.com"]);
await client.query("INSERT INTO posts (author_id, title) VALUES ($1, $2)", [1, "First post"]);
await client.query("COMMIT");
} catch (e) {
await client.query("ROLLBACK");
throw e;
} finally {
cPreview of the first 4,000 characters. Read full instructions →
Referenced files: 6 — file references are metadata, not package contents.
---
name: netlify-deploy
description: Create, configure, and manage Netlify deploys from code — reach for this when setting up Git continuous deployment, running netlify deploy or netlify deploy --prod from the CLI, writing netlify.toml deploy contexts, adding a Deploy to Netlify button, wiring build hooks, configuring Deploy Previews or branch deploys, locking or skipping deploys, fixing a failed or secrets-scanning deploy, or when someone asks to "deploy my site", "set up preview deploys", "add per-branch build config", or "add a deploy button to my README".
---
# Netlify deploy
## Modern CLI
```bash
netlify deploy # manual draft deploy (no CI)
netlify deploy --prod # deploy straight to production
netlify create # new project from a natural-language prompt
netlify deploy --allow-anonymous # temp project, claim within 1 hour
npm update -g netlify-cli # skew protection needs 23.11.0+
```
A deploy is a versioned, **atomic** snapshot: Netlify uploads only changed files and switches the live site only after all files land — the site is never in an inconsistent state. A deploy can be a preview or a production version served at your primary domain.
**Continuous deployment vs manual deploys:** Deploy with Git and the Netlify CLI support continuous deployment — a push auto-triggers a build. Drag and drop and the API create one-off manual deploys. Manual deploys (`netlify deploy`) do **not** run a build command; drag-and-drop while logged in is the only exception (framework auto-detected).
**⚠ When linking or creating a site, add `.netlify` to `.gitignore`.** Every linking path writes `.netlify/state.json`, which must not be committed.
## Ways to create a deploy
- **Git CD** — connect a repo; Netlify builds and deploys on every push (OAuth2 or the Netlify GitHub App). This is the default path.
- **CLI** — `netlify create`, `netlify deploy`, `netlify deploy --prod`.
- **Drag and drop** — https://app.netlify.com/drop. Logged in: builds if needed. Not logged in: publishes files as-is.
- **API** — create deploys via file digest or ZIP (one-off manual).
- **Deploy to Netlify button** — one-click from a public template repo.
- **Build hooks** — unique URLs that trigger builds. (Deploys from build hooks are treated as trusted and bypass the deploy request policy.)
- **AI agents** — Agent Runners (Claude Code, OpenAI Codex, Google Gemini) from the dashboard; every file-changing run auto-generates a Deploy Preview at `agent-<runID>--<site>.netlify.app`. The inline preview shown next to the prompt is the same Deploy Preview available at that URL.
- **Zapier / n8n** — automation integrations.
Not sure which path? The Deploy Navigator gives personalized recommendations: https://docs.netlify.com/start/choose-your-path#deploy-navigator (also embedded on the create-deploys page as "Not sure where to start?").
## netlify.toml deploy contexts
At the repo root. File config overrides UI settings. Five predefined contexts: `production`, `deploy-preview`, `branch-deploy`, `preview-server`, `dev`. Branch names also work as custom contexts; more specific contexts override general ones.
```toml
[context.production]
command = "make production"
[context.production.environment]
ACCESS_TOKEN = "super secret"
[[context.production.plugins]] # plugins REQUIRE double brackets
package = "@netlify/plugin-sitemap"
[context.deploy-preview.environment]
ACCESS_TOKEN = "not so secret"
[context.branch-deploy]
command = "make staging"
[context.dev.environment]
NODE_ENV = "development"
[context."features/branch"] # quote slashed branch names
command = "gulp"
```
**⚠ Environment variables set in `netlify.toml` are NOT available to the deploy environment** — set them via UI/CLI/API. `netlify.toml` is committed, so keep sensitive values out of it; use per-context env vars via UI/CLI/API instead.
See `references/netlify-toml.md` for the full context precedence rules and `references/deplPreview of the first 4,000 characters. Read full instructions →
Referenced files: 3 — file references are metadata, not package contents.
---
name: netlify-edge-functions
description: Write and configure Netlify Edge Functions — TypeScript/JavaScript handlers running in a Deno runtime at the network edge. Use when adding auth middleware or auth redirects, geolocation or localization logic, A/B testing or personalization, request/response transforms (rewrites/redirects), or edge SSR to a Netlify site. Triggers on tasks like "add an edge function", "auth check at the edge", "redirect visitors by country", "A/B test with cookies", "rewrite requests", "personalize by geo", or "cache an edge response". Covers the config export, path routing, the Context object, response caching, environment variables, and edge-vs-serverless choices. Check the framework's adapter first — only hand-write an edge function when the framework doesn't already generate one.
---
# Netlify Edge Functions
## Modern syntax (reach for this)
Export a default handler plus a `config` object. Import `Config`/`Context` types from `@netlify/edge-functions`; `Request`/`Response`/`URL` are global.
```ts
import type { Config, Context } from "@netlify/edge-functions";
export default async (request: Request, context: Context) => {
return new Response("Hello world");
};
export const config: Config = {
path: "/test",
};
```
**Do not hand-write an edge function when your framework's adapter already generates middleware for the job** — duplicating it causes conflicts. Check the framework adapter/reference first.
**Edge vs serverless:** use edge functions for low-latency request/response manipulation, geolocation logic, auth checks/redirects, and A/B personalization. Use serverless functions for long-running work (up to 15 min), heavy Node.js dependencies, database-heavy operations, background/scheduled tasks, or memory above 512 MB.
## File location
- Default directory: `YOUR_BASE_DIRECTORY/netlify/edge-functions`. Custom: `edge_functions` under `[build]` in `netlify.toml` (path relative to base directory).
- Keep the directory **outside your publish directory** so source files aren't deployed.
- Extensions: `.js`, `.ts`, `.jsx`, `.tsx` (`.jsx`/`.tsx` useful for SSR).
- Same-name conflict: if `my-function.ts` and `my-function.js` both exist, the **TypeScript file is ignored** and the JavaScript one is deployed.
## Routing — required, or the function silently never runs
⚠️ **An edge function without a route (no `config` export and no `netlify.toml` declaration) still deploys but never runs — no build error, no warning.** When "my edge function does nothing", check the route first.
⚠️ **Scope `path` narrowly.** `path: "/*"` intercepts every request including static assets, adding latency and billing an edge invocation for each one.
Edge functions are **not** auto-assigned a URL route. Configure via inline `config` or `netlify.toml`.
`path` is a `URLPattern` expression, must start with `/`, single string or array:
```ts
export const config: Config = {
path: ["/", "/products/*"],
excludedPath: ["/*.css", "/*.js"],
};
```
Config properties: `path`, `excludedPath`, `pattern` (regex alternative to `path`), `excludedPattern`, `method`, `header`, `onError`, `cache`.
### netlify.toml declaration
Use `[[edge_functions]]` to declare multiple functions on one path and control order:
```toml
[[edge_functions]]
path = "/admin"
function = "auth"
[[edge_functions]]
path = "/admin"
function = "injector"
cache = "manual"
[[edge_functions]]
pattern = "/products/(.*)"
excludedPattern = "/products/things/(.*)"
function = "highlight"
```
Properties: `function`, `path`, `excludedPath`, `pattern`, `excludedPattern`, `header`, `cache`.
**Merge precedence:** if the same function is declared both inline and in `netlify.toml`, configs merge and are treated as inline; inline wins duplicate fields.
### Match by headers
`header` keys are HTTP header names (case-insensitive); values are `true` (present), `false` (absent), or a string regex on the value. Multiple same-name values match against Preview of the first 4,000 characters. Read full instructions →
---
name: netlify-forms
description: Serverless form handling on Netlify-hosted sites — detects HTML forms at deploy time, stores submissions, filters spam, and sends notifications. Use when adding a contact form, lead-capture form, file-upload form, or newsletter signup to a Netlify site; wiring AJAX form submission; setting up a custom thank-you page; adding a honeypot or reCAPTCHA to a form; getting forms working in Next.js, Nuxt, SvelteKit, Astro, or Gatsby; reading form submissions via the Netlify API; or debugging missing submissions and forms that silently fail to register.
---
# Netlify Forms
Mark a form for detection with `data-netlify="true"` (or the bare `netlify` attribute — equivalent) on the `<form>` tag. Forms are detected by **parsing the final built HTML at deploy time** — there is no runtime API call or backend code. Client-side/JS-rendered/SSR forms are NOT in the built HTML and are never detected on their own; they require a static skeleton file (see below).
Prerequisite: form detection must be enabled once in the Netlify UI (Forms > **Enable form detection**). Takes effect on the next deploy.
## Static HTML form
```html
<form name="contact" method="POST" data-netlify="true">
<p><label>Your Name: <input type="text" name="name" /></label></p>
<p><label>Your Email: <input type="email" name="email" /></label></p>
<p><label>Message: <textarea name="message"></textarea></label></p>
<p><button type="submit">Send</button></p>
</form>
```
- `name` sets the form name in the UI and **must be unique per site**.
- At deploy, Netlify strips the `data-netlify`/`netlify` attribute and injects `<input type="hidden" name="form-name" value="contact" />`.
- Add an `<input name="email">` so the notification email's `Reply-to` is set to the submitter.
## JS-rendered / SSR / framework forms (Next.js, Nuxt, SvelteKit, Astro, Gatsby)
Two required pieces:
**1. Static skeleton file `public/__forms.html`** — a hidden copy of each form with `data-netlify="true"`, a hidden `form-name` input, and every field the component submits, with names matching **exactly** (Netlify validates field names against the registered form). Without this file, submissions silently fail.
```html
<!-- public/__forms.html -->
<form name="pizzaOrder" data-netlify="true" hidden>
<input type="hidden" name="form-name" value="pizzaOrder" />
<input name="order" type="text" />
</form>
```
**2. The rendered form** carries a matching hidden `form-name` input:
```jsx
<form name="pizzaOrder" method="post" data-netlify="true" onSubmit={handleSubmit}>
<input type="hidden" name="form-name" value="pizzaOrder" />
<input name="order" type="text" onChange={handleChange} />
<input type="submit" />
</form>
```
**⚠️ SSR POST target:** In SSR apps, `fetch("/")` is intercepted by the SSR catch-all function and never reaches form processing. POST to the static skeleton file itself — `/__forms.html` — not `/` or an arbitrary path.
**⚠️ Astro on-demand routes:** Routes with `export const prerender = false` or `output: "server"` are never scanned at build time, so their forms are never registered. Put the form on a prerendered page, or rely on the static skeleton file.
**Next.js Runtime v5 (Next.js 13.5+):** extract form definitions to the static skeleton file and submit via AJAX rather than full-page navigation. See https://docs.netlify.com/build/frameworks/framework-setup-guides/nextjs/overview#v5-breaking-changes
## AJAX submission
```js
const handleSubmit = event => {
event.preventDefault();
const formData = new FormData(event.target);
fetch("/__forms.html", { // static sites may POST to "/"; SSR must target the skeleton file
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(formData).toString()
})
.then(() => alert("Thank you for your submission")) // or navigate("/thank-you")
.catch(error => alert(error));
};
document.querySelector("form").addEventListener("Preview of the first 4,000 characters. Read full instructions →
--- name: netlify-frameworks description: Deploy and configure web frameworks on Netlify — build settings and SSR/edge adapters plus local platform emulation and env vars. Use when setting up or fixing a framework deploy (Next.js / Astro / Nuxt / SvelteKit / Remix / React Router / TanStack Start / SolidStart / Gatsby / Angular / Vite / Express / Hydrogen / Hugo / Eleventy / Vue / React), adding SSR or edge functions or middleware wired to Netlify context, fixing SPA redirect and catch-all rules, setting a build command or publish directory, or debugging "why isn't my env var updating" and framework build failures. --- Route framework-specific deep work to the guides in this skill: `references/astro.md`, `references/nextjs.md`, `references/nuxt.md`, `references/sveltekit.md`, `references/tanstack.md`, `references/vite.md`. ## Env vars: modern rules (read first) Env values are injected **at build time**. Any change (client- or server-side) requires a **redeploy** — editing a var in the UI/CLI does NOT reach the live site or already-deployed functions until a new build runs. **Never use a client prefix for secrets.** Client-prefixed vars are inlined into the browser bundle: `VITE_`, `NEXT_PUBLIC_`, `PUBLIC_`, `NUXT_PUBLIC_`, `REACT_APP_`, `GATSBY_`, `VUE_APP_`. Client-embed prefixes by framework: CRA `REACT_APP_`, Gatsby `GATSBY_`, Next `NEXT_PUBLIC_`, Nuxt `NUXT_ENV_`, Vue CLI `VUE_APP_`. **Scopes:** build-time access needs **Builds** scope; SSR/DSG runtime access needs **both Functions and Builds**. `netlify.toml` is read only during build — functions cannot read it at runtime; set runtime vars in UI/CLI/API. Netlify build variables can't be used as values in the UI or `netlify.toml` env sections. Set them inline before the build command: ```toml [build] command = "REACT_APP_CONTEXT=$CONTEXT npm run build" ``` ## SPA redirects and the SSR catch-all footgun SPAs (React, Vue CLI, Vite, Nuxt in SPA mode) need a rewrite to serve `index.html` for `pushState`: ``` /* /index.html 200 ``` **Remove any SPA catch-all when adopting an SSR adapter.** A leftover `/* → /index.html 200` silently serves static `index.html` for SSR pages and API routes — user redirects beat adapter-generated routes. ## Local dev with platform emulation (no Netlify CLI) Vite-based frameworks emulate Netlify primitives (functions, edge functions, blobs, Netlify Database, Cache API, Image CDN, redirects/rewrites, headers, env vars, AI Gateway) in the dev server: | Framework | Plugin/module | Run | |-----------|---------------|-----| | Astro (5.12+) | built-in (Netlify Vite plugin auto-loaded) | `astro dev` | | Nuxt | `@netlify/nuxt` | `nuxt dev` | | React Router | `@netlify/vite-plugin` | `react-router dev` | | SolidStart 2 | `@netlify/vite-plugin` | `vite dev` | | TanStack Start | `@netlify/vite-plugin-tanstack-start` | (vite) | | Vite | `@netlify/vite-plugin` | `npx vite` | Still need `netlify dev` (Netlify CLI) for: Gatsby generated functions (run `netlify build` first), Angular SSR local test (`netlify serve`), and frameworks without a Vite plugin. **`netlify dev` gotcha:** with both a custom `command` and a `targetPort` in `[dev]`, you must set `framework = "#custom"` — otherwise the detector runs and your custom command is silently ignored. ## Build settings by framework | Framework | Build command | Publish | |-----------|---------------|---------| | Angular (standard) | `ng build --prod` | `dist/YOUR_PROJECT_NAME` | | Astro | `astro build` | `dist` | | Create React App | `react-scripts build` | `build` | | Eleventy | `eleventy` | `_site` | | Gatsby | `gatsby build` | `public` | | Hugo | `hugo` | `public` | | Hydrogen | `remix vite:build` | `dist/client` | | Next.js (SSR/hybrid) | `next build` | `.next` | | Next.js (static export) | `next build && next export` | `out` (`NETLIFY_NEXT_PLUGIN_SKIP=true`) | | Nuxt 3 | `nuxt build` | `dist` | | Nuxt 2 | `nuxt generate` | `dist` | | React Router | `react-router build` | `build/client` | | Remix
Preview of the first 4,000 characters. Read full instructions →
Referenced files: 6 — file references are metadata, not package contents.
---
name: netlify-functions
description: 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.
---
# 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 `index` or matches the subdirectory name. All of these create a function `hello`:
- `netlify/functions/hello.mts`
- `netlify/functions/hello/hello.mts`
- `netlify/functions/hello/index.mts`
- Use `.mts` (TS) / `.mjs` (JS) for ES modules. `.cts`/`.cjs` force CommonJS; `.ts`/`.js` follow the nearest `package.json` `"type"`.
## Minimal function
No `config` export. Serves at `/.netlify/functions/hello`.
```ts title="netlify/functions/hello.mts"
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()`:
```ts
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>`.
```ts title="netlify/functions/travel.mts"
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: `path` supports [`URLPattern`](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API) syntax — `path: ["/sale/*", "/item/:sku"]`. Named groups land on `context.params`. For the query string use `req.url`.
- `excludedPath`: carve exceptions, e.g. `excludedPath: ["/product/*.css"]` with `path: "/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.
```ts
import type { NetlifyFunction } from "@netlify/functions"
export default {
fetch: (req, context) => new Response("Hello, world!"),
config: { path: "/hello" },
} satisfies NetlifyFunction
```
## Context object
Second handler argument (or `getContext()` from `@netlify/functions` when out of handler scope — throws outside a request; wrap inPreview of the first 4,000 characters. Read full instructions →
---
name: netlify-identity
description: Add user authentication to a Netlify site with @netlify/identity — signup/login/logout, Google/GitHub/GitLab/Bitbucket OAuth, server-side getUser() checks, role-based access control, and Identity event functions. Use it when a task involves adding a login or signup form, gating content to members or roles, "auth middleware" or verifying users in Netlify Functions or Edge Functions, handling OAuth or email-confirmation callbacks, assigning roles at signup, or customizing Identity emails. For locking a whole site to your company or employees-only access, use netlify-access-control instead.
---
# Netlify Identity
Use `@netlify/identity` (npm). For new projects it replaces the legacy `netlify-identity-widget` and `gotrue-js` — do not reach for those.
```bash
npm install @netlify/identity
```
Framework examples (Next.js/Astro/Remix/SvelteKit) and the full API reference are in the [`@netlify/identity` README on npm](https://www.npmjs.com/package/@netlify/identity).
> **Identity does not run under `netlify dev`.** Test all auth flows on a deploy — Deploy Previews work. Local dev will not complete signup/login/OAuth.
> **Identity config is dashboard-only — there is no public API.** Never curl `api.netlify.com` to flip or read Identity settings, never read tokens from local Netlify config, never probe undocumented endpoints. Enable and configure Identity at `https://app.netlify.com/projects/{site_name}/identity`.
> **Never build a from-scratch OAuth flow alongside Identity.** No provider app registration in code, no `client_id`/`secret` in source, no custom callback token exchange. Use `oauthLogin()` + `handleAuthCallback()`. Raw OAuth beside Identity is the most common source of rework.
## Client auth (browser)
```ts
import { signup, login, logout, getUser, oauthLogin, handleAuthCallback } from '@netlify/identity'
// Register — confirmation email sent by default (unless autoconfirm is on)
const user = await signup('jane@example.com', 'securepassword', { full_name: 'Jane Doe' })
// Log in / out
await login('jane@example.com', 'securepassword')
await logout()
// Current user or null
const current = await getUser()
if (current) console.log(`Logged in as ${current.email}`)
// External provider — redirects the browser; provider is one of
// 'google' | 'github' | 'gitlab' | 'bitbucket'
oauthLogin('github')
```
> **`handleAuthCallback()` is mandatory on your landing page.** Without it, OAuth redirects, email-confirmation links, password-recovery links, and invite links never complete. Call it on page load:
```ts
import { handleAuthCallback } from '@netlify/identity'
const result = await handleAuthCallback() // falsy if no token in URL hash
if (result) console.log(result.type, result.user.email) // confirmation | invite | recovery | email change
```
Alternatives for a single token type: `recoverPassword()` (recovery), `acceptInvite()` (invite). Refresh a session with `refreshSession()`.
Don't hard-code which providers exist. Call `getSettings()` at startup and render the signup form and OAuth buttons from what it returns.
## Server-side auth (Netlify Functions & Edge Functions)
Server-side `getUser()`/`login()`/`admin.*` require modern **v2 functions** (`export default`). The v1 `export { handler }` form is not supported.
`getUser()` works in both runtimes. **`admin.*` runs ONLY in Netlify Functions — not the browser, not Edge Functions.**
```ts
// netlify/functions/me.ts — verify user
import { getUser } from '@netlify/identity'
import type { Context } from '@netlify/functions'
export default async (req: Request, context: Context) => {
const user = await getUser()
if (!user) return new Response('Unauthorized', { status: 401 })
return Response.json({ id: user.id, email: user.email })
}
```
Edge Function form is identical but imports `Context` from `@netlify/edge-functions`.
### Role checks
```ts
// netlify/functions/admin-users.ts
import { getUser, admin } from '@netlify/iPreview of the first 4,000 characters. Read full instructions →
Referenced files: 2 — file references are metadata, not package contents.
---
name: netlify-image-cdn
description: Transforms images on demand via Netlify Image CDN's /.netlify/images endpoint with query parameters for resizing/cropping/format/quality. Use when adding image optimization or responsive images, converting formats (WebP/AVIF/PNG), generating thumbnails or blur placeholders, serving remote/third-party images through the CDN, allowlisting remote domains in netlify.toml, setting up image redirects or cache headers, building user-uploaded image pipelines, or debugging a 404 on /.netlify/images. Also covers framework image handling for Angular/Astro/Gatsby/Next.js/Nuxt.
---
# Netlify Image CDN
Transform images by requesting the endpoint with a `url` query parameter. This is the current and only documented surface — there is no legacy form.
```
GET /.netlify/images?url=<source>[&w=][&h=][&fit=][&position=][&fm=][&q=]
```
```bash
# resize a deployed image to 50px wide
curl -vs 'https://mysitename.netlify.app/.netlify/images?url=/owl.jpeg&w=50'
```
`url` is required; all other parameters are optional.
## Query parameters
| Parameter | Purpose | Values | Default |
|-----------|---------|--------|---------|
| `url` | Source asset (required) | Relative path or remote URL | — |
| `w` | Width in pixels | Integer | — |
| `h` | Height in pixels | Integer | — |
| `fit` | Resize behavior | `contain`, `cover`, `fill` | `contain` |
| `position` | Crop anchor when `fit=cover` | `top`, `bottom`, `left`, `right`, `center` | `center` |
| `fm` | Output format | `avif`, `jpg`, `png`, `webp`, `gif`, `blurhash` | content-negotiated |
| `q` | Quality for lossy output | Integer `1`–`100` | `75` |
## Common transformations
```bash
# resize + crop to a 50px square, retaining the left side
curl -vs 'https://mysitename.netlify.app/.netlify/images?url=/owl.jpeg&fit=cover&w=50&h=50&position=left'
# convert JPEG to PNG (response carries content-type: image/png)
curl -vs 'https://mysitename.netlify.app/.netlify/images?url=/owl.jpeg&fm=png'
# convert JPEG to AVIF at medium quality
curl -vs 'https://mysitename.netlify.app/.netlify/images?url=/owl.jpeg&fm=avif&q=50'
```
### fit behavior
- **`contain` (default):** maintains aspect ratio; one dimension may come back smaller than requested. Supply one dimension and the other is computed.
- **`cover`:** fills exactly, cropping excess. **Requires BOTH `w` and `h`** — omitting either is invalid. Use `position` to choose what's retained.
- **`fill`:** fills exactly, stretching/squishing if aspect ratios differ.
### Format notes
- `q` applies only when output is `avif`, `jpg`, `gif`, or `webp`.
- `webp` and `gif` can be static or animated.
- If `fm` is omitted, format is content-negotiated from the `Accept` header: `webp` if accepted, else `avif` if accepted, else the original format. A source-only request (no other params) still converts to `webp`/`avif` but keeps size and shape.
## Remote source images
Remote sources must be allowlisted in `netlify.toml` before transformation, or the request fails.
```toml
[images]
remote_images = ['https://my-images\.com/.*', 'https://animals.more-images.com/[bcr]at/.*']
```
Percent-encode remote source URLs before placing them in the `url` parameter with `encodeURIComponent` — a URL containing `?` or `&` breaks otherwise.
```js
const src = `/.netlify/images?url=${encodeURIComponent('https://my-images.com/owl.jpeg?v=2')}&w=400`;
```
Constraints:
- Remote sources must be **publicly accessible**.
- Credential-bearing headers (`Authorization`, `Cookie`) are **NOT forwarded** when fetching a remote source. For authenticated sources, use URLs that carry their own authorization (e.g. S3 presigned URLs) and make sure your `remote_images` patterns match those full URLs.
### remote_images regex escaping
The only meaningful escape is the literal dot (`\.`). Forward slashes are NOT metacharacters — never write `https:\/\/`. In `netlify.toml`, use single-quoted literal strings (`'https://example\.com/.*'`) or double the backslash in dPreview of the first 4,000 characters. Read full instructions →
Referenced files: 1 — file references are metadata, not package contents.
--- name: netlify-mcp-servers description: Build, deploy, and secure Model Context Protocol (MCP) servers on Netlify. Use whenever the task involves creating an MCP server, exposing an app or API to AI agents as MCP tools, letting Claude / Cursor / Claude Code call a custom remote server, or adding MCP tools to an existing Netlify site. Covers the MCP SDK + Streamable HTTP transport on a Netlify Function, authentication (single shared secret vs per-user API keys with Netlify Identity), read/write safety, file uploads, and connecting clients. Use even when the user just says "MCP", "tool server for an agent", or "let an AI use my API". --- # Netlify MCP Servers An MCP server exposes **tools** (and optionally resources/prompts) that an AI client — Claude Desktop, Claude Code, Cursor — can call. On Netlify, a remote MCP server is just **one Netlify Function** that speaks the MCP protocol over HTTP. This skill gets you a working, secure server and connects a client to it. **"Netlify MCP" means two different things — make sure you're building the right one.** Netlify publishes its *own* hosted MCP server that lets an AI client operate the **Netlify platform** on your behalf — create projects, trigger deploys, manage env vars and infrastructure through your Netlify account. You don't write that one; you point your client at Netlify's hosted MCP server per Netlify's MCP-server docs (and see the **netlify-agent-runner** skill for running agents against your site). This skill is the *other* thing: building **your own** MCP server — an endpoint that exposes *your* app's tools and data to an agent — hosted on a Netlify Function. If the ask is "let my agent manage my Netlify sites/deploys/env vars," that's the hosted Netlify MCP server, not a function you write. The same setup works two ways: - **Standalone server** — a repo whose only job is the MCP endpoint (e.g. wrapping a third-party API). - **Added to an existing app** — one more function alongside your site. Have its tools call the **same service/data layer your UI and REST routes already use**, so logic isn't duplicated. ## Before you build Decide one thing up front, because it shapes the auth code: - **Who calls this server?** Just you (a personal/single-user server) → use a **single shared secret**. Multiple people, each acting as themselves → use **per-user API keys** backed by Netlify Identity. See [authentication](references/authentication.md). If you're not sure, start with the single shared secret — it's a few lines and you can layer per-user keys on later. I'll default to that unless you say otherwise. ## Stack Use the official MCP SDK with its Web-standard Streamable HTTP transport, running statelessly inside a Netlify Function. ```bash npm install @modelcontextprotocol/sdk zod ``` A Netlify Function already speaks the web platform — it receives a `Request` and returns a `Response`. The SDK ships a transport built on exactly those primitives, `WebStandardStreamableHTTPServerTransport` (the same core the SDK runs on internally, and what Cloudflare Workers / Deno / Bun use): you hand it the `Request` and return the `Response` it produces — no adapter, no version pin. Older guides reach for the Node-flavored `StreamableHTTPServerTransport` plus a `fetch-to-node` bridge to synthesize the Node `req`/`res` objects it expects; on Netlify you need neither, and skipping them is both simpler and what's verified to work here. One gotcha, independent of all this: the transport returns **HTTP 406** to any POST whose `Accept` header lacks *both* `application/json` and `text/event-stream`. That's an MCP-spec requirement the *client* must satisfy — a 406 means fix the client's `Accept` header, not the server. Letting the SDK own the protocol also means you don't hand-maintain JSON-RPC framing or the protocol-version handshake. ## The server function With the Web-standard transport this is a few lines — most of what older guides show was the Node bridge, which you don't nee
Preview of the first 4,000 characters. Read full instructions →
Referenced files: 3 — file references are metadata, not package contents.
Publisher declarations from the archived package. These are separate from our research and the live service's terms.
Package observed Nov 7, 2026.
plugin_asdk_app_691f1f8f72408191afdbbdf8242bdf86
Download plugin data (JSON)Plugin details: served from cache. Prepared . Refreshed when plugin data changes or after 6 hours. Your account and Follow status are live.