Penv Cloud
Start free
Docs
Guidesince 1.0.0-alpha.3

Typed access for your language

Generate a typed env file from your .env.schema in TypeScript, Python, Go, Rust, Java, PHP or C#, keep its settings in .penv/config.toml, and check it in CI.

penv gen          # list targets: source, output file, options, detected directories
penv gen ts
penv gen go --out internal/env/env.go

penv gen writes generated code into your repository; your app imports nothing from penv at runtime. Data types come from @env-spec; this page covers only how penv renders them.

Targets

TargetDetected byImport line penv printsCompile check
tspackage.json, tsconfig.jsonimport { env } from "<path>"tsc --strict
pypyproject.toml, requirements.txt, setup.py, Pipfilefrom <module> import envpy_compile
gogo.modimport "<module>/env"gofmt -l -e
rustCargo.tomlmod env; use env::Env;none
javapom.xml, build.gradle[.kts]import config.Env;none
phpcomposer.jsonuse App\Env;php -l
csharp*.csproj, *.slnusing App;none

Detection searches three levels deep, skipping node_modules, dist, build, target and dot-directories. penv edits no build config; you paste the import line. The ts line follows tsconfig.json extends and uses a paths alias when one reaches the file.

Type mapping

Base typetspygorustjavaphpcsharp
string, url, emailstringstrstringStringStringstringstring
numbernumberfloatfloat64f64doublefloatdouble
number(isInt=true)numberintint64i64longintlong
portnumberintintu16intintint
booleanbooleanboolboolboolbooleanboolbool
enum(a,b)"a" | "b"Literal[...]stringStringStringstringstring

An optional key with no default is T | undefined (ts), T | None (py), a pointer (go) or Option<T> (rust).

Secrets in generated code

TargetSensitive string, url or email key
go, rust, java, php, csharpType Secret, which prints [redacted]. Value(), expose() or Expose() returns it
tsRead by computed name, so no bundler inlines it. With mask = true (default), console output and Response bodies are masked in Node, Bun, Deno, edge and Workers
pystr; SecretStr with pydantic = true

ts exports one env for server and client code. A @public key is read by its literal name so your bundler can inline it. The other targets expose Env.load() (Load() in go and C#), whose error names each problem and never a value.

Output path

Only --out or output in [targets.<name>] sets the path. At a terminal with neither, penv lists detected directories:

#  PATH
1  apps/web/src/env.ts
2  apps/api/src/env.ts

where should env.ts go? [apps/web/src/env.ts] (Enter, number, path, none):

Enter takes the first suggestion, a number picks one, a path is used relative to your repository root, and none skips the target. A path outside the repository is refused with output_outside_repo.

With no terminal and no --out, penv writes nothing for that target, reports it skipped, and exits 3. Pass --out <PATH> in your scripts and pipelines.

Settings

.penv/config.toml
[targets.ts]
output = "apps/web/src/env.ts"

[targets.ts.options]
key_case = "camel"
mask = true
runtime = "node"

penv writes the section after every answer, defaults included, and never asks again. Commit .penv/config.toml. A .penv/targets/<name>/ folder from an earlier release migrates on the next gen: fields into the section, env.tmpl to .penv/<name>.tmpl.

TargetOptionValuesDefault
tskey_caseupper, camelupper
tsruntimenode, vite, deno, workersnode; suggested from vite.config.*, wrangler.*, deno.json
tsmasktrue, falsetrue
pypydanticfalse, truefalse; suggested when pyproject.toml lists pydantic
go, javapackageanyenv, config
php, csharpnamespaceanyApp

penv gen <target> --options prints them. penv prompts only when a suggestion differs from the default; with no terminal it takes the default.

Custom target

[targets.<name>] plus .penv/<name>.tmpl overrides a built-in target or adds a language. Lookup order: your repository, then ~/.penv/targets/<name>/ (target.toml, env.tmpl), then built-in. A location missing a file inherits it from the next. A template receives only penv schema --json: no values, no network.

Procedure

Run penv gen <target> and choose the output path.

Paste the printed import line into your settings module.

Commit .penv/config.toml with the generated file.

Add penv gen <target> --check to your CI. It diffs against the file on disk, runs the compile check when the tool is installed, never prompts, and exits 0 for a skipped target.