Skip to content
PlatformTypeScript for Edge Functions on Deno; any language for clients

Supabase environment variables: which keys are public

Supabase publishable keys can go in the browser, secret keys bypass Row Level Security and must not. Edge Function secrets, legacy keys and Crunu explained.

SupabaseAt a glance

Env file that wins
supabase/functions/.env

The only .env file it reads. See the load order

Public prefix
None

No names are copied into browser code by a prefix.

Values applied
At runtime

Values are read from the environment when the app runs.

Built-in secret store
Edge Function secrets, set with supabase secrets set or in the dashboard

supabase.com/docs/guides/functions/secrets

Crunu command
crunu pull --env backend/development --root .RecipeCrunu has no dedicated integration here: this is the general setup.

Common pitfalls

  1. A secret key bypasses every Row Level Security policy. It must never be in browser code, a shipped app or source control.
  2. Legacy anon and service_role keys are being deprecated by the end of 2026, and creating new keys does not revoke them.
  3. Locally, Edge Functions read supabase/functions/.env. A variable that exists only in the project's root .env never reaches them.
  4. Custom secret names can't start with SUPABASE_, which is reserved for the default secrets.
  5. supabase secrets set --env-file pushes every line of the file to the remote project, not only the ones you meant.
On this page

A Supabase project gives your app two kinds of keys, and they belong in different environment variables. The publishable key, sb_publishable_…, is meant for browsers and apps: anyone can read it, and it reaches only what your Row Level Security policies allow, so it can go into a NEXT_PUBLIC_ or VITE_ variable. The secret key, sb_secret_…, bypasses every policy and must live only in server-side variables. Edge Functions get their own values from Edge Function secrets, set with supabase secrets set, plus default secrets like SUPABASE_URL, and read them with Deno.env.get() without a redeploy. Locally they read supabase/functions/.env. These facts come from Supabase's API keys guide and Edge Function secrets guide, checked October 30, 2026.

The keys and where they may go

KeyLooks likeSafe in a browser?Notes
Publishable keysb_publishable_…yeslimited by Row Level Security
Secret keysb_secret_…neverbypasses all RLS policies
Legacy anon keya long JWT starting with eyJyes, like publishabledeprecated by the end of 2026
Legacy service_role keya long JWT starting with eyJneverdeprecated by the end of 2026

The secret key's power comes from Postgres itself: requests with it run as service_role, which has the BYPASSRLS attribute, so no policy ever applies. Supabase adds a guard for the new secret keys: they don't work in a browser, because Supabase checks the User-Agent header and answers 401. The guide is honest that this isn't complete protection, since someone holding the key can use it from any other tool.

Supabase's advice for secret keys is specific: use a separate secret key for each backend component, so that if one leaks you rotate only that one, and rotate a leaked key as soon as you've fixed the cause. A project that doesn't need a secret key doesn't have to have one.

Moving off the legacy keys

Supabase is deprecating anon and service_role by the end of 2026. Creating publishable and secret keys doesn't revoke the legacy ones. Both systems work at the same time, and the legacy keys stay valid until you disable them in Settings > API Keys in the dashboard. That's convenient for migration and a trap afterwards: a long-lived service_role JWT that leaked years ago keeps working until someone switches the legacy keys off.

A migration usually goes in this order: add the new keys, switch clients to the publishable key, switch servers and functions to secret keys, check that nothing still sends a key starting with eyJ, then disable the legacy keys.

Variable names in your app

For a web app, Supabase's Next.js quickstart uses two public variables in .env.local: NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY. Both are fine to inline into browser code. A server-side secret key goes into a variable without the public prefix, read only by server code. The framework's own rules decide the rest: see Next.js, Vite or SvelteKit for which prefix publishes a value.

Edge Function secrets

