Documentation

dottyenv validates .env files against a schema, dottyenv.toml, that you commit to the repo in place of .env.example. It holds no secrets, only their shape.

Getting started

Point init at an existing .env or .env.example. It writes a schema and fills in patterns and source URLs for providers it recognises.

$ dottyenv init      # writes dottyenv.toml
$ dottyenv check     # validate .env against it

Review the generated file before committing it. Anything init couldn't infer is left as a # TODO comment rather than guessed at. Every variable present in the source file is marked required, so loosen anything that is actually optional. init won't overwrite an existing schema unless you pass --force.

Schema

One file, one table per variable. The field set is small on purpose.

[meta]
version = 1

[vars.STRIPE_SECRET_KEY]
required    = true
pattern     = "^sk_(test|live)_"
source      = "https://dashboard.stripe.com/apikeys"
description = "Server-side Stripe key. Use sk_test_ locally."
secret      = true

[vars.LOG_LEVEL]
required = false
default  = "info"
one_of   = ["debug", "info", "warn", "error"]
FieldMeaning
requiredThe variable must be present in the env file.
patternA regex the value must match. Match the prefix; don't end-anchor on length.
one_ofThe value must be one of these strings.
defaultThe value used when the variable is absent.
sourceWhere a human goes to get the value. Printed when the variable is missing.
descriptionWhat the variable is for.
secretNever print the value. Problems are reported by length only.

Loose beats precise

A false rejection does more damage than a missed check: one wrong โœ— and people stop trusting the output. Write patterns that survive a vendor changing key length.

pattern = "^sk_(test|live)_"                  # good: survives length changes
pattern = "^sk_(test|live)_[A-Za-z0-9]{24}$"  # bad: breaks when Stripe changes length

Commands

dottyenv init

Generate dottyenv.toml from an existing env file. Matches values by prefix against the built-in provider catalog, falling back to the variable name when the value is a placeholder.

dottyenv check

Validate the env file against the schema. This is the core command and the one to run in CI.

$ dottyenv check
โœ— 3 problems in .env

  INVALID   DATABASE_URL
            expected ^postgres(ql)?://, got "<redacted, 47 chars>"
            PostgreSQL connection string

  INVALID   LOG_LEVEL
            expected one of [debug, info, warn, error], got "verbose"

  MISSING   STRIPE_SECRET_KEY
            Stripe secret key. Use sk_test_ locally.
            โ†’ https://dashboard.stripe.com/apikeys

  4 of 6 required variables OK

dottyenv scan

Cross-reference the schema against the code. It answers two questions:

$ dottyenv scan
used but not declared
  SENDGRID_API_KEY                 src/server.ts:2
  SESSION_SECRET                   app.rb:1

declared but never mentioned
  LEGACY_UNUSED_FLAG

note: 1 file accesses the environment dynamically, so this list may be
      incomplete: src/server.ts

scan always exits 0. It is advisory; check is the gate.

dottyenv list

Print each declared variable, whether it is set, and whether it is required.

Global flags

FlagEffect
-f, --file <PATH>Env file to read. Default .env.
-s, --schema <PATH>Schema to validate against. Default dottyenv.toml.
-e, --env <NAME>Environment overlay to apply. Default: inferred from --file.
--jsonMachine-readable JSON on stdout.
-q, --quietSuppress non-error output.

Environments

SQLite locally and Postgres in production is one variable with two shapes. Declare it once and override per environment:

[vars.DATABASE_URL]
required    = true
description = "Primary database"
secret      = true

[envs.development.vars.DATABASE_URL]
pattern = '^(sqlite:|file:)'

[envs.production.vars.DATABASE_URL]
pattern = '^postgres(ql)?://'

The environment comes from the filename, so .env.production uses the production overlay. --env overrides it:

$ dottyenv check --file .env.production               # infers production
$ dottyenv check --file .env.staging --env production # staging against prod rules

Overlays may override required, pattern, one_of and default. They may not override description, source or secret, and trying to is a load error. A variable means the same thing everywhere.

A misspelled --env is an error rather than a silent fallback, since a fallback would quietly downgrade a production gate. An unrecognised filename does fall back quietly, because .env.local is a common name that isn't an environment.

Exit codes

CodeMeaning
0All required variables present and valid
1Validation failure: missing or malformed variables
2Usage error: bad flags or arguments
3Config error: schema missing or unparseable

Output

Data goes to stdout and diagnostics to stderr, so dottyenv composes in pipes. Values of variables marked secret = true are never printed; output reports presence and validity only. Non-secret values are shown, because seeing "verbose" is how you fix LOG_LEVEL.

Shell completions

$ dottyenv completions zsh  > ~/.zfunc/_dottyenv
$ dottyenv completions bash > /etc/bash_completion.d/dottyenv
$ dottyenv completions fish > ~/.config/fish/completions/dottyenv.fish

powershell and elvish are also supported.