Offline static analysis for environment variables. Scan your project, find missing definitions, unused variables, and naming issues — before they hit production.
$ envdoctor scan .
EnvDoctor Scan Report
════════════════════
Project: .
Scanned: 2026-07-23 07:11:04 UTC
Duration: 0.32s
Summary
───────
Total variables: 47
Defined in .env: 12
Used in code: 84
Errors: 0
Warnings: 5
- Multi-language — JavaScript, TypeScript, Python, Rust, Go, Ruby via tree-sitter AST parsing
- Smart .env parsing —
.env,.env.example,.env.local,.env.production, and more - 4 analysis rules out of the box:
- R001 Missing Definition — variables used in code but not in any
.envfile - R002 Unused Variable — variables defined in
.envbut never referenced in code - R003 Naming Convention — enforce
UPPER_SNAKE_CASE(configurable regex) - R004 Undocumented Variable — variables in
.envbut missing from.env.example
- R001 Missing Definition — variables used in code but not in any
- Multiple output formats — terminal text, JSON, HTML report
- Configurable —
envdoctor.tomlfor rules, exclusions, include/exclude globs - Offline — no network, no telemetry, no accounts
cargo install envdoctorOr build from source:
git clone https://github.com/mangodxd/envdoctor
cd envdoctor
cargo build --release# Scan current directory
envdoctor scan
# Check with exit code (CI-friendly)
envdoctor check
# Explain a specific variable
envdoctor explain DATABASE_URL
# Export HTML report
envdoctor export --format html -o report.html
# Export JSON
envdoctor export --format json -o report.json
# Generate config file
envdoctor init
# Validate config
envdoctor doctor| Command | Description |
|---|---|
scan |
Scan workspace and print results |
check |
Validate and exit with code 0 (clean) or 1 (issues found) |
explain <VAR> |
Show all usages, definitions, and issues for a variable |
export |
Export to HTML or JSON |
init |
Generate a default envdoctor.toml |
doctor |
Validate configuration and diagnose environment |
completions |
Generate shell completions (bash, zsh, fish, PowerShell) |
Create an envdoctor.toml in your project root:
[analysis]
naming_pattern = "^[A-Z][A-Z0-9_]*$"
naming_min_length = 2
strict_mode = false
[analysis.severity]
missing_definition = "warning"
unused_variable = "info"
naming_convention = "warning"
undocumented_var = "info"
[scan]
max_file_size = 1048576 # 1MB
include = ["src/**", "lib/**", "app/**"]
exclude = ["node_modules/**", "target/**", "dist/**", ".git/**"]Scanner → Adapter → IR → Analysis → Report
│ │ │ │ │
│ │ │ │ └─ Text / JSON / HTML
│ │ │ └─ R001-R004 rules
│ │ └─ Common intermediate representation
│ └─ Language-specific parsers (tree-sitter)
└─ File discovery with glob filtering
- Scanner discovers files, filters by extension and globs
- Adapters parse each language with tree-sitter AST (not regex)
- IR Builder normalizes everything into a common graph
- Analysis Engine runs rules against the IR
- Renderer outputs results as text, JSON, or HTML
- Payload CMS — 8,666 files, 119 variables, 844 usages
- Node.js / Express / Next.js projects
- Python / Django / FastAPI projects
- Rust / Go / Ruby projects
Variable is used in code but not defined in any .env file.
⚠ DATABASE_URL (warning): Variable 'DATABASE_URL' is used in code but not defined in .env
at src/config.ts:12:21
Variable is defined in .env but never referenced in code.
ℹ OLD_API_KEY (info): Variable 'OLD_API_KEY' is defined in .env but not used in code
at .env:5
Variable name doesn't match the configured pattern (default: UPPER_SNAKE_CASE).
⚠ api_key (warning): Variable 'api_key' does not match naming convention
Variable is in .env but missing from .env.example.
ℹ SECRET_KEY (info): Variable 'SECRET_KEY' is in .env but not documented in .env.example
| Language | Access Patterns |
|---|---|
| JavaScript | process.env.X, import.meta.env.X, require('dotenv').config() |
| TypeScript | Same as JavaScript |
| Python | os.environ['X'], os.getenv('X'), os.environ.get('X'), from dotenv import load_dotenv |
| Rust | std::env::var("X"), std::env::var_os("X"), env!("X"), dotenv::dotenv() |
| Go | os.Getenv("X"), os.Lookupenv("X"), os.Setenv("X"), os.Unsetenv("X") |
| Ruby | ENV['X'], ENV.fetch('X'), Dotenv.load |
MIT