env

A simple, zero-dependencies Go library that parses environment variables directly into struct fields using tags.

Library
Go
vv11.4.1
6,307stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
71/100Good
Development Activity68
Maintenance56
Community60
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
81/100Excellent
Architecture85
Code Quality88
Innovation62
Learning Curve90

env is a small, dependency-free Go library for turning environment variables into typed configuration structs. Instead of hand-writing os.Getenv calls and manual type conversions for every setting, you annotate a struct with env tags and call env.Parse (or the generic env.ParseAs[T]) to populate it in one pass, with built-in support for primitives, slices, maps, time.Duration, time.Location, url.URL, and any type implementing encoding.TextUnmarshaler.

Beyond basic parsing, it supports default values (envDefault), required and non-empty enforcement (required, notEmpty), variable expansion (,expand), loading a value from a file path (,file), prefixing nested structs (envPrefix), custom separators for slices and maps, and a hook (OnSet) for observing every value as it’s assigned. Custom parsers can be registered per-type via FuncMap, and ParseWithOptions exposes fine-grained control over tag names, the source environment map, and default-value behavior.

The project has been stable and widely adopted for years (created 2015, on major version v11), is used in production by platforms like Encore, and the maintainer explicitly considers it feature-complete — new work is limited to bug fixes and carefully considered additions. Its zero-dependency footprint and predictable, tag-driven API make it a common default choice for twelve-factor-style Go services that read configuration from the environment.

What You Get

  • One-call parsing of environment variables into any struct via Parse, ParseWithOptions, or the generic ParseAs[T] / ParseAsWithOptions[T]
  • Built-in parsers for all primitive Go types plus time.Duration, time.Location, url.URL, slices, maps, and pointers, with support for registering custom parsers via FuncMap
  • Declarative validation and defaults through tag options: required, notEmpty, envDefault, ,expand for variable interpolation, and ,file for file-backed values
  • Nested-struct support with envPrefix to namespace environment variable names, and envSeparator/envKeyValSeparator to control slice and map parsing
  • An OnSet hook that fires for every field as it is populated, useful for logging or auditing configuration at startup
  • Structured, typed errors (ParseError, VarIsNotSetError, EmptyVarError, NoParserError, etc.) aggregated into a single AggregateError that supports errors.Is/errors.As

Common Use Cases

  • Loading twelve-factor-app configuration (ports, hostnames, credentials, feature flags) into a typed Config struct at service startup
  • Enforcing required environment variables in CI/CD and containerized deployments, failing fast with a clear aggregated error instead of a nil-pointer panic later
  • Reading secrets or credentials mounted as files (e.g. Docker/Kubernetes secrets) into config fields via the ,file tag option instead of plaintext env vars
  • Namespacing configuration for multiple instances of the same component (e.g. two database connections) using envPrefix on nested structs
  • Providing sane defaults for local development while requiring explicit values in production, via envDefault combined with RequiredIfNoDef

Under The Hood

Architecture Parsing flows through a small, linear pipeline in env.go: Parse/ParseWithOptions build an Options value (merging user overrides over defaultOptions() via reflection-based mergeOptions), then hand off to parseInternal, which validates the input is a pointer to a struct and calls doParse. doParse walks each struct field with reflect, delegating to doParseField for scalar/aggregate fields and doParseSlice for slices of nested structs (each element gets its own prefixed Options via optionsWithSliceEnvPrefix), recursing into nested structs so envPrefix composes naturally. Field-level tag parsing (parseFieldParams), value lookup (get, getOr, getFromFile), and type coercion (set, handleSlice, handleMap, parseTextUnmarshalers) are each isolated into their own functions, so the recursive struct-walking logic never touches string-to-type conversion directly — a clean separation that keeps the reflection-heavy core auditable despite touching every exported field in a struct tree.

Tech Stack The module (github.com/caarlos0/env/v11, Go 1.18+) has zero runtime dependencies — everything is built on the standard library (reflect, encoding, net/url, os, strconv, strings, time, unicode). Build and release tooling is handled by a Makefile plus GoReleaser, and CI runs via GitHub Actions across Ubuntu/macOS/Windows and three Go versions (1.18, oldstable, stable), with golangci-lint (.golangci.yml), Semgrep, CodeQL, and a Codecov upload gating merges.

Code Quality The test suite (env_test.go) contains well over a hundred test functions covering scalar types, slices, maps, nested structs, prefixes, defaults, required/notEmpty combinations, file-backed values, and custom parsers, alongside a dedicated example_test.go of runnable Example* functions that double as documentation on pkg.go.dev. Errors are modeled as distinct exported types (ParseError, VarIsNotSetError, NoParserError, etc.) rather than opaque strings, aggregated through an AggregateError that implements Unwrap() []error and a custom Is for errors.Is compatibility — a deliberately typed, inspectable error-handling style rather than sentinel strings.

What Makes It Unique Rather than a runtime config-loading framework with file formats and remote backends, env stays narrowly scoped to “struct tags in, typed values out,” but pushes that scope further than most equivalents: generics-based ParseAs[T] avoids a pre-allocated pointer argument, ,file transparently swaps a value for file contents (useful for secret-mounted configs), and ,expand performs recursive environment-variable interpolation. Combined with its zero-dependency footprint and long-standing API stability (feature-complete per the maintainer, on major version v11 after eleven years), it optimizes for being a safe, boring default rather than the most feature-rich option.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search