Configure Netlify projects via netlify.toml and the _headers/_redirects files — covering build settings and deploy contexts alongside environment variables/scopes and the Secrets Controller plus redirect/rewrite/proxy and custom-header rules. Use when setting a build command or publish directory, adding redirect or rewrite or proxy rules, configuring custom headers or basic auth, setting or scoping environment variables and secrets, wiring up a monorepo or SPA fallback, or skipping unnecessary builds. Reach for this whenever you touch netlify.toml or ask "why is my env var undefined in a function" or "how do I redirect this path".
70
86%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
netlify.toml lives at the repo root (or set base/package directory for monorepos). Settings in netlify.toml override the Netlify UI on conflict. _headers and _redirects are extensionless plain-text files in the publish directory, processed before netlify.toml rules.
netlify.toml are NOT available to functions or edge functions at runtime — reading them there returns undefined. Vars declared in netlify.toml only get the Builds and Post processing scopes. Set runtime vars in the UI or with netlify env:set.VITE_, NEXT_PUBLIC_, PUBLIC_, …) — they are inlined into the client bundle. --secret does not protect them..env is not read by the Netlify build system — import variables into Netlify first (netlify env:import). The CLI reads .env only for local builds.netlify.toml (key = "$VAR") is unsupported — except signed proxy redirects. Use a build plugin or sed in the build command.[[redirects]] and [[headers]] are global — NOT context-aware, cannot be scoped to branches/contexts. Workaround: per-context build command copies a custom file into the publish directory.307 is unsupported — use 302.netlify.toml — core structure[build]
base = "project/" # base directory
publish = "build-output/" # relative to base, default /
command = "npm run build" # runs in Bash shell
[build.environment]
NODE_VERSION = "18"
[context.production] # production branch deploy
command = "make publish"
environment = { NODE_VERSION = "14.15.3" }
[context.deploy-preview] # PR/MR previews
publish = "dist/"
[context.branch-deploy] # non-production branches
command = "echo branch"
[context.dev.environment] # local dev env vars ONLY
NODE_ENV = "development"
[context.staging] # a specific branch name
command = "echo staging"
[context."feat/branch"] # quote branches with special chars
command = "echo special"Context precedence (least → most specific): UI settings < base context-aware key < [context.production|deploy-preview|branch-deploy|dev] < [context.branchname]. Only [build] and [[plugins]] are context-aware. All paths are absolute relative to the base directory (root / default).
Config file search order: package directory → base directory → root.
[functions]
directory = "functions/" # default: YOUR_BASE_DIR/netlify/functions
node_bundler = "esbuild" # prefer esbuild; zisi is the JS default
external_node_modules = ["package-1"]
included_files = ["files/*.md", "!files/skip.md"]
[functions."api_*"] # glob filter; values CONCATENATE across matches
external_node_modules = ["package-2"]esbuild = smaller/faster artifacts; TypeScript functions always use esbuild.external_node_modules applies only with esbuild. included_files: * wildcard, ! excludes; paths absolute to base.Set runtime/scoped vars via CLI/UI/API (not netlify.toml):
netlify env:set MY_KEY value --secret # --secret marks an env var secret
netlify env:import .env # site-level, all scopes, all contexts
netlify env:list --plain --context production > .env
netlify env:unset MY_KEYKeep any .env snapshot gitignored — never commit it.
Types: site vars (one site) vs shared vars (whole team; Pro/Enterprise; Team Owners only).
Scopes (Pro/Enterprise; default = all): Builds, Functions (also Edge Functions + On-demand Builders), Runtime (forms, signed proxy redirects), Post processing (snippet injection). Vars from netlify.toml are locked to Builds + Post processing.
Scope precedence is independent per scope: a site variable scoped only to Builds does NOT shadow a shared variable for the Functions scope — the shared value still applies there. Site beats shared only within the scopes the site variable actually carries.
Deploy-context values: Production, Deploy Previews, Branch deploys (override per-branch with a Branch value, wildcard suffix release/*), Preview server, Local development.
Overrides: netlify.toml vars override same-key UI/CLI/API vars. Site var beats shared var per its scopes/contexts.
Limits: keys ≤ 255 chars, alphanumeric + underscore, first char a letter (KEY1 ok; 1KEY/_KEY1 invalid). Values ≤ 5,000 chars (functions within AWS limits). Reserved read-only names can't be overridden.
Settable in netlify.toml [build.environment]: NODE_VERSION, NODE_ENV, NPM_VERSION, NPM_FLAGS, NPM_TOKEN, YARN_VERSION, PNPM_FLAGS, BUN_VERSION, RUBY_VERSION, PHP_VERSION, PYTHON_VERSION, GO_VERSION, HUGO_VERSION, NETLIFY_USE_YARN, CI, etc.
Set in UI/CLI only (NOT netlify.toml, which is read after clone): AWS_LAMBDA_JS_RUNTIME, GIT_LFS_ENABLED, GIT_LFS_FETCH_INCLUDE, NETLIFY_BUILD_DEBUG.
Read-only build metadata (examples): NETLIFY, BUILD_ID, CONTEXT (production/deploy-preview/branch-deploy/dev), BRANCH, HEAD, COMMIT_REF, CACHED_COMMIT_REF, PULL_REQUEST, REVIEW_ID, URL, DEPLOY_URL, DEPLOY_PRIME_URL, DEPLOY_ID, SITE_NAME, SITE_ID, ACCOUNT_ID.
Access: Bash $VAR_NAME in build/ignore commands; process.env.VAR_NAME in Node scripts and plugins. Scope must include Builds.
[build]
command = "sed -i \"s|HEADER_PLACEHOLDER|${PROD_API_LOCATION}|g\" netlify.toml && yarn build"Substitution only reaches [[headers]]/[[redirects]] (read after build); NOT available to build plugins. Alternatively mutate netlifyConfig in a local build plugin.
_redirects (one rule per line) or [[redirects]]. Rules process top-down; first match wins. _redirects/file rules run before netlify.toml.
/home / 301
/my-redirect / 302
/store id=:id /blog/:id 301
/news/* /blog/:splat
/* /index.html 200 # SPA fallback[[redirects]]
from = "/old-path"
to = "/new-path"
status = 302 # default 301
force = true # default false; shadow an existing URL
query = { id = ":id" }
conditions = { Language = ["en"], Country = ["US"], Role = ["admin"] }
[redirects.headers]
X-From = "Netlify"! in _redirects or force = true in toml.*) only at the end of a path segment (/jobs/*.html won't work). Can't exclude a path from a splat — order a more specific rule first.id=:id matches URLs with only id and no other params. List optional-param variants most-general-last.Country=au,nz). Country = ISO 3166-1 alpha-2; Language = browser/locale codes, matches the FIRST Accept-Language entry. nf_country/nf_lang cookies override.307 unsupported → use 302._redirects + netlify.toml can fail the deploy if too large — consider Edge Functions./api/* https://api.example.com/:splat 200
/netlify-site/* https://my-other-site.netlify.app/:splat 200[[redirects]]
from = "/search"
to = "https://api.mysearch.com"
status = 200
force = true
headers = { X-From = "Netlify" }<base>..netlify.app subdomain. Rewrites into a separate password-protected site are not allowed.netlify.toml only)[[redirects]]
from = "/search"
to = "https://api.mysearch.com"
status = 200
force = true
signed = "API_SIGNATURE_TOKEN_PLACEHOLDER"Must be in netlify.toml; env var scope must include Runtime; not supported proxying Netlify→Netlify. Netlify sends the JWS as HMAC HS256 in the x-nf-sign header. (This is the one place $VAR-style env injection is allowed.)
/*
X-Frame-Options: DENY
cache-control: max-age=0
cache-control: no-cache # multi-value collapses comma-joined[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
Basic-Auth = "someuser:somepassword anotheruser:anotherpassword"
cache-control = '''
max-age=0,
no-cache,
no-store'''Content-Length, Content-Encoding, Location (use redirects), Set-Cookie (may be overridden), Server, Date, Age, Connection, Transfer-Encoding, etc.*.netlify.app (Public Suffix List) — needs a custom domain.Mark a var secret via --secret (CLI), is_secret: true (API), or the UI. Enforced, non-customizable policy:
post processing scope.dev context value is unmasked and exempt.SECRETS_SCAN_SMART_DETECTION_OMIT_VALUES (comma-separated), then redeploy.Sensitive variable policy (public repos only): untrusted deploys (unrecognized authors) default to Require approval; alternatives are Deploy without sensitive variables or Deploy without restrictions. Not available for GitHub Enterprise Server / GitLab self-managed (treated as private).
[build]
ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF packages/blog"0 = no changes, build stops; exit 1 = changed, build continues.package.json deps unavailable. Referenced file paths must start with ./.Node.js variant:
// ignore_build.js — build only non-debug branches
process.exitCode = process.env.BRANCH.includes("debug") ? 0 : 1[build]
command = "npm run build"
publish = "dist" # varies by framework
[[redirects]]
from = "/*"
to = "/index.html"
status = 200 # required for pushState routing to avoid 404sHashed/code-split filenames + atomic deploys can break asset refs (Uncaught SyntaxError: Unexpected token) — disable hashed filenames, use permalinks, or a service worker.
Recommended: set the site's subdirectory as the package directory (keep netlify.toml there), leave base directory at repo root /, declare deps at the subdirectory level.
netlify.toml. Base directory can be set in root-level netlify.toml ([build] base) and overrides the UI./frontend + plugin at /frontend/packages/my-app/plugins → specify /packages/my-app/plugins/....ignore command. CLI: --filter <site>. Netlify caches all node_modules regardless of where deps are declared.[[plugins]]
package = "@netlify/plugin-lighthouse"
[plugins.inputs]
breeds = ["pomeranian"]
[[integrations]] # extensions; install on team first
name = "abc-performance-extension"
[integrations.config]
output_path = "reports/perf.html"
[dev] # Netlify Dev — NOT run in Bash; no `environment` key here
command = "yarn start"
targetPort = 3000 # if command + targetPort both set, framework must be "#custom"
port = 8888
publish = "dist"
[dev.https]
certFile = "cert.pem"
keyFile = "key.pem"[dev] has no environment property — set local env vars in [context.dev.environment] instead. framework values: #auto (default), #static, #custom.
For Deploy-to-Netlify buttons use [template] / [template.environment].
Post-processing pretty URLs:
[build.processing.html]
pretty_urls = trueThese are org conventions, not docs facts — merged into the rendered skill by ctx-gen and never generated. Owned by the skills maintainer.
netlify.toml are NOT available to functions or edge
functions at runtime — reading them there returns undefined. Set
runtime vars in the UI or with netlify env:set, not netlify.toml.VITE_, NEXT_PUBLIC_,
PUBLIC_, ...) — they are inlined into the client bundle; --secret
does not protect them.netlify env:list --plain > .env),
keep .env gitignored — never commit it.47848e2
Also appears in
last in sync May 18, 2026
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.