caarlos0/env
A simple, zero-dependencies library to parse environment variables into structs
Healthy across the board
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 6d ago
- ✓14 active contributors
- ✓Distributed ownership (top contributor 41% of recent commits)
Show all 6 evidence items →Show less
- ✓MIT licensed
- ✓CI configured
- ⚠No test directory detected
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/caarlos0/env)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/caarlos0/env on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: caarlos0/env
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/caarlos0/env shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across the board
- Last commit 6d ago
- 14 active contributors
- Distributed ownership (top contributor 41% of recent commits)
- MIT licensed
- CI configured
- ⚠ No test directory detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live caarlos0/env
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/caarlos0/env.
What it runs against: a local clone of caarlos0/env — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in caarlos0/env | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 36 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of caarlos0/env. If you don't
# have one yet, run these first:
#
# git clone https://github.com/caarlos0/env.git
# cd env
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of caarlos0/env and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "caarlos0/env(\\.git)?\\b" \\
&& ok "origin remote is caarlos0/env" \\
|| miss "origin remote is not caarlos0/env (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "env.go" \\
&& ok "env.go" \\
|| miss "missing critical file: env.go"
test -f "error.go" \\
&& ok "error.go" \\
|| miss "missing critical file: error.go"
test -f "env_tomap.go" \\
&& ok "env_tomap.go" \\
|| miss "missing critical file: env_tomap.go"
test -f "env_tomap_windows.go" \\
&& ok "env_tomap_windows.go" \\
|| miss "missing critical file: env_tomap_windows.go"
test -f "go.mod" \\
&& ok "go.mod" \\
|| miss "missing critical file: go.mod"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 36 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~6d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/caarlos0/env"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
A lightweight, zero-dependency Go library that unmarshals environment variables into struct fields via reflection and struct tags. It provides functions like Parse() and ParseAs() to populate typed config structs directly from os.Environ(), supporting 15+ built-in types (int, string, bool, time.Duration, url.URL, etc.) plus pointers, slices, maps, and custom parsers—eliminating manual string parsing and type conversion boilerplate. Flat, single-package structure: core parsing logic in env.go (the exported API), error types in error.go, platform-specific toMap logic split across env_tomap.go (Unix) and env_tomap_windows.go (Windows), with mirrored test files. No subdirectories or plugins—everything converges at the package level for minimal surface area.
👥Who it's for
Go backend developers and DevOps engineers building cloud services (notably Encore.dev users) who need a declarative, type-safe way to load 12-factor app configuration from environment variables into config structs without external dependencies.
🌱Maturity & risk
Production-ready and actively maintained. The v11 series is current, has extensive test coverage in env_test.go and env_tomap_test.go, runs CI/CD via GitHub Actions (build, lint, govulncheck, CodeQL), and the maintainer uses semantic versioning with retraction notes for accidental breaking changes (v11.0.1, v11.2.0, v11.3.0), indicating careful stewardship.
Very low risk. Zero dependencies (by design—see go.mod/go.sum), single-maintainer (caarlos0) but mature and widely adopted, no external service requirements. Only risk: the library has retracted three v11.x versions due to subtle breaking changes in nil pointer and slice behavior, so test your config parsing carefully when upgrading major versions.
Active areas of work
No specific active work visible in the file list, but the retraction history (v11.0.1, v11.2.0, v11.3.0) and presence of govulncheck/grype/CodeQL workflows indicate ongoing maintenance, security scanning, and stability focus. The library appears in a 'steady, well-supported' state rather than active feature development.
🚀Get running
git clone https://github.com/caarlos0/env.git
cd env
go mod download
make test
Daily commands:
make test # Run all tests
make lint # Run golangci-lint
make ci # Run full CI pipeline (lint, test, govulncheck, grype)
🗺️Map of the codebase
env.go— Core entry point containing Parse() and ParseAs() functions that implement the primary struct-to-env-var mapping logic; all env parsing flows through here.error.go— Defines error types and handling for the library; essential for understanding how parsing failures are reported to callers.env_tomap.go— Converts OS environment variables into a map for platform-agnostic parsing; handles the environment data transformation layer.env_tomap_windows.go— Windows-specific implementation of environment variable parsing; required for understanding platform-specific behavior.go.mod— Declares zero-dependencies policy and retracted versions; critical for understanding stability guarantees and breaking changes.env_test.go— Comprehensive test suite covering parsing behavior, tag options, and edge cases; documents expected behavior.
🛠️How to make changes
Add support for a new struct tag option
- Document the new tag option in README.md with example usage (
README.md) - Add parsing logic for the tag in env.go's field processor functions (
env.go) - Write test cases in env_test.go covering the new option with various input scenarios (
env_test.go) - Add example test in example_test.go demonstrating the new tag option (
example_test.go)
Add support for a new Go type
- Implement type conversion logic in env.go's field processor switch statement (
env.go) - Write comprehensive tests in env_test.go for zero-value, valid, and invalid inputs (
env_test.go) - Update README.md with a note about the newly supported type (
README.md)
Fix a cross-platform environment parsing bug
- Add failing test case to env_test.go or env_tomap_test.go (
env_test.go) - Fix logic in env_tomap.go for cross-platform handling (
env_tomap.go) - If Windows-specific, also fix env_tomap_windows.go (
env_tomap_windows.go) - Add Windows-specific test in env_tomap_windows_test.go if applicable (
env_tomap_windows_test.go)
🪤Traps & gotchas
- Unexported (lowercase) struct fields are silently ignored—no error, no warning. Always use exported (capitalized) field names. 2) Nil pointers are not auto-initialized by default in v11+; you must add the
inittag option or the field stays nil even if the env var is set. 3) v11.0.1, v11.2.0, and v11.3.0 are retracted due to breaking changes—always specifyv11versions >= v11.4.0 or explicitly test pointer/slice behavior. 4) Thefiletag reads a file path from the env var and loads its content; it does not set a default file path. 5) Maps require bothenvKeyValSeparator(default:) andenvSeparator(default,);FOO=key1:val1,key2:val2is the expected format.
🏗️Architecture
💡Concepts to learn
- Struct Tags and Reflection — This library relies entirely on Go's
reflectpackage to introspect struct fields and parseenv:,envDefault:,envPrefix:tags at runtime—understanding reflection is essential to modify parsing logic or add custom type support. - TextUnmarshaler Interface — The library uses
encoding.TextUnmarshaler(similar to JSON unmarshaling) to parse custom types; implementing this interface lets you add support for any type without modifying the core library. - Pointer Initialization and Nil Safety — The retracted v11.0.1 and v11.2.0 versions exposed subtle bugs in how nil pointers and nil slices are handled; the
inittag option now controls whether pointers are auto-allocated, requiring careful understanding of Go's pointer semantics. - Environment Variable Expansion — The
,expandtag option allows syntax likeFOO_${BAR}where${BAR}is replaced with another env var's value at parse time—a common pattern in cloud deployments and 12-factor apps. - 12-Factor Application Configuration — This library is built specifically for the 12-factor app principle that configuration (database URLs, API keys, timeouts) should live in environment variables, not code or files.
- Cross-Platform File Path Handling — The separate
env_tomap.go(Unix) andenv_tomap_windows.goimplementations handle OS-specific environment variable semantics (case sensitivity, path separators); understanding this split is critical for debugging platform-specific behavior. - Delimiter-Based Parsing (CSV-Style) — Slices and maps use
envSeparator(,by default) andenvKeyValSeparator(:by default) to split values; this is a simple but error-prone pattern that requires explicit testing for edge cases like escaped delimiters.
🔗Related repos
kelseyhightower/envconfig— Earlier, mature alternative in the same space; inspired many design patterns; pre-datesenvand influenced Go stdlib thinking on env parsing.spf13/viper— Broader config management library supporting env vars, files, and flags; heavier thanenvbut useful if you need multi-source config in the same app.caarlos0/gon— Same maintainer; complementary tool for Go binary notarization/signing; often used in pipelines withenvfor deploying Go services.google/go-github— Example ecosystem consumer: usesenvinternally for configuration in CI/CD and GitHub Actions contexts, showing real-world adoption patterns.caarlos0/goreleaser— Same maintainer's flagship release automation tool; commonly paired withenvfor handling CI/CD env vars and build config in Go projects.
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive tests for Windows-specific environment variable behavior in env_tomap_windows.go
The repo has Windows-specific implementation (env_tomap_windows.go and env_tomap_windows_test.go) but lacks cross-platform validation tests. Given the file exists with platform-specific logic, adding integration tests that verify Windows environment variable parsing behaves identically to Unix variants would ensure reliability across platforms and prevent regressions.
- [ ] Review env_tomap_windows.go to identify untested edge cases (e.g., case-insensitive environment variables on Windows)
- [ ] Add test cases to env_tomap_windows_test.go covering: mixed-case variable names, registry-sourced variables, and path separators
- [ ] Run tests locally on Windows or add a Windows CI runner to .github/workflows/build.yml to validate the changes
Add benchmarking tests for performance-critical parsing paths
The env.go file implements core parsing logic but has no benchmarks. Since this is a performance-sensitive utility library used by many projects (as shown by the 'Used and supported by' section), adding benchmarks would help identify regressions and provide baseline metrics for future optimizations.
- [ ] Create a new file env_bench_test.go with Benchmark* functions covering: struct with 10+ fields, nested struct parsing, and slice/map parsing
- [ ] Include benchmarks for both Parse() and ParseAsT generic variants mentioned in README
- [ ] Add benchmark results documentation to README.md to help users understand performance characteristics
Add specific documentation and tests for the init tag option introduced in v11.0.1
The go.mod file mentions v11.0.1 introduced an init tag option for auto-initializing nil pointers, but the README doesn't document this feature. This causes confusion for users and reduces discoverability of an important feature. Adding documented examples with tests would improve usability.
- [ ] Add a section to README.md under 'Usage' explaining the
inittag option with a code example showing nil pointer auto-initialization - [ ] Add test cases to env_test.go covering: init tag with nil pointer fields, init tag with already-allocated pointers, and init tag interactions with other options
- [ ] Verify the feature works consistently across different field types (structs, custom types, nested structs)
🌿Good first issues
- Add examples demonstrating the
inittag option behavior for nil pointers inexample_test.go, since the recent retraction of v11.0.1 and v11.2.0 left this feature underdocumented and a frequent source of confusion. - Write a Windows-specific integration test in
env_tomap_windows_test.gothat verifiesenvPrefixand nested struct parsing work identically to the Unix version (currently onlyenv_tomap_test.gohas deep coverage). - Extend
env_test.goto add test cases for customencoding.TextUnmarshalerimplementations (e.g., custom IP type), since the README lists it as supported but example coverage is minimal.
⭐Top contributors
Click to expand
Top contributors
- @caarlos0 — 41 commits
- @dependabot[bot] — 41 commits
- @astak16 — 3 commits
- @alexandear — 2 commits
- @dnovikoff — 2 commits
📝Recent commits
Click to expand
Recent commits
a72d89a— ci: update release config (caarlos0)6daeb06— fix: use Key instead of OwnKey in rawEnvVars to properly reference existing defaults in complex structs (#412) (AndrewChubatiuk)33faf6f— ci(deps): bump the actions group with 3 updates (#417) (dependabot[bot])1ff55e6— docs: adaptive starchart (#416) (alexandear)0cb635b— ci(deps): bump the actions group with 6 updates (#415) (dependabot[bot])cf4a968— ci(deps): bump the actions group with 3 updates (#411) (dependabot[bot])aaa4511— fix: lint issues (caarlos0)4fe3f70— chore: dependabot update (caarlos0)c98ad84— ci(deps): bump github/codeql-action in the actions group (#410) (dependabot[bot])d28373e— ci(deps): bump the actions group with 2 updates (#408) (dependabot[bot])
🔒Security observations
The codebase demonstrates strong security practices as a zero-dependencies library for parsing environment variables. No critical or high-severity vulnerabilities were identified. The library has appropriate security workflows (CodeQL, govulncheck, grype) integrated into CI/CD. The main consideration is ensuring users are on non-retracted versions that have fixed previous behavioral issues. The simple, focused nature of the library (parsing env vars into structs) minimizes attack surface. No hardcoded credentials, injection risks, or infrastructure misconfigurations were detected in the provided file structure.
- Low · Multiple Retracted Versions in go.mod —
go.mod. The go.mod file contains three retracted versions (v11.0.1, v11.2.0, v11.3.0) due to breaking changes. While this is a maintenance practice, it indicates historical issues with pointer initialization, slice handling, and environment variable merging logic that could have affected users. Fix: Ensure all users upgrade to a non-retracted version. Document the breaking changes clearly in release notes and consider adding integration tests to prevent similar issues in future releases.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.