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.
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]
base = "project/" # Base directory (default: root)
command = "npm run build" # Build command
publish = "dist/" # Output directory
# 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]]
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).
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.
[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.
# 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.
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]
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]]
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]
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]]
package = "@netlify/plugin-lighthouse"
[plugins.inputs]
audits = ["performance", "accessibility"]
[images]
remote_images = ["https://example\\.com/.*"]
See the netlify-image-cdn skill for full Image CDN usage.
skillbazaar install netlify-config --agent claudeSign in (free) to install skills with the CLI.
Author
@netlify
on GitHub
Published by