
Description
Reference for netlify.toml configuration and site environment variables. Use when configuring build settings, redirects, rewrites, headers, deploy contexts, the `[dev]` block that controls `netlify dev` (command, port, targetPort, framework), or any site-level configuration — and when managing environment variables or secrets with the Netlify CLI, including scoping values to specific deploy contexts. Covers the complete netlify.toml syntax including redirects with splats/conditions, headers, deploy contexts, functions config, edge functions config, and the `[dev]` block (including when `framework` must be `"#custom"` — required when both a custom `command` and `targetPort` are set).
SKILL.md
Netlify Configuration (netlify.toml)
Place netlify.toml at the repository root. In a monorepo, Netlify searches for the config and uses the first one it finds: the package directory (the subdirectory containing the site), then the base directory, then the repository root — so put a site-specific netlify.toml in the package directory to take precedence over root-level config (see the netlify-deploy skill's monorepo notes).
netlify.toml takes precedence over the Netlify UI. When the same property (build command, publish directory, an environment variable, a redirect, a header) is configured in both places, the value in netlify.toml wins and silently overrides the corresponding Netlify UI setting — the dashboard field still shows its old value but is inert. Once a netlify.toml is present, treat it as the source of truth and change settings there, not in the UI.
Build Settings
[build]
base = "project/" # Base directory (default: root)
command = "npm run build" # Build command
publish = "dist/" # Output directory
Redirects
# 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.
Redirect rules can also live in a plain-text _redirects file in the publish directory. If both a _redirects file and [[redirects]] in netlify.toml exist, the _redirects file rules are processed first, then the netlify.toml rules, reading top to bottom — and the first matching rule wins. The same ordering applies to a _headers file versus [[headers]]. Because a _redirects rule can silently shadow a netlify.toml rule for the same path, keep overlapping rules in a single source.
Headers
[[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:
[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"
[[redirects]] and [[headers]] are global — they cannot be scoped to a deploy context. Context tables like [context.production] work for keys such as [build], [build.environment], and [[plugins]], but redirect and header rules apply to every context no matter where you place them in the file; there is no [context.production.redirects] or context-nested [[redirects]]. For context-specific redirects or headers, use the per-deploy escape hatch: generate a _redirects or _headers file during that context's build (those files ship per deploy), or gate the behavior on a runtime signal in an edge function.
Environment Variables
[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 CLI Management below.
Variables declared in netlify.toml are build-scoped only. Values under [build.environment] or [context.*.environment] are available to the build (and snippet injection) but are not injected into the Functions or Edge Functions runtime — reading them with Netlify.env.get("VAR") or process.env.VAR inside a function returns undefined. To make a variable available at function runtime, set it in the Netlify UI or with netlify env:set (those are available to both builds and runtime), not in netlify.toml.
CLI Management
# 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 # Local snapshot only — keep .env gitignored, never commit it
# Import from file
netlify env:import .env
# Delete
netlify env:unset API_KEY
Never put secrets in client-prefixed variables (VITE_, PUBLIC_, NEXT_PUBLIC_, NUXT_PUBLIC_) — these are inlined into the client bundle and exposed to the browser. --secret only hides a value from logs and the UI; it does not protect a client-prefixed variable.
Context Scoping
Variables set via the CLI can also be scoped to deploy contexts:
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
This is the CLI equivalent of the [context.*.environment] tables above, but the resulting variables are available at both build and runtime (unlike netlify.toml-declared ones).
When reading these variables in server code, prefer Netlify.env.get("VAR"). process.env.VAR also works inside Functions, but Edge Functions expose only Netlify.env.get — the portable form keeps the same code working in both runtimes.
For the client-side rules (VITE_/PUBLIC_ prefixes and framework specifics), see the netlify-frameworks skill; for function-side details, see netlify-functions.
Functions Configuration
[functions]
directory = "netlify/functions" # Default
node_bundler = "esbuild"
# Scheduled function
[functions."cleanup"]
schedule = "@daily"
Use the single-table [functions] form for global settings and [functions."name-or-glob"] for per-function overrides. There is no [[functions]] array-of-tables and no path-based function routing table in netlify.toml — functions are routed by file (served at /.netlify/functions/{name}) or by an in-code path/config export, not by config.
Edge Functions Configuration
[[edge_functions]]
path = "/admin/*"
function = "auth"
excludedPath = "/admin/public/*" # Carve exceptions out of `path` (string or array of globs)
# Import map for Deno URL imports
[functions]
deno_import_map = "./import_map.json"
Dev Server
[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"
If you set both a custom command and a targetPort, framework must be "#custom". With framework = "#auto" (the default) Netlify Dev runs its own detector and ignores your custom command; "#custom" tells it to run your command as the app server and connect to targetPort. Setting command + targetPort while leaving framework at #auto (or omitting it) is a silent misconfiguration.
Plugins
[[plugins]]
package = "@netlify/plugin-lighthouse"
[plugins.inputs]
audits = ["performance", "accessibility"]
Image CDN
[images]
remote_images = ["https://example\\.com/.*"]
See the netlify-image-cdn skill for full Image CDN usage.
More skills from the context-and-tools repository
View all 15 skillsnetlify-access-control
manage Netlify site access control
Jul 14Access ControlNetlifyPermissionsSecuritynetlify-agent-runner
run AI agent tasks on Netlify
Jul 17AgentsAutomationLLMNetlifynetlify-ai-gateway
route AI requests via Netlify AI Gateway
Jul 17AI InfrastructureAPI DevelopmentLLMNetlifynetlify-blobs
manage file storage with Netlify Blobs
Jul 17BackendFile StorageNetlifyStoragenetlify-caching
configure CDN caching on Netlify
Jul 14CachingDeploymentNetlifyPerformancenetlify-database
provision and manage Netlify Database
Jul 20BackendDatabaseNetlifyPostgreSQL
More from Netlify
View publisherconfigure-axis
configure AXIS agent evaluation scenarios
axis
Jul 20AgentsConfigurationEvalsNetlifyusing-axis
run and interpret AXIS reports
axis
Jul 14CLIEvalsNetlifynetlify-deploy
deploy and host projects on Netlify
context-and-tools
Jul 17CLIDeploymentNetlifyWeb Developmentnetlify-edge-functions
write Netlify Edge Functions
context-and-tools
Jul 14Edge FunctionsMiddlewareNetlifyPerformancenetlify-forms
handle HTML forms with Netlify
context-and-tools
Jul 14FormsHTMLNetlifyWeb Development