All guidesVercel and env vars
On this page

Vercel error

Vercel

Environment Variables with a public framework prefix cannot use visibility: secret

By Ohad Krispin, founder of VibeRaven · LinkedIn

First decide what the value is. If the browser is meant to have it, such as the Supabase project URL or the publishable (legacy anon) key, keep the NEXT_PUBLIC_ or VITE_ name and save the variable as Config. If it is a secret, such as a Supabase secret or service_role key or a Stripe secret key, rename it without the prefix, keep it as a Secret, read it only in server code, and rotate it if it ever shipped under the public name. Vercel refuses Secret on these names because Next.js and Vite copy every prefixed value into the JavaScript bundle at build time, so a value hidden in the dashboard would still be readable by every visitor to the site. The rule came with the Config and Secret types Vercel announced on 2026-08-24.

Repository check

Check this in your repo

Find a Supabase service role key under a public name in your repo

For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. It reports a Supabase service role or secret key under a NEXT_PUBLIC_ or VITE_ name in .env.example as service_role_key_in_client_env, and a browser file that creates a Supabase client with that key as service_role_key_in_client_code. It reads Supabase keys only: in the same run on 2026-10-29, NEXT_PUBLIC_STRIPE_SECRET_KEY in .env.example gave no finding, so use the grep in the checklist for other secrets.

It is a repository check, not a live database test: it reads files, so it cannot see the type a variable has in your Vercel project. It is advice, not a gate, and it exits with code 1 when it finds a blocker.

npx -y viberaven@1.6.12 check

The error text, and what the Vercel CLI prints instead

Vercel does not publish the first two messages in its docs; they are quoted as developers and agents search them. The first uses the field names of the REST API, which takes "visibility": "secret" or "config" when you create a variable. The second is the hint next to the variable in the dashboard. The Vercel CLI checks the same rule before it calls the API, and its public source prints the third message (shown for a variable named NEXT_PUBLIC_STRIPE_SECRET_KEY) on vercel env add.

Environment Variables with a public framework prefix cannot use `visibility: secret`. Use `visibility: config` instead.

Remove the public framework prefix to keep this value private. Public prefixes expose values to the browser. If that's safe, change the variable to Config.

`NEXT_PUBLIC_` exposes this value to anyone visiting your site, so `NEXT_PUBLIC_STRIPE_SECRET_KEY` cannot be a Secret. To keep it private, rename the variable to `STRIPE_SECRET_KEY` and keep the Secret type. If the value is safe to expose, use `--type config`.

Which prefixes count as public

The Vercel CLI source lists the prefixes the API rejects for Secret variables: NEXT_PUBLIC_, VITE_, PUBLIC_, REACT_APP_, VUE_APP_, NUXT_PUBLIC_, GATSBY_, GRIDSOME_, STORYBOOK_, EXPO_PUBLIC_, NG_APP_ and REDWOOD_ENV_. Each is the name a framework uses to put a value into client code. Next.js says it inlines NEXT_PUBLIC_ values into the JavaScript sent to the browser during next build, and Vite says VITE_ values are bundled into client source at build time and should not hold API keys.

Where the error shows up

You choose Config or Secret under Type in the dashboard, with --type config or --type secret on vercel env add and vercel env update, and with the visibility field in the REST API and the @vercel/sdk createProjectEnv call. Vercel's CLI docs say a variable with a public framework prefix is stored as Config, while Production and Preview otherwise default to Secret. So in the CLI you usually meet the error only after passing --type secret or the older --sensitive flag. An agent that sends "visibility": "secret" for every variable through the API or the SDK meets it on the first NEXT_PUBLIC_ name.

An existing Sensitive variable with a NEXT_PUBLIC_ name

Variables saved with the old Sensitive toggle are now treated as Secrets, and Vercel says a saved Secret cannot be converted to Config in place. If the value is public, delete the variable and create it again as Config under the same name. If it is a secret, add it as a Secret under the name without the prefix, update the code, then remove the prefixed variable.

Checklist

How to fix it

  1. 01
    Find a Supabase service role key under a public name in your repo. For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. It reports a Supabase service role or secret key under a NEXT_PUBLIC_ or VITE_ name in .env.example as service_role_key_in_client_env, and a browser file that creates a Supabase client with that key as service_role_key_in_client_code. It reads Supabase keys only: in the same run on 2026-10-29, NEXT_PUBLIC_STRIPE_SECRET_KEY in .env.example gave no finding, so use the grep in the checklist for other secrets. It is a repository check, not a live database test: it reads files, so it cannot see the type a variable has in your Vercel project.
    npx -y viberaven@1.6.12 check
  2. 02
    List the public-prefixed names your code reads, and mark each value as public or secret:
    grep -rnoE "(NEXT_PUBLIC|VITE)_[A-Z0-9_]+" --include=*.ts --include=*.tsx --include=*.js --include=*.jsx --exclude-dir=node_modules . | sort -u
  3. 03
    For a value the browser is meant to have, such as the Supabase URL or publishable key, keep the name and save it as Config:
    vercel env add NEXT_PUBLIC_SUPABASE_URL production --type config
  4. 04
    For a secret, add it as a Secret under a name without the prefix, then remove the prefixed variable:
    vercel env add SUPABASE_SERVICE_ROLE_KEY production --type secret
    vercel env rm NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY production
  5. 05Change the code to read the new name only in server code: a Route Handler, a Server Action, a server component or an Edge Function. In the browser it now reads as undefined, which is the point; move the call that needs it behind a server route.
  6. 06If a variable saved earlier as Sensitive has a public name and a public value, delete it and create it again as Config. Vercel cannot convert a Secret to Config in place.
  7. 07Repeat the add for Preview (and Development if you use it), then redeploy. A deployment keeps the values it was built with, and Next.js and Vite inline public values during the build.
  8. 08If a secret ever deployed under a public name, treat it as leaked: create a new key with the provider, save it in Vercel, redeploy, check the new deployment works, then revoke the old key.

Related

Sources

Sources

FAQ

FAQ for builders before launch

Why can a public variable not be a Secret?

Secret only controls who can read the value in the Vercel dashboard. A NEXT_PUBLIC_ or VITE_ value is copied into the JavaScript bundle at build time, so anyone who loads the site can read it there. The Vercel CLI gives the same reason when it refuses: the prefix exposes the value to anyone visiting your site.

Is it fine to store the Supabase anon or publishable key as Config?

Yes. Supabase designs the publishable key, and the legacy anon key it replaces, to be used in the browser. What protects your data with that key is row level security on your tables, not the variable type.

I removed the prefix and now the value is undefined in the browser. What went wrong?

Nothing: without NEXT_PUBLIC_ or VITE_, the framework leaves the value out of the client bundle. Code that needs a secret has to run on the server, so move that call into a Route Handler, Server Action or Edge Function and call it from the page.

Should I use --type or --visibility in the Vercel CLI?

Use --type config or --type secret. The 2026-08-24 changelog used --visibility, and the current CLI docs list it as a deprecated alias of --type. --sensitive and --no-sensitive still work and map to Secret and Config.