Skill instructions
netlify-ai-gateway3.4 KB
View saved version →
---
name: netlify-ai-gateway
description: 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 model. Covers supported providers (OpenAI, Anthropic, Google), SDK setup, environment variables, and the list of available models.
---
# Netlify AI Gateway
> **IMPORTANT:** Only use models listed in the "Available Models" section below. AI Gateway does not support every model a provider offers. Using an unsupported model will cause runtime errors.
Netlify AI Gateway provides access to AI models from multiple providers without managing API keys directly. It is available on all Netlify sites.
## How It Works
The AI Gateway acts as a proxy — you use standard provider SDKs (OpenAI, Anthropic, Google) but point them at Netlify's gateway URL instead of the provider's API. Netlify handles authentication, rate limiting, and monitoring.
## Setup
1. Enable AI on your site in the Netlify UI
2. The environment variable `OPENAI_BASE_URL` is set automatically by Netlify
3. Install the provider SDK you want to use
No provider API keys are needed — Netlify's gateway handles authentication.
## Using OpenAI SDK
```bash
npm install openai
```
```typescript
import OpenAI from "openai";
const openai = new OpenAI();
// OPENAI_BASE_URL is auto-configured — no API key or base URL needed
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Hello!" }],
});
```
## Using Anthropic SDK
```bash
npm install @anthropic-ai/sdk
```
```typescript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: Netlify.env.get("ANTHROPIC_BASE_URL"),
});
const message = await client.messages.create({
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }],
});
```
## Using Google AI SDK
```bash
npm install @google/generative-ai
```
```typescript
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI("placeholder");
// Configure base URL via environment variable
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
const result = await model.generateContent("Hello!");
```
## In a Netlify Function
```typescript
import type { Config, Context } from "@netlify/functions";
import OpenAI from "openai";
export default async (req: Request, context: Context) => {
const { prompt } = await req.json();
const openai = new OpenAI();
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: prompt }],
});
return Response.json({
response: completion.choices[0].message.content,
});
};
export const config: Config = {
path: "/api/ai",
method: "POST",
};
```
## Environment Variables
| Variable | Provider | Set by |
|---|---|---|
| `OPENAI_BASE_URL` | OpenAI | Netlify (automatic) |
| `ANTHROPIC_BASE_URL` | Anthropic | Netlify (automatic) |
These are configured automatically when AI is enabled on the site. No manual setup required.
## Local Development
With `@netlify/vite-plugin` or `netlify dev`, gateway environment variables are injected automatically. The AI Gateway is accessible during local development after the site has been deployed at least once.
## Available Models
For the list of supported models, see https://docs.netlify.com/build/ai-gateway/overview/.
Referenced files: 4
netlify-blobs2.69 KB
View saved version →
---
name: netlify-blobs
description: Guide for using Netlify Blobs object storage. Use when storing files, images, documents, or simple key-value data without a full database. Covers getStore(), CRUD operations, metadata, listing, deploy-scoped vs site-scoped stores, and local development.
---
# Netlify Blobs
Netlify Blobs is zero-config object storage available from any Netlify compute (functions, edge functions, framework server routes). No provisioning required.
```bash
npm install @netlify/blobs
```
## Getting a Store
```typescript
import { getStore } from "@netlify/blobs";
const store = getStore({ name: "my-store" });
// Use "strong" consistency when you need immediate reads after writes
const store = getStore({ name: "my-store", consistency: "strong" });
```
## CRUD Operations
These are the **only** store methods. Do not invent others.
### Create / Update
```typescript
// String or binary data
await store.set("key", "value");
await store.set("key", fileBuffer);
// With metadata
await store.set("key", data, {
metadata: { contentType: "image/png", uploadedAt: new Date().toISOString() },
});
// JSON data
await store.setJSON("key", { name: "Example", count: 42 });
```
### Read
```typescript
// Text (default)
const text = await store.get("key"); // string | null
// Typed retrieval
const json = await store.get("key", { type: "json" }); // object | null
const stream = await store.get("key", { type: "stream" });
const blob = await store.get("key", { type: "blob" });
const buffer = await store.get("key", { type: "arrayBuffer" });
// With metadata
const result = await store.getWithMetadata("key");
// { data: any, etag: string, metadata: object } | null
// Metadata only (no data download)
const meta = await store.getMetadata("key");
// { etag: string, metadata: object } | null
```
### Delete
```typescript
await store.delete("key");
```
### List
```typescript
const { blobs } = await store.list();
// blobs: [{ etag: string, key: string }, ...]
// Filter by prefix
const { blobs } = await store.list({ prefix: "uploads/" });
```
## Store Types
- **Site-scoped** (`getStore()`): Persist across all deploys. Use for most cases.
- **Deploy-scoped** (`getDeployStore()`): Tied to a specific deploy lifecycle.
## Limits
| Limit | Value |
|---|---|
| Max object size | 5 GB |
| Store name max length | 64 bytes |
| Key max length | 600 bytes |
## Local Development
Local dev uses a sandboxed store (separate from production). For Vite-based projects, install `@netlify/vite-plugin` to enable local Blobs access. Otherwise, use `netlify dev`.
**Common error**: "The environment has not been configured to use Netlify Blobs" — install `@netlify/vite-plugin` or run via `netlify dev`.
Referenced files: 4
netlify-caching3.8 KB
View saved version →
---
name: netlify-caching
description: Guide for controlling caching on Netlify's CDN. Use when configuring cache headers, setting up stale-while-revalidate, implementing on-demand cache purge, or understanding Netlify's CDN caching behavior. Covers Cache-Control, Netlify-CDN-Cache-Control, cache tags, durable cache, and framework-specific caching patterns.
---
# Caching on Netlify
## Default Behavior
**Static assets** are cached automatically:
- CDN: cached for 1 year, invalidated on every deploy
- Browser: always revalidates (`max-age=0, must-revalidate`)
- No configuration needed
**Dynamic responses** (functions, edge functions, proxied) are **not cached by default**. Add cache headers explicitly.
## Cache-Control Headers
Three headers control caching, from most to least specific:
| Header | Who sees it | Use case |
|---|---|---|
| `Netlify-CDN-Cache-Control` | Netlify CDN only (stripped before browser) | CDN-only caching |
| `CDN-Cache-Control` | All CDN caches (stripped before browser) | Multi-CDN setups |
| `Cache-Control` | Browser and all caches | General caching |
### Common Patterns
```typescript
// Cache at CDN for 1 hour, browser always revalidates
return new Response(body, {
headers: {
"Netlify-CDN-Cache-Control": "public, s-maxage=3600, must-revalidate",
"Cache-Control": "public, max-age=0, must-revalidate",
},
});
// Stale-while-revalidate (serve stale for 2 min while refreshing)
return new Response(body, {
headers: {
"Netlify-CDN-Cache-Control": "public, max-age=60, stale-while-revalidate=120",
},
});
// Durable cache (shared across edge nodes, serverless functions only)
return new Response(body, {
headers: {
"Netlify-CDN-Cache-Control": "public, durable, max-age=60, stale-while-revalidate=120",
},
});
```
### Immutable Assets
For fingerprinted files (hash in filename):
```toml
# netlify.toml
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
```
## Cache Tags and On-Demand Purge
Tag responses for selective cache invalidation:
```typescript
return new Response(body, {
headers: {
"Netlify-Cache-ID": "product,listing",
"Netlify-CDN-Cache-Control": "public, s-maxage=86400",
},
});
```
Purge by tag:
```typescript
import { purgeCache } from "@netlify/functions";
export default async () => {
await purgeCache({ tags: ["product"] });
return new Response("Purged", { status: 202 });
};
```
Purge entire site:
```typescript
await purgeCache();
```
Responses with `Netlify-Cache-ID` are **excluded from automatic deploy-based invalidation** — they must be purged explicitly.
## Cache Key Variation
Customize what creates separate cache entries:
```typescript
return new Response(body, {
headers: {
"Netlify-Vary": "cookie=ab_test|is_logged_in",
// Other options: query=param1|param2, header=X-Custom, country=us|de, language=en|fr
},
});
```
## Framework-Specific Caching
### Next.js
ISR uses Netlify's durable cache automatically (runtime 5.5.0+). `revalidatePath` and `revalidateTag` trigger cache purge.
### Astro / Remix
Full control over cache headers in server routes. Set `Netlify-CDN-Cache-Control` in responses for CDN caching.
### Nuxt
Default Nitro preset handles caching. ISR-style patterns use `routeRules` with `swr` or `isr` options.
### Vite SPA
Static assets are cached by default. API responses from Netlify Functions need explicit cache headers.
## Debugging
Check the `Cache-Status` response header:
- `HIT` — served from cache
- `MISS` — generated fresh
- `REVALIDATED` — stale content was revalidated
## Constraints
- Basic auth disables caching for the entire site
- Durable cache is serverless functions only (not edge functions)
- Same URL must return identical `Netlify-Vary` headers across responses
- Deploy invalidation is scoped to deploy context (production vs preview)
Referenced files: 4
netlify-cli-and-deploy4.18 KB
View saved version →
---
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) |
| `netlify deploy` | Draft deploy |
| `netlify deploy --prod` | Production deploy |
| `netlify dev:exec <cmd>` | Run command with Netlify environment loaded |
| `netlify env:list` | List environment variables |
| `netlify clone org/repo` | Clone, link, and set up in one step |
Referenced files: 4
netlify-config3.61 KB
View saved version →
---
name: netlify-config
description: Reference for netlify.toml configuration. Use when configuring build settings, redirects, rewrites, headers, deploy contexts, environment variables, or any site-level configuration. Covers the complete netlify.toml syntax including redirects with splats/conditions, headers, deploy contexts, functions config, and edge functions config.
---
# Netlify Configuration (netlify.toml)
Place `netlify.toml` at the repository root (or at the base directory for monorepos).
## Build Settings
```toml
[build]
base = "project/" # Base directory (default: root)
command = "npm run build" # Build command
publish = "dist/" # Output directory
```
## Redirects
```toml
# Basic redirect
[[redirects]]
from = "/old"
to = "/new"
status = 301 # 301 (default), 302, 200 (rewrite), 404
# SPA catch-all
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
# Splat (wildcard)
[[redirects]]
from = "/blog/*"
to = "/news/:splat"
# Path parameters
[[redirects]]
from = "/users/:id"
to = "/api/users/:id"
status = 200
# Force (override existing files)
[[redirects]]
from = "/app/*"
to = "/index.html"
status = 200
force = true
# Proxy to external service
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
[redirects.headers]
X-Custom = "value"
# Country/language conditions
[[redirects]]
from = "/*"
to = "/fr/:splat"
status = 200
conditions = { Country = ["FR"], Language = ["fr"] }
```
**Rule order matters** — Netlify processes the first matching rule. Place specific rules before general ones.
## Headers
```toml
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
```
Headers apply only to files served from Netlify's CDN (not to function or edge function responses — set those in code).
## Deploy Contexts
Override settings per deploy context:
```toml
[context.production]
command = "npm run build"
environment = { NODE_ENV = "production" }
[context.deploy-preview]
command = "npm run build:preview"
[context.branch-deploy]
command = "npm run build:staging"
[context.dev]
environment = { NODE_ENV = "development" }
# Specific branch
[context."staging"]
command = "npm run build:staging"
```
## Environment Variables
```toml
[build.environment]
NODE_VERSION = "20"
[context.production.environment]
API_URL = "https://api.prod.com"
[context.deploy-preview.environment]
API_URL = "https://api.staging.com"
```
**Do not put secrets in netlify.toml** (it's committed to source control). Use the Netlify UI or CLI for sensitive values. See the **netlify-cli-and-deploy** skill for CLI environment variable management.
## Functions Configuration
```toml
[functions]
directory = "netlify/functions" # Default
node_bundler = "esbuild"
# Scheduled function
[functions."cleanup"]
schedule = "@daily"
```
## Edge Functions Configuration
```toml
[[edge_functions]]
path = "/admin"
function = "auth"
# Import map for Deno URL imports
[functions]
deno_import_map = "./import_map.json"
```
## Dev Server
```toml
[dev]
command = "npm start" # Dev server command
port = 8888 # Netlify Dev port
targetPort = 3000 # Your app's dev server port
framework = "#auto" # "#auto", "#static", "#custom"
```
## Plugins
```toml
[[plugins]]
package = "@netlify/plugin-lighthouse"
[plugins.inputs]
audits = ["performance", "accessibility"]
```
## Image CDN
```toml
[images]
remote_images = ["https://example\\.com/.*"]
```
See the **netlify-image-cdn** skill for full Image CDN usage.
Referenced files: 4
netlify-deploy6.83 KB
View saved version →
---
name: netlify-deploy
description: 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 and production flows.
---
# Netlify Deployment Skill
Deploy web projects to Netlify using the Netlify CLI with intelligent detection of project configuration and deployment context.
## Overview
This skill automates Netlify deployments by:
- Verifying Netlify CLI authentication
- Detecting project configuration and framework
- Linking to existing sites or creating new ones
- Deploying to production or preview environments
## Prerequisites
- **Netlify CLI**: Installed via npx (no global install required)
- **Authentication**: Netlify account with active login session
- **Project**: Valid web project in current directory
- When sandboxing blocks the deployment network calls, rerun with `sandbox_permissions=require_escalated`.
- The deployment might take a few minutes. Use appropriate timeout values.
## Authentication Pattern
The skill uses the **pre-authenticated Netlify CLI** approach:
1. Check authentication status with `npx netlify status`
2. If not authenticated, guide user through `npx netlify login`
3. Fail gracefully if authentication cannot be established
Authentication uses either:
- **Browser-based OAuth** (primary): `netlify login` opens browser for authentication
- **API Key** (alternative): Set `NETLIFY_AUTH_TOKEN` environment variable
## Workflow
### 1. Verify Netlify CLI Authentication
Check if the user is logged into Netlify:
```bash
npx netlify status
```
**Expected output patterns**:
- ✅ Authenticated: Shows logged-in user email and site link status
- ❌ Not authenticated: "Not logged into any site" or authentication error
**If not authenticated**, guide the user:
```bash
npx netlify login
```
This opens a browser window for OAuth authentication. Wait for user to complete login, then verify with `netlify status` again.
**Alternative: API Key authentication**
If browser authentication isn't available, users can set:
```bash
export NETLIFY_AUTH_TOKEN=your_token_here
```
Tokens can be generated at: https://app.netlify.com/user/applications#personal-access-tokens
### 2. Detect Site Link Status
From `netlify status` output, determine:
- **Linked**: Site already connected to Netlify (shows site name/URL)
- **Not linked**: Need to link or create site
### 3. Link to Existing Site or Create New
**If already linked** → Skip to step 4
**If not linked**, attempt to link by Git remote:
```bash
# Check if project is Git-based
git remote show origin
# If Git-based, extract remote URL
# Format: https://github.com/username/repo or git@github.com:username/repo.git
# Try to link by Git remote
npx netlify link --git-remote-url <REMOTE_URL>
```
**If link fails** (site doesn't exist on Netlify):
```bash
# Create new site interactively
npx netlify init
```
This guides user through:
1. Choosing team/account
2. Setting site name
3. Configuring build settings
4. Creating netlify.toml if needed
### 4. Verify Dependencies
Before deploying, ensure project dependencies are installed:
```bash
# For npm projects
npm install
# For other package managers, detect and use appropriate command
# yarn install, pnpm install, etc.
```
### 5. Deploy to Netlify
Choose deployment type based on context:
**Preview/Draft Deploy** (default for existing sites):
```bash
npx netlify deploy
```
This creates a deploy preview with a unique URL for testing.
**Production Deploy** (for new sites or explicit production deployments):
```bash
npx netlify deploy --prod
```
This deploys to the live production URL.
**Deployment process**:
1. CLI detects build settings (from netlify.toml or prompts user)
2. Builds the project locally
3. Uploads built assets to Netlify
4. Returns deployment URL
### 6. Report Results
After deployment, report to user:
- **Deploy URL**: Unique URL for this deployment
- **Site URL**: Production URL (if production deploy)
- **Deploy logs**: Link to Netlify dashboard for logs
- **Next steps**: Suggest `netlify open` to view site or dashboard
## Handling netlify.toml
If a `netlify.toml` file exists, the CLI uses it automatically. If not, the CLI will prompt for:
- **Build command**: e.g., `npm run build`, `next build`
- **Publish directory**: e.g., `dist`, `build`, `.next`
Common framework defaults:
- **Next.js**: build command `npm run build`, publish `.next`
- **React (Vite)**: build command `npm run build`, publish `dist`
- **Static HTML**: no build command, publish current directory
The skill should detect framework from `package.json` if possible and suggest appropriate settings.
## Example Full Workflow
```bash
# 1. Check authentication
npx netlify status
# If not authenticated:
npx netlify login
# 2. Link site (if needed)
# Try Git-based linking first
git remote show origin
npx netlify link --git-remote-url https://github.com/user/repo
# If no site exists, create new one:
npx netlify init
# 3. Install dependencies
npm install
# 4. Deploy (preview for testing)
npx netlify deploy
# 5. Deploy to production (when ready)
npx netlify deploy --prod
```
## Error Handling
Common issues and solutions:
**"Not logged in"**
→ Run `npx netlify login`
**"No site linked"**
→ Run `npx netlify link` or `npx netlify init`
**"Build failed"**
→ Check build command and publish directory in netlify.toml or CLI prompts
→ Verify dependencies are installed
→ Review build logs for specific errors
**"Publish directory not found"**
→ Verify build command ran successfully
→ Check publish directory path is correct
## Troubleshooting
### Escalated Network Access
If deployment fails due to network issues (timeouts, DNS errors, connection resets), rerun the deploy with escalated permissions (use `sandbox_permissions=require_escalated`). The deploy requires escalated network access when sandbox networking blocks outbound requests.
Example guidance to the user:
```
The deploy needs escalated network access to deploy to Netlify. I can rerun the command with escalated permissions—want me to proceed?
```
## Environment Variables
For secrets and configuration:
1. Never commit secrets to Git
2. Set in Netlify dashboard: Site Settings → Environment Variables
3. Access in builds via `process.env.VARIABLE_NAME`
## Tips
- Use `netlify deploy` (no `--prod`) first to test before production
- Run `netlify open` to view site in Netlify dashboard
- Run `netlify logs` to view function logs (if using Netlify Functions)
- Use `netlify dev` for local development with Netlify Functions
## Reference
- Netlify CLI Docs: https://docs.netlify.com/cli/get-started/
- netlify.toml Reference: https://docs.netlify.com/configure-builds/file-based-configuration/
## Bundled References (Load As Needed)
- [CLI commands](references/cli-commands.md)
- [Deployment patterns](references/deployment-patterns.md)
- [netlify.toml guide](references/netlify-toml.md)
Referenced files: 7
netlify-edge-functions3.48 KB
View saved version →
---
name: netlify-edge-functions
description: 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.
---
# Netlify Edge Functions
Edge functions run on Netlify's globally distributed edge network (Deno runtime), providing low-latency responses close to users.
## Syntax
```typescript
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
```typescript
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
};
```
## Middleware Pattern
Use `context.next()` to invoke the next handler in the chain and optionally modify the response:
```typescript
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:
```typescript
export default async (req: Request, context: Context) => {
if (!shouldHandle(req)) return; // continues to next handler
return new Response("Handled");
};
```
## Geolocation and IP
```typescript
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`
## Environment Variables
Use `Netlify.env` (not `process.env` or `Deno.env`):
```typescript
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:
```json
// import_map.json
{ "imports": { "html-rewriter": "https://ghuc.cc/worker-tools/html-rewriter/index.ts" } }
```
```toml
# 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 |
Referenced files: 4
netlify-forms6.19 KB
View saved version →
---
name: netlify-forms
description: 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 by Netlify. Covers the data-netlify attribute, spam filtering, AJAX submissions, file uploads, notifications, and the submissions API.
---
# Netlify Forms
Netlify Forms collects HTML form submissions without server-side code. Form detection must be enabled in the Netlify UI (Forms section).
## Basic Setup
Add `data-netlify="true"` and a unique `name` to the form:
```html
<form name="contact" method="POST" data-netlify="true">
<label>Name: <input type="text" name="name" /></label>
<label>Email: <input type="email" name="email" /></label>
<label>Message: <textarea name="message"></textarea></label>
<button type="submit">Send</button>
</form>
```
Netlify's build system detects the form and injects a hidden `form-name` input automatically. For a custom success page, add `action="/thank-you"` to the form tag. Use paths without `.html` extension — Netlify serves `thank-you.html` at `/thank-you` by default, and the `.html` path returns 404.
## JavaScript-Rendered Forms (React, Vue, SSR Frameworks)
For forms rendered by JavaScript frameworks (React, Vue, TanStack Start, Next.js, SvelteKit, Remix, Nuxt), Netlify's build parser cannot detect the form in static HTML. You MUST create a static HTML skeleton file for build-time form detection:
Create a static HTML file in `public/` (e.g. `public/__forms.html`) containing a hidden copy of each form:
```html
<!DOCTYPE html>
<html>
<body>
<form name="contact" data-netlify="true" netlify-honeypot="bot-field" hidden>
<input type="hidden" name="form-name" value="contact" />
<input type="text" name="name" />
<input type="email" name="email" />
<textarea name="message"></textarea>
<input name="bot-field" />
</form>
</body>
</html>
```
**Rules:**
- The form `name` must exactly match the `form-name` value used in your component's fetch call
- Include every field your component submits — Netlify validates field names against the registered form
- Without this file, Netlify cannot detect the form and submissions will silently fail
Your component must also include a hidden `form-name` input:
```jsx
<form name="contact" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="contact" />
{/* ... fields ... */}
</form>
```
## AJAX Submissions
### Vanilla JavaScript
```javascript
const form = document.querySelector("form");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData(form);
await fetch("/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(formData).toString(),
});
});
```
> **SSR frameworks (TanStack Start, Next.js, SvelteKit, Remix, Nuxt):** The `fetch` URL must target the static skeleton
> file path (e.g. `"/__forms.html"`), **not** `"/"`. In SSR apps, `fetch("/")` is intercepted by the SSR catch-all
> function and never reaches Netlify's form processing middleware. See the React example and troubleshooting section below.
### React Example
```tsx
function ContactForm() {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
// For SSR apps, use the skeleton file path instead of "/" (e.g. "/__forms.html")
const response = await fetch("/__forms.html", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(formData as any).toString(),
});
if (response.ok) {
// Show success feedback
}
};
return (
<form name="contact" method="POST" data-netlify="true" onSubmit={handleSubmit}>
<input type="hidden" name="form-name" value="contact" />
<input type="text" name="name" placeholder="Name" />
<input type="email" name="email" placeholder="Email" />
<textarea name="message" placeholder="Message" />
<button type="submit">Send</button>
</form>
);
}
```
> **SSR troubleshooting:** If form submissions appear to succeed (200 response) but nothing shows in the Netlify Forms
> UI, the POST is likely being intercepted by the SSR function. Ensure `fetch` targets the skeleton file path (e.g.
> `"/__forms.html"`), not `"/"`. The skeleton file path routes through the CDN origin where Netlify's form handler runs.
## Spam Filtering
Netlify uses Akismet automatically. Add a honeypot field for extra protection:
```html
<form name="contact" method="POST" netlify-honeypot="bot-field" data-netlify="true">
<p style="display:none">
<label>Don't fill this out: <input name="bot-field" /></label>
</p>
<!-- visible fields -->
</form>
```
For reCAPTCHA, add `data-netlify-recaptcha="true"` to the form and include `<div data-netlify-recaptcha="true"></div>` where the widget should appear.
## File Uploads
```html
<form name="upload" enctype="multipart/form-data" data-netlify="true">
<input type="text" name="name" />
<input type="file" name="attachment" />
<button type="submit">Upload</button>
</form>
```
For AJAX file uploads, use `FormData` directly — do **not** set `Content-Type` (the browser sets it with the correct boundary):
```javascript
await fetch("/", { method: "POST", body: new FormData(form) });
```
**Limits:** 8 MB max request size, 30-second timeout, one file per input field.
## Notifications
Configure in the Netlify UI under **Project configuration > Notifications**:
- **Email**: Auto-sends on submission. Add `<input type="hidden" name="subject" value="Contact form" />` for custom subject lines.
- **Slack**: Via Netlify App for Slack.
- **Webhooks**: Trigger external services on submission.
## Submissions API
Access submissions programmatically:
```
GET /api/v1/forms/{form_id}/submissions
Authorization: Bearer <PERSONAL_ACCESS_TOKEN>
```
Key endpoints:
| Action | Method | Path |
|---|---|---|
| List forms | GET | `/api/v1/sites/{site_id}/forms` |
| Get submissions | GET | `/api/v1/forms/{form_id}/submissions` |
| Get spam | GET | `/api/v1/forms/{form_id}/submissions?state=spam` |
| Delete submission | DELETE | `/api/v1/submissions/{id}` |
Referenced files: 4
netlify-frameworks2.88 KB
View saved version →
---
name: netlify-frameworks
description: Guide for deploying web frameworks on Netlify. Use when setting up a framework project (Vite/React, Astro, TanStack Start, Next.js, Nuxt, SvelteKit, Remix) for Netlify deployment, configuring adapters or plugins, or troubleshooting framework-specific Netlify integration. Covers what Netlify needs from each framework and how adapters handle server-side rendering.
---
# Frameworks on Netlify
Netlify supports any framework that produces static output. For frameworks with server-side capabilities (SSR, API routes, middleware), an adapter or plugin translates the framework's server-side code into Netlify Functions and Edge Functions automatically.
## How It Works
During build, the framework adapter writes files to `.netlify/v1/` — functions, edge functions, redirects, and configuration. Netlify reads these to deploy the site. You do not need to write Netlify Functions manually when using a framework adapter for server-side features.
## Detecting Your Framework
Check these files to determine the framework:
| File | Framework |
|---|---|
| `astro.config.*` | Astro |
| `next.config.*` | Next.js |
| `nuxt.config.*` | Nuxt |
| `vite.config.*` + `react-router` | Vite + React (SPA or Remix) |
| `app.config.*` + `@tanstack/react-start` | TanStack Start |
| `svelte.config.*` | SvelteKit |
## Framework Reference Guides
Each framework has specific adapter/plugin requirements and local dev patterns:
- **Vite + React (SPA or with server routes)**: See [references/vite.md](references/vite.md)
- **Astro**: See [references/astro.md](references/astro.md)
- **TanStack Start**: See [references/tanstack.md](references/tanstack.md)
- **Next.js**: See [references/nextjs.md](references/nextjs.md)
## General Patterns
### Client-Side Routing (SPA)
For single-page apps with client-side routing, add a catch-all redirect:
```toml
# netlify.toml
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
```
### Custom 404 Pages
- **Static sites**: Create a `404.html` in your publish directory. Netlify serves it automatically for unmatched routes.
- **SSR frameworks**: Handle 404s in the framework's routing (the adapter maps this to Netlify's function routing).
### Environment Variables in Frameworks
Each framework exposes environment variables to client-side code differently:
| Framework | Client prefix | Access pattern |
|---|---|---|
| Vite / React | `VITE_` | `import.meta.env.VITE_VAR` |
| Astro | `PUBLIC_` | `import.meta.env.PUBLIC_VAR` |
| Next.js | `NEXT_PUBLIC_` | `process.env.NEXT_PUBLIC_VAR` |
| Nuxt | `NUXT_PUBLIC_` | `useRuntimeConfig().public.var` |
Server-side code in all frameworks can access variables via `process.env.VAR` or `Netlify.env.get("VAR")`.
## Bundled References (Load As Needed)
- [Vite + React guide](references/vite.md)
- [Astro guide](references/astro.md)
- [TanStack Start guide](references/tanstack.md)
- [Next.js guide](references/nextjs.md)
Referenced files: 8
netlify-functions5.08 KB
View saved version →
---
name: netlify-functions
description: Guide for writing Netlify serverless functions. Use when creating API endpoints, background processing, scheduled tasks, or any server-side logic using Netlify Functions. Covers modern syntax (default export + Config), TypeScript, path routing, background functions, scheduled functions, streaming, and method routing.
---
# Netlify Functions
## Modern Syntax
Always use the modern default export + Config pattern. Never use the legacy `exports.handler` or named `handler` export.
```typescript
import type { Context, Config } from "@netlify/functions";
export default async (req: Request, context: Context) => {
return new Response("Hello, world!");
};
export const config: Config = {
path: "/api/hello",
};
```
The handler receives a standard Web API `Request` and returns a `Response`. The second argument is a Netlify `Context` object.
## File Structure
Place functions in `netlify/functions/`:
```
netlify/functions/
_shared/ # Non-function shared code (underscore prefix)
auth.ts
db.ts
items.ts # -> /.netlify/functions/items (or custom path via config)
users/index.ts # -> /.netlify/functions/users
```
Use `.ts` or `.mts` extensions. If both `.ts` and `.js` exist with the same name, the `.js` file takes precedence.
## Path Routing
Define custom paths via the `config` export:
```typescript
export const config: Config = {
path: "/api/items", // Static path
// path: "/api/items/:id", // Path parameter
// path: ["/api/items", "/api/items/:id"], // Multiple paths
// excludedPath: "/api/items/special", // Excluded paths
// preferStatic: true, // Don't override static files
};
```
Without a `path` config, functions are available at `/.netlify/functions/{name}`. Setting a `path` makes the function available **only** at that path.
Access path parameters via `context.params`:
```typescript
// config: { path: "/api/items/:id" }
export default async (req: Request, context: Context) => {
const { id } = context.params;
// ...
};
```
## Method Routing
```typescript
export default async (req: Request, context: Context) => {
switch (req.method) {
case "GET": return handleGet(context.params.id);
case "POST": return handlePost(await req.json());
case "DELETE": return handleDelete(context.params.id);
default: return new Response("Method not allowed", { status: 405 });
}
};
export const config: Config = {
path: "/api/items/:id",
method: ["GET", "POST", "DELETE"],
};
```
## Background Functions
For long-running tasks (up to 15 minutes). The client receives an immediate `202` response; return values are ignored.
Name the file with a `-background` suffix:
```
netlify/functions/process-background.ts
```
Store results externally (Netlify Blobs, database) for later retrieval.
## Scheduled Functions
Run on a cron schedule (UTC timezone):
```typescript
export default async (req: Request) => {
const { next_run } = await req.json();
console.log("Next invocation at:", next_run);
};
export const config: Config = {
schedule: "@hourly", // or cron: "0 * * * *"
};
```
Shortcuts: `@yearly`, `@monthly`, `@weekly`, `@daily`, `@hourly`. Scheduled functions have a **30-second timeout** and only run on published deploys.
## Streaming Responses
Return a `ReadableStream` body for streamed responses (up to 20 MB):
```typescript
export default async (req: Request) => {
const stream = new ReadableStream({ /* ... */ });
return new Response(stream, {
headers: { "Content-Type": "text/event-stream" },
});
};
```
## Context Object
| Property | Description |
|---|---|
| `context.params` | Path parameters from config |
| `context.geo` | `{ city, country: {code, name}, latitude, longitude, subdivision, timezone, postalCode }` |
| `context.ip` | Client IP address |
| `context.cookies` | `.get()`, `.set()`, `.delete()` |
| `context.deploy` | `{ context, id, published }` |
| `context.site` | `{ id, name, url }` |
| `context.account.id` | Team account ID |
| `context.requestId` | Unique request ID |
| `context.waitUntil(promise)` | Extend execution after response is sent |
## Environment Variables
Use `Netlify.env` (not `process.env`) inside functions:
```typescript
const apiKey = Netlify.env.get("API_KEY");
```
## Resource Limits
| Resource | Limit |
|---|---|
| Synchronous timeout | 60 seconds |
| Background timeout | 15 minutes |
| Scheduled timeout | 30 seconds |
| Memory | 1024 MB |
| Buffered payload | 6 MB |
| Streamed payload | 20 MB |
## Framework Considerations
Frameworks with server-side capabilities (Astro, Next.js, Nuxt, SvelteKit, TanStack Start) typically generate their own serverless functions via adapters. You usually do not write raw Netlify Functions in these projects — the framework adapter handles server-side rendering and API routes. Write Netlify Functions directly when:
- Using a client-side-only framework (Vite + React SPA, vanilla JS)
- Adding background or scheduled tasks to any project
- Building standalone API endpoints outside the framework's routing
See the **netlify-frameworks** skill for adapter setup.
Referenced files: 4
netlify-identity9.66 KB
View saved version →
---
name: netlify-identity
description: Use when the task involves authentication, user signups, logins, password recovery, OAuth providers, role-based access control, or protecting routes and functions. Always use `@netlify/identity`. Never use `netlify-identity-widget` or `gotrue-js` — they are deprecated.
---
# Netlify Identity
Netlify Identity is a user management service for signups, logins, password recovery, user metadata, and role-based access control. It is built on [GoTrue](https://github.com/netlify/gotrue) and issues JSON Web Tokens (JWTs).
**Always use `@netlify/identity`.** Never use `netlify-identity-widget` or `gotrue-js` — they are deprecated. `@netlify/identity` provides a unified, headless TypeScript API that works in both browser and server contexts (Netlify Functions, Edge Functions, SSR frameworks).
## Setup
```bash
npm install @netlify/identity
```
Identity is automatically enabled when a deploy created by a Netlify Agent Runner session includes Identity code. Otherwise, it must be manually enabled in the UI. These are the default settings:
- **Registration** — Open (anyone can sign up). Change to Invite only in **Project configuration > Identity** if needed.
- **Autoconfirm** — Off (new signups require email confirmation). Enable in **Project configuration > Identity** to skip confirmation during development.
### Local Development
Identity does **not** currently work with `netlify dev`. You must deploy to Netlify to test Identity features. Use `npx netlify deploy` for preview deploys during development. This limitation may be resolved in a future release.
## Quick Start
Log in from the browser:
```typescript
import { login, getUser } from '@netlify/identity'
const user = await login('user@example.com', '<password>')
console.log(`Hello, ${user.name}`)
// Later, check auth state
const currentUser = await getUser()
```
Protect a Netlify Function:
```typescript
// netlify/functions/protected.mts
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 })
}
```
## Core API
Import and use headless functions directly:
```typescript
import {
getUser,
handleAuthCallback,
login,
logout,
signup,
oauthLogin,
onAuthChange,
getSettings,
} from '@netlify/identity'
```
### Login
```typescript
import { login, AuthError } from '@netlify/identity'
async function handleLogin(email: string, password: string) {
try {
const user = await login(email, password)
showSuccess(`Welcome back, ${user.name ?? user.email}`)
} catch (error) {
if (error instanceof AuthError) {
showError(error.status === 401 ? 'Invalid email or password.' : error.message)
}
}
}
```
### Signup
After signup, check `user.emailVerified` to determine if the user was auto-confirmed or needs to confirm their email.
```typescript
import { signup, AuthError } from '@netlify/identity'
async function handleSignup(email: string, password: string, name: string) {
try {
const user = await signup(email, password, { full_name: name })
if (user.emailVerified) {
// Autoconfirm ON — user is logged in immediately
showSuccess('Account created. You are now logged in.')
} else {
// Autoconfirm OFF — confirmation email sent
showSuccess('Check your email to confirm your account.')
}
} catch (error) {
if (error instanceof AuthError) {
showError(error.status === 403 ? 'Signups are not allowed.' : error.message)
}
}
}
```
### Logout
```typescript
import { logout } from '@netlify/identity'
await logout()
```
### OAuth
OAuth is a two-step flow: `oauthLogin(provider)` redirects away from the site, then `handleAuthCallback()` processes the redirect when the user returns.
```typescript
import { oauthLogin } from '@netlify/identity'
// Step 1: Redirect to provider (navigates away — never returns)
function handleOAuthClick(provider: 'google' | 'github' | 'gitlab' | 'bitbucket') {
oauthLogin(provider)
}
```
Enable providers in **Project configuration > Identity > External providers** before using OAuth.
### Handling Callbacks
Always call `handleAuthCallback()` on page load in any app that uses OAuth, password recovery, invites, or email confirmation. It processes all callback types via the URL hash.
```typescript
import { handleAuthCallback, AuthError } from '@netlify/identity'
async function processCallback() {
try {
const result = await handleAuthCallback()
if (!result) return // No callback hash — normal page load
switch (result.type) {
case 'oauth':
showSuccess(`Logged in as ${result.user?.email}`)
break
case 'confirmation':
showSuccess('Email confirmed. You are now logged in.')
break
case 'recovery':
// User is authenticated but must set a new password
showPasswordResetForm(result.user)
break
case 'invite':
// User must set a password to accept the invite
showInviteAcceptForm(result.token)
break
case 'email_change':
showSuccess('Email address updated.')
break
}
} catch (error) {
if (error instanceof AuthError) showError(error.message)
}
}
```
### Auth State
```typescript
import { getUser, onAuthChange, AUTH_EVENTS } from '@netlify/identity'
// Check current user (never throws — returns null if not authenticated)
const user = await getUser()
// Subscribe to auth state changes (returns unsubscribe function)
const unsubscribe = onAuthChange((event, user) => {
switch (event) {
case AUTH_EVENTS.LOGIN:
console.log('Logged in:', user?.email)
break
case AUTH_EVENTS.LOGOUT:
console.log('Logged out')
break
case AUTH_EVENTS.TOKEN_REFRESH:
break
case AUTH_EVENTS.USER_UPDATED:
console.log('Profile updated:', user?.email)
break
case AUTH_EVENTS.RECOVERY:
console.log('Password recovery initiated')
break
}
})
```
### Settings-Driven UI
Fetch the project's Identity settings to conditionally render signup forms and OAuth buttons.
```typescript
import { getSettings } from '@netlify/identity'
const settings = await getSettings()
// settings.autoconfirm — boolean
// settings.disableSignup — boolean
// settings.providers — Record<AuthProvider, boolean>
if (!settings.disableSignup) showSignupForm()
for (const [provider, enabled] of Object.entries(settings.providers)) {
if (enabled) showOAuthButton(provider)
}
```
## Minimal React Example
```tsx
import { useEffect, useState } from 'react'
import {
getUser,
handleAuthCallback,
login,
logout,
oauthLogin,
onAuthChange,
} from '@netlify/identity'
function App() {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
;(async () => {
await handleAuthCallback()
setUser(await getUser())
setLoading(false)
})()
return onAuthChange((_event, currentUser) => setUser(currentUser))
}, [])
const handleLogin = async (email, password) => {
const currentUser = await login(email, password)
setUser(currentUser)
}
const handleGoogleLogin = () => oauthLogin('google')
const handleSignOut = async () => {
await logout()
setUser(null)
}
if (loading) return <p>Loading...</p>
// Render login form or user details based on `user` state
}
```
## Error Handling
`@netlify/identity` throws two error classes:
- **`AuthError`** — Thrown by auth operations. Has `message`, optional `status` (HTTP status code), and optional `cause`.
- **`MissingIdentityError`** — Thrown when Identity is not configured in the current environment.
`getUser()` and `isAuthenticated()` never throw — they return `null` and `false` respectively on failure.
| Status | Meaning |
|--------|---------|
| 401 | Invalid credentials or expired token |
| 403 | Action not allowed (e.g., signups disabled) |
| 422 | Validation error (e.g., weak password, malformed email) |
| 404 | User or resource not found |
## Identity Event Functions
Special serverless functions that trigger on Identity lifecycle events. These use the **legacy named `handler` export** (not the modern default export).
**Event names:** `identity-validate`, `identity-signup`, `identity-login`
```typescript
// netlify/functions/identity-signup.mts
import type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions'
const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
const { user } = JSON.parse(event.body || '{}')
return {
statusCode: 200,
body: JSON.stringify({
app_metadata: {
...user.app_metadata,
roles: ['member'],
},
}),
}
}
export { handler }
```
The response body replaces `app_metadata` and/or `user_metadata` on the user record — include all fields you want to keep.
## Roles and Authorization
- **`app_metadata.roles`** — Server-controlled. Only settable via the Netlify UI, admin API, or Identity event functions. Never let users set their own roles.
- **`user_metadata`** — User-controlled. Users can update via `updateUser({ data: { ... } })`.
### Role-Based Redirects
```toml
# netlify.toml
[[redirects]]
from = "/admin/*"
to = "/admin/:splat"
status = 200
conditions = { Role = ["admin"] }
[[redirects]]
from = "/admin/*"
to = "/"
status = 302
```
Rules are evaluated top-to-bottom. The `nf_jwt` cookie is read by the CDN to evaluate role conditions.
## Bundled References (Load As Needed)
- [Advanced patterns](references/advanced-patterns.md) — password recovery, invite acceptance, email change, session hydration, SSR integration
Referenced files: 5
netlify-image-cdn2.61 KB
View saved version →
---
name: netlify-image-cdn
description: Guide for using Netlify Image CDN for image optimization and transformation. Use when serving optimized images, creating responsive image markup, setting up user-uploaded image pipelines, or configuring image transformations. Covers the /.netlify/images endpoint, query parameters, remote image allowlisting, clean URL rewrites, and composing uploads with Functions + Blobs.
---
# Netlify Image CDN
Every Netlify site has a built-in `/.netlify/images` endpoint for on-the-fly image transformation. No configuration required for local images.
## Basic Usage
```html
<img src="/.netlify/images?url=/photo.jpg&w=800&h=600&fit=cover&q=80" />
```
## Query Parameters
| Param | Description | Values |
|---|---|---|
| `url` | Source image path (required) | Relative path or absolute URL |
| `w` | Width in pixels | Any positive integer |
| `h` | Height in pixels | Any positive integer |
| `fit` | Resize behavior | `contain` (default), `cover`, `fill` |
| `position` | Crop alignment (with `cover`) | `center` (default), `top`, `bottom`, `left`, `right` |
| `fm` | Output format | `avif`, `webp`, `jpg`, `png`, `gif`, `blurhash` |
| `q` | Quality (lossy formats) | 1-100 (default: 75) |
When `fm` is omitted, Netlify auto-negotiates the best format based on browser support (preferring `webp`, then `avif`).
## Remote Image Allowlisting
External images must be explicitly allowed in `netlify.toml`:
```toml
[images]
remote_images = ["https://example\\.com/.*", "https://cdn\\.images\\.com/.*"]
```
Values are regex patterns.
## Clean URL Rewrites
Create user-friendly image URLs with redirects:
```toml
# Basic optimization
[[redirects]]
from = "/img/*"
to = "/.netlify/images?url=/:splat"
status = 200
# Preset: thumbnail
[[redirects]]
from = "/img/thumb/:key"
to = "/.netlify/images?url=/uploads/:key&w=150&h=150&fit=cover"
status = 200
# Preset: hero
[[redirects]]
from = "/img/hero/:key"
to = "/.netlify/images?url=/uploads/:key&w=1200&h=675&fit=cover"
status = 200
```
## Caching
- Transformed images are cached at the CDN edge automatically
- Cache invalidates on new deploys
- Set cache headers on source images to control caching:
```toml
[[headers]]
for = "/uploads/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
```
## User-Uploaded Images
Combine **Netlify Functions** (upload handler) + **Netlify Blobs** (storage) + **Image CDN** (serving/transforming) to build a complete user-uploaded image pipeline. See [references/user-uploads.md](references/user-uploads.md) for the full pattern.
## Bundled References (Load As Needed)
- [User uploads pipeline](references/user-uploads.md)
Referenced files: 5