varsvars

Getting Started

From zero to encrypted, typed env vars in under 5 minutes.

1. Initialize

npx dotvars init

You'll set a PIN to protect your encryption key. After that, you get:

FileWhat it isCommitted to git?
config.varsYour variables — encrypted at restYes
.vars/keyEncryption key (locked behind your PIN)No (gitignored)
config.generated.tsTypeScript types (auto-generated)Yes
config.local.varsPer-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.vars

Enter 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?

  • public means 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" infers z.string() automatically

3. Encrypt and commit

npx dotvars hide

Enter 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.js

vars 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.vars

This 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 exist

The #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:

  1. They clone the repo (which has the encrypted config.vars)
  2. You share the PIN with them (verbally, or via a secure channel)
  3. 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 build

What's next?