varsvars

Why vars?

Your .env files are a liability. Here's why vars exists.

Stop managing secrets like it's 2015

Your team shares secrets over Slack. Your .env.production lives on three laptops — two are outdated. Someone commits .env by accident and you're rotating every key at midnight. Your CI pulls secrets from a dashboard you haven't audited in months. Meanwhile, every AI coding assistant and IDE extension on your machine can read your plaintext secrets.

This isn't a workflow problem. It's a compounding risk.

What if your config file was safe to commit?

vars replaces scattered .env files with a single config file that's encrypted, typed, and version-controlled. Here's what you actually work with:

# config.vars — what you edit

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://admin@staging.db.internal:5432/myapp"
  prod    = "postgres://admin@prod.db.internal:5432/myapp"
}

API_KEY : z.string().min(32) {
  dev     = "dev_key_a1b2c3d4e5f6g7h8i9j0k1l2m3"
  staging = "stg_key_m3l2k1j0i9h8g7f6e5d4c3b2a1"
  prod    = "prod_key_x9y8w7v6u5t4s3r2q1p0o9n8m7"
} (description = "Primary API key", expires = 2026-09-01)

When you commit, secret values are encrypted automatically. Public values (like APP_NAME and PORT) stay readable — only secrets get locked:

# config.vars — what git sees

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...:g7h8i9...
  prod = enc:v2:aes256gcm-det:e8d1f0...:k5l6m7...:n8o9p0...
}

The encryption key is locked behind a PIN that only your team knows. AI agents, IDE extensions, and anything else with file access can see the variable names and schemas — but not the secret values.

What you get

Type safety. Each variable has a schema — z.string().url(), z.number().min(1024), z.enum(["debug", "info", "warn"]). These are Zod expressions, a popular TypeScript validation library. If someone puts a typo in the database URL, vars catches it at build time, not after it crashes in prod.

All environments in one file. Dev, staging, prod — defined once, in one place. No more .env.local + .env.staging + .env.production drifting apart.

Team-friendly. New teammate clones the repo, runs vars show, enters the PIN. They have every secret for every environment. No Slack DMs. No 1Password vaults. No Doppler subscription.

AI-safe. The only way to decrypt is entering the PIN — a step AI coding agents can't complete. Your secrets stay locked even when Copilot, Cursor, or Claude has access to your files.

Generated types. vars generates a TypeScript file with full autocomplete:

import { vars } from '#vars'

vars.APP_NAME              // string — public, plain value
vars.PORT                  // number — public, auto-coerced
vars.DATABASE_URL          // Redacted<string> — secret, safe to log
vars.DATABASE_URL.unwrap() // actual value — explicit opt-in

Public variables are plain types. Secret variables are wrapped in Redacted — you can pass them around safely, and .unwrap() when you actually need the value. No accidental logging.

vars is for you if...

  • You're a team of any size tired of Slack-based secret sharing and .env sprawl
  • You want type-safe config without a SaaS dependency
  • You run a monorepo or multi-service setup and need shared config with per-service overrides
  • You deploy to multiple regions and need conditional config without duplicating files
  • You want AI agents locked out of your production secrets
  • You use TypeScript and want config that integrates with your type system

Ready?

Get started in under 5 minutes →