Environment Variables
envGo reads a single .env file at startup and keeps it in memory, hot-reloading
when the file changes. This page documents the file format, exactly how variables
are resolved, and how your HTML, JavaScript, and PHP can use them.
How the file is loaded
There is one env source: the file given by --env (default .env).
envgo --env .env # defaultenvgo --env /etc/myapp/.env # explicit pathenvgo -e ../shared/.env # short formIf the file does not exist, envGo still starts — it logs a warning and runs with
zero variables, then picks the file up as soon as it appears. It does not
merge multiple files, and it does not layer .env on top of the process
environment.
File format
# Comments start with #KEY=value
# Blank lines are ignored
# Optional `export` prefix is acceptedexport ANOTHER_KEY=another value with spaces
# Double or single quotes are strippedQUOTED="a value with # not a comment"SINGLE='another quoted value'
# Empty values are validEMPTY_VAR=
# Cross-platform line endings work (\r\n)Parsing rules, precisely
| Rule | Behaviour |
|---|---|
| Empty lines | Ignored |
# at start of a line | Comment, ignored |
export KEY= | The export prefix is stripped |
Missing = | Load fails with expected KEY=VALUE and the line number |
Empty key (=value) | Load fails with empty key |
| Whitespace | Trimmed from both key and value |
| Quotes | A matching pair of " or ' around the whole value is removed |
${VAR} expansion | Resolved against earlier keys in the same file first, then against the OS environment |
Value containing # | Not treated as a comment — only whole-line comments count |
| Internal spaces | Preserved (API_KEY=sk-123 456 keeps the spaces) |
Because expansion resolves earlier keys first, later keys can build on earlier ones:
HOST=api.example.comBASE_URL=https://${HOST}/v1# BASE_URL becomes https://api.example.com/v1If a name is not defined earlier in the file, the OS environment is consulted. So
TOKEN=${CI_TOKEN} picks up an exported CI_TOKEN — but only inside the value
of that one variable. There is no general precedence chain: .env values do not
inherit from or override the process environment.
Key naming rules
Two different rule sets apply, and they are not the same thing:
.env keys are not validated. Any non-empty key is accepted:
123KEY=worksMY-KEY=works_dotworks=worksThese all load. envGo does not reject them. However, a key containing a character
outside [A-Za-z0-9_] cannot be referenced from a {NAME} placeholder, because
the placeholder pattern is stricter:
Placeholder names must match [A-Za-z_][A-Za-z0-9_]*.
Key in .env | Usable as {…} placeholder? |
|---|---|
MY_KEY | Yes |
_private | Yes |
KEY2 | Yes |
123KEY | No — starts with a digit |
MY-KEY | No — contains a hyphen |
MY KEY | No — contains a space |
Keys are case-sensitive: MY_KEY and my_key are two different variables.
.env vs .env.example
.env holds real values and must never be committed. .env.example documents
which keys are required and is safe to commit — use obvious placeholders:
OPENAI_API_KEY=your-key-hereDATABASE_URL=postgres://user:pass@localhost:5432/dbSECRET_KEY=change-meOPENAI_API_KEY=sk-live-abc123DATABASE_URL=postgres://app:realpassword@db.internal:5432/appSECRET_KEY=9f3c1d7a...At startup envGo compares only the key names of the two files and warns about
mismatches in either direction — it never compares values. Keys MODE_PUBLIC, CONFIG, HOST, PORT are ignored for this check and never treated as secrets in logs.
Using variables in HTML
<!DOCTYPE html><html> <head><title>My App</title></head> <body> <!-- envGo reports whether these keys exist --> <div id="MY_SECRET"></div> <div id="OPENAI_API_KEY"></div>
<script src="/__env.js"></script> </body></html>/__env.js scans every element with an id, fetches the list of variable names,
and for each element either shows ✓ NAME — Success (value hidden) or
✗ NAME — ID not match Key in .env. When something does not match it prepends a
red banner suggesting the closest name (Levenshtein distance, threshold 3).
Using variables in JavaScript
Only booleans ever reach JavaScript:
// Set to true when the id matched a variable, otherwise absentconsole.log(window.EnvLoaded.MY_SECRET); // trueconsole.log(window.EnvLoaded.NOPE); // undefinedThe value itself is never available — not in memory, not on the network, not in
localStorage. To actually use a secret you must go through /proxy (local
mode) or /api/<name> (public mode).
Using variables in PHP
When PHP is installed, envGo executes .php files server-side with every .env
variable added to the process environment.
<?php// Any of these three access styles work$secret = getenv("MY_SECRET");$secret = $_ENV["MY_SECRET"];$secret = $_SERVER["MY_SECRET"];
if ($secret) { echo "<p>Secret configured ✓</p>";} else { echo "<p>Error: Secret not configured</p>";}?>Below is an example — the names are illustrative, not built into envGo:
<?php// Example: validate without revealing$apiKey = getenv("OPENAI_API_KEY");if ($apiKey) { // Use the key server-side. Never echo its value.}?>Patterns that trigger a warning
envGo scans the PHP source. Echoing a secret triggers a red security banner:
<?phpecho getenv("MY_SECRET"); // DANGEROUSprint getenv("API_KEY"); // DANGEROUSvar_dump(getenv("SECRET")); // DANGEROUS?>Typos are detected
Referencing a key that is not in .env triggers a red banner naming the closest
match:
<?php $secret = getenv("MY_SECRETS"); // typo ?>envGo — PHP env typo: undefined variable MY_SECRETS not found in .env.Did you mean 'MY_SECRET'?See PHP Support for the installed-interpreter lookup order and the caveat about what happens when PHP is unavailable.
Using variables through the proxy
Local mode
// 1. Get the session tokenconst token = await fetch("/__envgo_token").then((r) => r.text());
// 2. Send the request with placeholdersconst res = await fetch("/proxy", { method: "POST", headers: { "Content-Type": "application/json", "X-EnvGo-Token": token, }, body: JSON.stringify({ target_url: "https://api.openai.com/v1/chat/completions", method: "POST", headers: { Authorization: "Bearer {OPENAI_API_KEY}" }, body: { model: "gpt-4", messages: [{ role: "user", content: "Hello" }] }, }),});The host must be in --allow, and every referenced variable must exist in
.env.
Public mode
// No token needed — the target is fixed in the routes configconst res = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: "gpt-4", messages: [{ role: "user", content: "Hello" }] }),});The route decides which variables may be injected. See
Configuration for the vars allow-set.
Hot reload
Edit .env while envGo runs and the change is picked up within roughly 1.5
seconds — there is no restart and no file watcher dependency:
# Terminal 1envgo run dev
# Terminal 2echo "NEW_API_KEY=value" >> .env[envGo] hot-reloaded 6 variables from .envReload is triggered by comparing the file’s size and modification time. Writing a file with identical size and mtime will not trigger a reload. A reload replaces the entire variable map, so removing a key really does remove it.
Debugging
# Verbose logging (flag after the subcommand)envgo run dev --debug[envGo] loaded 5 variables from .envThe dashboard at /__envgo_dashboard lists variable names and recent request
metadata — never values:
envgo --dashboard --dir . --env .env --allow api.openai.comBest practices
- Never commit
.env— keep it in.gitignore - Keep
.envout of the served directory so it can never be static-served - Document required keys in
.env.examplewith placeholder values - Use descriptive names —
OPENAI_API_KEY, notKEY1 - Rotate secrets periodically, and immediately if one is ever echoed or committed
- Give each route the minimum — list only the variables it needs in
vars
Next steps
- How It Works — the request lifecycle
- Local Mode —
/proxyin depth - Public Mode — the
/api/<name>gateway