caarlos0/env: parsing environment variables into Go structs
A simple, zero-dependencies library to parse environment variables into structs
At a glance
- What is it?
- A zero-dependency Go library that maps environment variables onto struct fields through tags. It is feature-complete, MIT licensed, and deliberately narrow in scope.
- Who is it for?
- Adopt caarlos0/env if you have a Go service with a fixed set of settings and you want them bound to a struct without pulling in a configuration framework. Skip it if you need layered config files, hot reloading, or a validation pipeline beyond what the tags express.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 27 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem caarlos0/env solves for Go services
Every Go binary that reads configuration ends up writing the same loop: call os.Getenv, check for an empty string, fall back to a literal, convert to int or time.Duration, and repeat for each setting. The repetition is boring, and the failure mode is silent. A typo in a variable name produces an empty string, not an error, and the process starts with the wrong value.
caarlos0/env replaces that loop with struct tags. You declare a struct whose fields carry env tags, hand a pointer to env.Parse, and the library reads the current environment into the fields. The README describes it as "a simple, zero-dependencies library to parse environment variables into structs," and the repository layout backs that up: the module has no runtime dependencies beyond the standard library.
The audience is Go developers writing services, CLIs or jobs that are configured through environment variables, which in container platforms is most of them. It is not a configuration framework. There is no file loading, no remote source, no watch loop. The README states plainly that the library "is considered feature-complete" and that new features will only be added if they "really make sense, and are requested by many people." Read that as a promise about stability, and as a warning that gaps will probably stay gaps.
How the tag parser walks your struct
The mechanism is reflection over exported fields. You pass a pointer to a struct, and the library inspects each field's env tag for the variable name and any comma-separated options. A field tagged `env:"HOME"` is filled from the HOME variable. Fields without a matching variable keep their zero value unless an envDefault tag supplies one.
Options change what happens at that point. `,required` makes the field error when the variable is not set. `,notEmpty` errors when the value is empty. `,expand` resolves embedded references such as `FOO_${BAR}`. `,file` treats the variable's content as a path and reads the file. `,init` allocates nil pointers. `,unset` removes the variable after it has been consumed, which matters when the process later spawns a child that should not inherit a secret.
Beyond the tag layer, the WithOptions variants accept an Environment slice to use instead of os.Environ(), an OnSet callback that fires when a value is assigned, a FuncMap of custom parse functions for your own types, and alternative tag names if env collides with something else in your struct. The supported type list is broad: all the built-in numeric and string types, bool, time.Duration, time.Location, url.URL, anything implementing encoding.TextUnmarshaler, plus pointers, slices and maps of those. There are two entry points for the common case, Parse and the generic ParseAs, and Must wraps either to panic on error instead of returning one.
Two caveats in the README are worth internalising before you write a single tag. Unexported fields are ignored, and the README says this is "by design and will not change." And a variable that is set but empty falls back to envDefault, which means neither required nor notEmpty protects you in that case: required is satisfied by the default, and notEmpty only inspects the value after the default has been applied. If your deployment sets FOO="" intending to force an error, this library will not give you one when a default exists.
Installing caarlos0/env and a first parse
The README gives one installation command. It fetches the v11 module path, which is what the import statements in your code must match.
go get github.com/caarlos0/env/v11A first program needs a struct with env tags and a call to the parser. The README's getting-started example is the smallest useful shape, and it compiles as written once you add the import and a main function.
type config struct {
Home string `env:"HOME"`
}
// parse
var cfg config
err := env.Parse(&cfg)
// parse with generics
cfg, err := env.ParseAs[config]()The first form takes a pointer, so the struct must be addressable and its fields exported. The second form is the generics variant and returns the value directly. Both return an error, which you should check: a missing required variable or a value that will not convert to the field's type surfaces there rather than as a zero value.
Defaults and requiredness are declared on the tag itself, not in code. A field tagged `env:"PORT" envDefault:"8080"` uses 8080 when PORT is absent. Adding `,required` to the env tag turns absence into an error instead. For a nested group of settings, envPrefix on a struct field prefixes every variable inside it, which keeps related names from colliding across subsystems. Once the struct is populated, treat it as an ordinary value: the library has done its work and does not hold a reference to your environment afterwards.
Where caarlos0/env stops being the right tool
The library reads the environment once, at the moment you call it. There is no reload, no file watcher, no signal handling. If your service needs to pick up a changed value without restarting, this is the wrong library, and no option in the README changes that.
Validation is limited to what the tags express. required and notEmpty cover presence and emptiness. They do not cover ranges, formats, cross-field consistency, or enumerations. A field tagged as an int will reject a non-numeric string, but it will happily accept a negative port number. Anything beyond type conversion belongs in code you write after Parse returns.
The empty-string interaction described earlier is the sharpest edge. A deployment that sets a variable to an empty value, perhaps because a secret was mounted but empty, will silently receive the default. That is a real failure mode in orchestrated environments where variables are frequently injected as empty strings rather than omitted.
Finally, the scope is deliberately closed. The README states the project is feature-complete and that bug fixes will "keep being merged" while new features are unlikely. If your requirement is not already covered by the tag list or the options list, the maintainer's stated position suggests you should not expect it to arrive. That is not a defect, but it should shape your evaluation: check the existing feature set against your needs before adopting, not after.
caarlos0/env compared with godotenv and hand-rolled getenv code
The closest thing to a default answer in this space is godotenv, which appears in the search terms around this project. The two solve different halves of the problem. godotenv reads a .env file and loads its contents into the process environment; it is about getting values into the environment, typically in development. caarlos0/env does not read files at all. It takes an environment that already exists and maps it onto typed struct fields.
That difference decides the choice. If your deployment supplies real environment variables and your problem is binding them to a struct with defaults and type conversion, caarlos0/env is the direct fit. If your problem is that developers want a checked-in .env file rather than exported shell variables, godotenv addresses that, and you could use both: godotenv to populate the environment, caarlos0/env to parse it. The README's `,file` option is not the same thing as godotenv. It reads the contents of a single file whose path is stored in a variable, which is a pattern for secrets rather than for bulk configuration.
The other alternative is the code you would write yourself: a getenv helper per type, a defaults map, and a validation pass. That code is not hard to write, and it has no dependency and no reflection. What it loses is the declarative part. The struct tag is the documentation of what the program expects, and it sits next to the field it configures. The README links to envdoc, a separate project that generates documentation for environment variables from these same env tags, which is a benefit hand-rolled helpers do not offer without extra work.
Maintenance, versioning and the MIT licence
The repository is not archived, and the last push was on 2026-09-04. The most recent release listed is v11.4.1, published on 2026-05-01, following v11.4.0 on 2026-02-22 and v11.3.1 on 2024-12-20. The gap between v11.3.1 and v11.4.0 is roughly fourteen months, so release cadence should be read as occasional rather than frequent. The README's own statement that the library is feature-complete explains the pattern: bug fixes continue, features do not.
The go.mod file is unusually informative. It carries three retract directives: v11.0.1 for a breaking change in nil pointer behaviour, v11.2.0 for a breaking change in nil slices of complex types, and v11.3.0 for merging OS environment variables with environments set through Options instead of overriding them. Retractions are the module system's way of telling the toolchain not to select those versions, and their presence here is a sign that the maintainer treats breaking changes as something to correct rather than paper over. It also means you should not pin to a version below v11.4.0 without checking whether it is retracted.
The module requires go 1.18, which is the floor for the generics-based ParseAs entry point. The licence is MIT, stated in the repository's LICENSE.md and shown in the README badges. MIT is permissive: it allows use in closed-source products with attribution, and it does not impose copyleft obligations on your code. That is a description of the licence text, not legal advice; if your organisation has specific policy about third-party dependencies, route it through whoever handles that.
Editorial conclusion
Adopt caarlos0/env if you have a Go service with a fixed set of settings and you want them bound to a struct without pulling in a configuration framework. Skip it if you need layered config files, hot reloading, or a validation pipeline beyond what the tags express. Before committing, verify three things in your own code: that no unexported fields are expected to be populated, that no variable is set to an empty string where you rely on required or notEmpty, and that your Go version satisfies the go 1.18 directive in go.mod.
Frequently asked questions
How do I install caarlos0/env in a Go project?
Run go get github.com/caarlos0/env/v11, then import that same module path. The README gives this as the only installation step, and the module has no dependencies beyond the standard library.
How do I set a default value in caarlos0/env?
Add an envDefault tag to the field, for example `env:"PORT" envDefault:"8080"`. The README notes that the default is also used when the environment variable is set but empty.
How does caarlos0/env handle a required environment variable?
Append the ,required option to the env tag, which makes the field error when the variable is not set. Be aware that if the field also declares envDefault, the default satisfies the requirement.
Does caarlos0/env read .env files?
No. The library parses the current environment into a struct and does not load files. The ,file tag option reads the contents of a file whose path is stored in a variable, which is a different thing from loading a .env file.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/caarlos0-env)