Vercel environment variables
Secrets are the highest-risk thing this skill touches. Rules first:
- Never print, log, echo or paste a variable's value into the chat, a commit, a
command line or a file in the repository. Refer to variables by name.
- Never ask the user to paste a secret into the chat. Tell them to run the
command that prompts for the value in their own terminal.
- Any write (
add, update, rm, --force) changes the user's project and
needs a yes from the user in this conversation for that specific variable,
environment and project. Reads of names (vercel env ls) do not.
Environments
| Environment | Used by | Notes |
|---|
| Production | production deployments (an approved production deploy, or a push to the production branch) | |
| Preview | every non-production deployment; can be limited to one Git branch | a branch-specific value overrides the general Preview one |
| Development | vercel dev, vercel env pull | not used by deployments |
Custom (for example staging) | deployments targeting that environment | can import from another environment |
- Changes apply only to new deployments. An existing deployment keeps the
values it was built with; redeploy to pick up a change.
- Total size is 64 KB per deployment; Edge Runtime code is limited to 5 KB per
variable.
- The CLI stores production, preview and custom variables as
sensitive by default (value cannot be read back from the dashboard or
CLI). Development variables stay encrypted.
--no-sensitive opts out, which
a team policy may forbid; do not use it without approval.
- Variables reach code as
process.env.NAME. Vercel also sets system variables
such as VERCEL_ENV (production, preview, development), VERCEL_URL
(no protocol), VERCEL_PROJECT_PRODUCTION_URL, and VERCEL_GIT_COMMIT_REF.
Public prefixes expose values to browsers
Frameworks inline variables with a public prefix (NEXT_PUBLIC_, VITE_,
PUBLIC_, NUXT_PUBLIC_, REACT_APP_, EXPO_PUBLIC_) into client bundles at
build time. Rules:
- Never put a secret, private key, service-role key or password under a public
prefix. A publishable or anon key is fine by design.
- Changing a public variable requires a rebuild, not just a restart.
/vercel-preflight flags public-prefixed names that look secret.
Commands
vercel env ls # names, environments, types (no values for sensitive ones)
vercel env ls preview feature-x # one environment, one branch
vercel env pull .env.local # writes DEVELOPMENT values to a file (secrets on disk)
vercel env pull .env.preview.local --environment=preview --git-branch=feature-x
vercel env run -- next dev # run a command with Development values, nothing written
vercel env run -e preview -- npm test
vercel env add API_TOKEN production # prompts for the value in the user's terminal
vercel env update API_TOKEN production
vercel env rm OLD_TOKEN production
- Prefer
vercel env run -- <cmd> over pull when the values are only needed for
one command: nothing is written to disk.
vercel env pull merges into an existing file and keeps local-only entries.
Before pulling, confirm the target file is covered by .gitignore
(.env*.local is the usual pattern). Sensitive values that Vercel cannot
return are written as placeholders.
- Never put a secret in a pipe or argument (
echo $SECRET | vercel env add ...):
it lands in shell history. Let the CLI prompt, or redirect from a file the user
controls (vercel env add NAME production < secret.txt), and tell the user to
delete that file afterward.
- Rotating a secret: add the new value with the provider first,
vercel env update, redeploy, verify, then revoke the old one. Each step is a separate
approval-worthy action.
vercel env rm on a variable still in use breaks the next deployment; check
usage in code first (grep -rn "process.env.NAME").
Auditing
To audit without reading values: vercel env ls, then compare names against what
the code reads (grep -rhoE "process\.env\.[A-Z0-9_]+" --include=*.{ts,tsx,js,jsx,mjs})
and against .env.example. Report variables that are used but not defined for an
environment, defined but unused, and secret-looking names with a public prefix.
Done means
Names were checked or changed as approved, no value appeared in output or files,
and the user knows which deployments still need a redeploy to see the change.