Skip to main content
aapp-hacks
← Back to articles
Author
app-hacks
Published
Updated
Reviewed

Cloudflare Pages / Security / Environment Variables

How to Separate Environment Variables and Secrets in Cloudflare Pages Functions

A practical way to decide which contact-form settings can reach the browser and which must remain as secrets.

The name “environment variable” alone does not mean a value is protected. A value referenced by Astro code sent to the client can be obtained from generated HTML or JavaScript. In contrast, a value used only by a Pages Function can be designed never to reach the browser. The contact form separates settings based on that difference in exposure.

Separate public and secret values by their purpose

PUBLIC_TURNSTILE_SITE_KEY is used by the browser to display the widget, so it is passed as a public variable. API tokens, recipient addresses, and the Turnstile secret are used only through the Function’s env. The configured variable names are listed below; their values are not included anywhere.

CLOUDFLARE_ACCOUNT_ID
CLOUDFLARE_EMAIL_API_TOKEN
CONTACT_RECIPIENT_EMAIL
CONTACT_FROM_EMAIL
PUBLIC_TURNSTILE_SITE_KEY
TURNSTILE_SECRET_KEY

The Env type in functions/api/contact.ts is the boundary that receives values from the runtime instead of fixing them in code. If a required variable is missing, the Function stops with a 503 rather than calling an external API with empty credentials. The PUBLIC prefix is not a secrecy mechanism; it is used only for a value intended to reach the browser.

Operate without exposing secret values

Do not put actual secret values in Git, Markdown, browser JavaScript, build output, error messages, or chat. Avoid broad diagnostic logging such as console.log(env), which can leak values while a problem is being investigated. When needed, check only whether a setting exists, not the value itself.

When a sending-service token or Turnstile secret must be rotated, replace it in Pages and do not keep the old value in history. Keep local test settings separate from production secrets, as described in docs/contact-form-setup.md.

Common pitfalls and how to check them

Check generated HTML and delivered JavaScript to ensure no secret was added to client-side code. Also ensure a failed Function response does not contain credentials or email addresses. The decision is based on who needs to read a value at runtime, rather than on the type of value.

A variable being registered in a settings screen does not make every use safe. A public variable can be used by a Function, but there is no reason to make it public if the browser does not need it. Conversely, putting a widget initialization value only in a secret prevents the browser from using it. Start with the required execution location and choose the narrowest exposure.

When investigating a contact-form problem, record variable names, whether settings exist, HTTP status, and reproduction steps—never copy values. That makes missing settings and authentication failures distinguishable without turning the investigation record into secret information.

The code examples in this article list variable names only; they do not show value formats or partial values. This treatment stays consistent throughout the article.