Every Edge Function receives default secrets: SUPABASE_URL, SUPABASE_DB_URL, SUPABASE_PUBLISHABLE_KEYS and SUPABASE_SECRET_KEYS, the last two as JSON dictionaries keyed by key name that you parse with JSON.parse, plus SUPABASE_JWKS and the legacy SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY. Your own secrets can't start with SUPABASE_.

WhereHow values get there
local stacksupabase/functions/.env, loaded on supabase start; other files only with supabase functions serve --env-file
hosted projectsupabase secrets set NAME=value, supabase secrets set --env-file FILE, or the dashboard's Edge Function Secrets page

Creating or deleting a production secret requires the Owner or Administrator role, and functions see new values immediately. One detail trips people: a root .env serves config.toml, but a variable a function needs must also be in supabase/functions/.env.

Where Crunu fits with Supabase

Edge Functions run on Supabase's runtime, so there's no place to run crunu inside them, and Crunu has no built-in sync into Edge Function secrets. Crunu fits everywhere around them.

Your app and your own servers. Keep the publishable key, the project URL and any server-side secret key in Crunu environments, and start the app with crunu run, for example crunu run --env web-app/development -- npm run dev. A developer with access to development never receives a production secret key, because each Crunu environment has its own encryption key.

Local Edge Functions. The local stack reads a file, so let Crunu write that file. Every file in a Crunu environment has a path, and crunu pull writes it there. With --root, the path is taken relative to a directory you choose instead of your home directory.

  1. Keep the function secrets for local work in a Crunu environment such as backend/development, in an env file whose path is supabase/functions/.env, with names that don't start with SUPABASE_.
  2. From the project root, run crunu pull --env backend/development --root . to write that file with mode 0600.
  3. Add supabase/functions/.env and supabase/functions/.env.prev to .gitignore, then run supabase start or supabase functions serve as usual.
  4. When a value changes in Crunu, run the same crunu pull again.

The .prev file is the previous version, which crunu pull keeps next to each file it replaces, so it holds secrets too. Run with --dry-run first to see exactly which paths will be written. This setup puts plaintext on disk, which crunu run normally avoids. It's the honest cost of a tool that only reads a file, and it should hold development values only. Don't do the same with production secrets on a laptop.

Production secrets. Push them from a trusted pipeline rather than from laptops. Because supabase secrets set --env-file sends every line of the file, keep the function secrets in a Crunu environment of their own, such as backend/functions-production. In the pipeline step, pull it into a temporary directory with --root, pass the written file to supabase secrets set --env-file, and delete the directory in the same step. Setting values one by one as command arguments avoids the file but puts them in the process list for the duration of the command, so prefer the file on a runner you control.

Frequently asked questions

Something else? Write to support@crunu.com

Is it safe to put the Supabase publishable key in NEXT_PUBLIC_ or VITE_ variables?

Yes, that's what it's for. Supabase says the publishable key is safe to expose, and anyone holding it can reach only what your Row Level Security policies allow. The secret key is the one that must stay on servers.

Do Edge Functions need a redeploy after supabase secrets set?

No. Supabase says functions read a new secret immediately, so you don't need to redeploy.

What happens to my legacy anon and service_role keys?

Supabase is deprecating them by the end of 2026. They keep working alongside the new keys until you disable them in Settings, API Keys, so plan to move clients to publishable and secret keys and then turn the legacy ones off.

Can crunu run inside a Supabase Edge Function?

No. Edge Functions run on Supabase's own runtime, and their values come from Edge Function secrets. Crunu fits around them: local development, your own servers, and the pipeline that sets the secrets.

Sources

Every source this page relies on, with the date we last checked it.

  1. Supabase Docs: Understanding API keys · checked October 30, 2026
  2. Supabase Docs: Edge Function secrets · checked October 30, 2026
  3. Supabase Docs: Use Supabase with Next.js · checked October 30, 2026

Keep your .env values in one place

Free during early access. Values are encrypted on your devices, each environment has its own key, and crunu run hands them to your app without writing a file.