Getting Started
From zero to encrypted, typed env vars in under 5 minutes.
1. Initialize
npx dotvars initYou'll set a PIN to protect your encryption key. After that, you get:
| File | What it is | Committed to git? |
|---|---|---|
config.vars | Your variables — encrypted at rest | Yes |
.vars/key | Encryption key (locked behind your PIN) | No (gitignored) |
config.generated.ts | TypeScript types (auto-generated) | Yes |
config.local.vars | Per-developer overrides (optional) | No (gitignored) |
vars also updates your .gitignore and installs a pre-commit hook that prevents committing decrypted secrets or local override files.
Already have a .env file? vars will detect it and offer to migrate your variables automatically.
2. Decrypt and edit
npx dotvars show config.varsEnter your PIN. The file decrypts in-place — open it in your editor and add variables:
env(dev, staging, prod)
public APP_NAME = "my-app"
public PORT : z.number().min(1024).max(65535) = 3000
DATABASE_URL : z.string().url() {
dev = "postgres://localhost:5432/myapp"
staging = "postgres://staging.db:5432/myapp"
prod = "postgres://admin@prod.db.internal:5432/myapp"
}What's the syntax?
publicmeans the value is not a secret — it stays plaintext and generates a plain TypeScript type- The part after
:is a Zod schema — think of it as a type annotation that also validates.z.string().url()means "this must be a valid URL string." Common ones:z.string(),z.number(),z.boolean(),z.enum(["a", "b", "c"]) - The
{ dev = ..., prod = ... }block defines per-environment values - No schema?
APP_NAME = "my-app"infersz.string()automatically
3. Encrypt and commit
npx dotvars hideEnter your PIN. All secret values get encrypted. Public values stay readable:
public APP_NAME = "my-app"
public PORT : z.number().min(1024).max(65535) = 3000
DATABASE_URL : z.string().url() {
dev = enc:v2:aes256gcm-det:7f3a9b...:d4e5f6...:a1b2c3...
staging = enc:v2:aes256gcm-det:e8d1f0...:k5l6m7...:n8o9p0...
prod = enc:v2:aes256gcm-det:x1y2z3...:a4b5c6...:d7e8f9...
}Now commit:
git add config.vars config.generated.ts
git commit -m "add database config"The pre-commit hook will block the commit if any secrets are still decrypted — so you can't accidentally push plaintext.
4. Run your app
npx dotvars run --env dev -- node server.jsvars decrypts secrets in memory (nothing written to disk) and injects them as environment variables before handing off to your process. Works with any command:
{
"scripts": {
"dev": "vars run --env dev -- next dev",
"build": "vars run --env prod -- next build",
"start": "vars run --env prod -- node dist/server.js"
}
}5. Use the generated types
npx dotvars gen config.varsThis generates config.generated.ts with typed exports. Import them anywhere:
import { vars } from '#vars'
// Public vars — plain types, no unwrap needed
console.log(`Starting ${vars.APP_NAME} on port ${vars.PORT}`)
// Secret vars — Redacted<string>, explicit unwrap
const pool = createPool(vars.DATABASE_URL.unwrap())
// Type errors caught at compile time
vars.TYPO_VAR // TS error: Property 'TYPO_VAR' does not existThe #vars import alias is set up by vars init in your package.json. The generated file has no runtime dependencies on vars — just Zod for validation.
Onboarding a teammate
When someone new joins:
- They clone the repo (which has the encrypted
config.vars) - You share the PIN with them (verbally, or via a secure channel)
- They run
vars show config.vars, enter the PIN — done
No .env file to find. No 1Password vault to configure. No Doppler invite to send.
If they need different local values (e.g., a different database host), they create a config.local.vars — see Local Overrides.
CI/CD Setup
In CI/CD pipelines where you can't enter a PIN interactively, use the VARS_KEY environment variable:
# Get your base64-encoded master key (run locally, once)
vars key export
# Set it in your CI platform (GitHub Actions, GitLab CI, etc.)
# Then use vars normally — no PIN prompt needed
VARS_KEY=<base64-key> vars run --env prod -- npm run build
VARS_KEY=<base64-key> vars export --env prod > .env.prod# GitHub Actions example
env:
VARS_KEY: ${{ secrets.VARS_KEY }}
steps:
- run: vars run --env prod -- npm run buildWhat's next?
- File format reference — full syntax guide (groups, conditionals, check blocks, interpolation)
- CLI reference — all commands
- Framework integration — Next.js, Vite, Astro, and more
- Encryption & security — how the crypto works, threat model