From 06aafbae4b569533ca5f79b181e2475b6f79baf3 Mon Sep 17 00:00:00 2001 From: Miranda Limonczenko Date: Fri, 18 Sep 2026 16:02:48 -0700 Subject: [PATCH] docs(functions): give local secrets a procedure that produces a working key (#50420) The Managing secrets guide is the only place that tells you where a local secret has to sit for the Edge Function runtime to load it. Neither the supabase agent skill nor Supacademy covers it. The page named supabase/functions/.env once, in prose, and never had the reader create it. The eval in supabase/evals#285 reproduces what that produces: across three runs on codex-gpt-5.6-luna-no-skills, every run built a function reading its key from the environment, started the stack, and answered missing_api_key. Two of the three wrote supabase/functions/.env.example and stopped, which is a template with the variable name in it rather than the file the runtime reads. Local secrets is now a five-step procedure that creates the file with a working value, ignores it, creates the function that reads the key, starts the stack, and calls the function to confirm. That last step is the one that tells the reader whether it worked. The function is created before the stack starts, so a reader following the steps literally from a fresh project has something to call. The gitignore instruction moved out of its admonition and into step 2, carrying its consequence with it. It's an instruction the reader has to follow, so it belongs in the procedure rather than beside it. Recovery for a variable the function can't see gets its own section rather than trailing the procedure. "I set it and the function cannot read it" is the most repeated shape in the feedback on this page, and as loose sentences mid-section it had no entry in the table of contents. --- .../docs/content/guides/functions/secrets.mdx | 68 ++++++++++++++++--- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/apps/docs/content/guides/functions/secrets.mdx b/apps/docs/content/guides/functions/secrets.mdx index ae432c437a2..769e686fb5f 100644 --- a/apps/docs/content/guides/functions/secrets.mdx +++ b/apps/docs/content/guides/functions/secrets.mdx @@ -9,23 +9,51 @@ Store API keys and other secrets where your Edge Functions can read them. Local ## Local secrets -In development, Edge Functions read secrets from `supabase/functions/.env`, which is automatically loaded on `supabase start`. To use a file you name yourself, such as `.env.local`, pass it to `supabase functions serve` with the `--env-file` option. +In development, Edge Functions read secrets from `supabase/functions/.env`, which is automatically loaded on `supabase start`. Create the file before you start the stack. -```bash -supabase functions serve --env-file .env.local -``` +1. Create `supabase/functions/.env` and add each secret with the value you want the function to read. A `.env.example` template isn't enough on its own, because the runtime reads the values rather than the variable names. - + ```bash + # supabase/functions/.env + STRIPE_SECRET_KEY=sk_test_... + ``` -A `.env` file committed to Git exposes every secret in it to anyone who can read the repository. Add the file to your `.gitignore` before you commit. +2. Add the file to your `.gitignore`, along with every other env file you create. A `.env` file committed to Git exposes every secret in it to anyone who can read the repository. - + ```bash + # .gitignore + supabase/functions/.env + .env.local + ``` -Serve the function locally: +3. Create the function, then replace its contents to read the secret and report whether it arrived. Return the result of the check rather than the value, so the response never carries the secret. -```bash -supabase functions serve hello-world -``` + ```bash + supabase functions new hello-world + ``` + + ```tsx + // supabase/functions/hello-world/index.ts + Deno.serve(() => { + const secretKey = Deno.env.get('STRIPE_SECRET_KEY') + return Response.json({ configured: Boolean(secretKey) }) + }) + ``` + +4. Start the local stack. + + ```bash + supabase start + ``` + +5. Call the function. A `configured` of `true` means the runtime handed it the secret. + + ```bash + curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/hello-world' \ + --header 'apikey: ' + ``` + +Your function now reads the secret from your local environment. --- @@ -62,6 +90,24 @@ const supabaseAdmin = createClient( --- +## When your function can't read a secret + +The local runtime loads `supabase/functions/.env` when the stack starts, so a function that returns nothing for a variable usually means the value never reached it. + +Restart the stack, or serve the function with the file passed explicitly: + +```bash +supabase functions serve hello-world --env-file supabase/functions/.env +``` + +To keep a separate file per environment, name your own and pass it the same way: + +```bash +supabase functions serve --env-file .env.local +``` + +--- + ## Production secrets Set secrets for your production Edge Functions in the Dashboard or with the CLI.