Documentation
¶
Overview ¶
Package env provides flexible configuration loading from environment variables, .env files, and JSON files. It supports struct tags for automatic binding, variable prefixes, stage-based configuration, and aggregate error handling.
The package is designed for both simple and complex configuration scenarios, with support for nested structs, slices, maps, custom parsers, and validation. All parsing errors are collected and reported together for better debugging.
Basic usage:
type Config struct {
Host string `env:"HOST" envDefault:"localhost"`
Port int `env:"PORT" envDefault:"8080"`
}
cfg, err := env.Parse[Config]()
if err != nil {
log.Fatal(err)
}
With options:
cfg, err := env.Parse[Config](
env.WithPrefix("MYAPP"),
env.WithEnvFile(".env"),
env.WithJSONFile("config.json"),
)
Supported struct tag options:
- env:"VAR" - environment variable name
- env:"VAR,required" - field must be set
- env:"VAR,notEmpty" - field must be non-empty
- env:"VAR,file" - value is a file path, load file contents
- env:"VAR,expand" - expand $VAR references in value
- env:"VAR,unset" - unset env var after reading (useful for secrets)
- envDefault:"value" - default value if not set
- envPrefix:"PREFIX_" - prefix for nested struct fields
- envSeparator:"," - separator for slice/map parsing
- envKeyValSeparator:":" - separator for map key:value pairs
The package supports all basic Go types (string, bool, int, uint, float), time.Duration, time.Location, slices, maps, pointers, and any type implementing encoding.TextUnmarshaler.
Package env provides .env file parsing and manipulation. This file contains functions for reading, parsing, and writing .env format files.
Example ¶
Example demonstrates basic configuration parsing with default values.
type Config struct {
Host string `env:"HOST" envDefault:"localhost"`
Port int `env:"PORT" envDefault:"8080"`
}
// Parse with defaults (no environment variables set)
cfg, err := Parse[Config](WithEnvironment(map[string]string{}))
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Host: %s, Port: %d\n", cfg.Host, cfg.Port)
Output: Host: localhost, Port: 8080
Example (CustomParser) ¶
Example_customParser demonstrates using a custom parser for a user-defined type.
type LogLevel int
const (
Debug LogLevel = iota
Info
Warn
Error
)
type Config struct {
Level LogLevel `env:"LOG_LEVEL"`
}
env := map[string]string{
"LOG_LEVEL": "warn",
}
cfg, err := Parse[Config](
WithEnvironment(env),
WithParser(func(s string) (LogLevel, error) {
switch strings.ToLower(s) {
case "debug":
return Debug, nil
case "info":
return Info, nil
case "warn":
return Warn, nil
case "error":
return Error, nil
default:
return 0, fmt.Errorf("invalid log level: %s", s)
}
}),
)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Log Level: %d (Warn)\n", cfg.Level)
Output: Log Level: 2 (Warn)
Example (NestedStructs) ¶
Example_nestedStructs demonstrates configuration with nested structs.
type Database struct {
Host string `env:"HOST"`
Port int `env:"PORT"`
}
type Config struct {
Database Database `envPrefix:"DB_"`
}
env := map[string]string{
"DB_HOST": "postgres.example.com",
"DB_PORT": "5432",
}
cfg, err := Parse[Config](WithEnvironment(env))
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Database: %s:%d\n", cfg.Database.Host, cfg.Database.Port)
Output: Database: postgres.example.com:5432
Example (SlicesAndMaps) ¶
Example_slicesAndMaps demonstrates parsing slices and maps from environment variables.
type Config struct {
Hosts []string `env:"HOSTS"`
Labels map[string]string `env:"LABELS"`
}
env := map[string]string{
"HOSTS": "host1,host2,host3",
"LABELS": "env:prod,region:us-west",
}
cfg, err := Parse[Config](WithEnvironment(env))
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Hosts: %v\n", cfg.Hosts)
fmt.Printf("Labels: %v\n", cfg.Labels)
Output: Hosts: [host1 host2 host3] Labels: map[env:prod region:us-west]
Example (WithPrefix) ¶
Example_withPrefix demonstrates using a prefix for all environment variables.
type Config struct {
Host string `env:"HOST"`
Port int `env:"PORT"`
}
env := map[string]string{
"MYAPP_HOST": "api.example.com",
"MYAPP_PORT": "443",
}
cfg, err := Parse[Config](
WithEnvironment(env),
WithPrefix("MYAPP"),
)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Host: %s, Port: %d\n", cfg.Host, cfg.Port)
Output: Host: api.example.com, Port: 443
Index ¶
- func GetErrors[T error](err error) []T
- func HasError[T error](err error) bool
- func LoadEnvFile(filenames ...string) error
- func Must[T any](opts ...Option) T
- func OverloadEnvFile(filenames ...string) error
- func Parse[T any](opts ...Option) (T, error)
- func ParseEnvReader(r io.Reader) (map[string]string, error)
- func ParseEnvString(s string) (map[string]string, error)
- func ParseInto(v any, opts ...Option) error
- func ReadEnvFile(filename string) (map[string]string, error)
- func WriteEnvFile(envMap map[string]string, filename string) error
- func WriteEnvFileWithPerm(envMap map[string]string, filename string, perm os.FileMode) error
- type AggregateError
- type EmptyVarError
- type FieldError
- type FileLoadError
- type OnSetFunc
- type Option
- func WithEnvFile(files ...string) Option
- func WithEnvironment(env map[string]string) Option
- func WithJSONFile(files ...string) Option
- func WithOnSet(fn OnSetFunc) Option
- func WithParser[T any](parser func(string) (T, error)) Option
- func WithPrefix(prefix string) Option
- func WithRequireConfigFile() Option
- func WithRequiredIfNoDefault() Option
- func WithStage(stage string) Option
- func WithTagName(envTag, defaultTag string) Option
- func WithUseFieldName() Option
- type Options
- type ParseError
- type ParserFunc
- type VarNotSetError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GetErrors ¶
GetErrors extracts all errors of a specific type from an AggregateError. Returns an empty slice if the error is not an AggregateError or if no matching errors are found.
Example:
varErrors := env.GetErrors[*env.VarNotSetError](err)
for _, e := range varErrors {
fmt.Printf("Missing: %s\n", e.EnvVar)
}
func HasError ¶
HasError checks if an error contains a specific error type using errors.As. It's useful for checking if a particular error type exists in an error chain or within an AggregateError.
Example:
if env.HasError[*env.VarNotSetError](err) {
fmt.Println("Missing required variables")
}
func LoadEnvFile ¶
LoadEnvFile loads environment variables from .env files into os.Environ(). Variables that already exist in the environment are NOT overwritten. Use OverloadEnvFile to override existing values.
If no filenames are provided, it defaults to loading ".env" from the current directory. Missing files return an error.
Example:
err := env.LoadEnvFile(".env", ".env.local")
if err != nil {
log.Fatal(err)
}
func Must ¶
Must wraps Parse and panics on error. Useful for initialization where configuration errors should be fatal.
Example:
var cfg = env.Must[Config](
env.WithEnvFile(".env"),
)
func OverloadEnvFile ¶
OverloadEnvFile loads environment variables from .env files, overwriting any existing values in os.Environ().
If no filenames are provided, it defaults to loading ".env" from the current directory. Missing files return an error.
Example:
err := env.OverloadEnvFile(".env.production")
if err != nil {
log.Fatal(err)
}
func Parse ¶
Parse parses environment variables into a struct of type T and returns the populated struct. This is the primary entry point for configuration loading.
The function loads configuration in the following order (later sources override earlier):
- envDefault struct tags (lowest priority)
- JSON files (if specified with WithJSONFile)
- .env files (if specified with WithEnvFile)
- Environment variables (highest priority, always take precedence)
Example:
type Config struct {
Host string `env:"HOST" envDefault:"localhost"`
Port int `env:"PORT" envDefault:"8080"`
}
cfg, err := env.Parse[Config](
env.WithEnvFile(".env"),
env.WithPrefix("MYAPP"),
)
func ParseEnvReader ¶
ParseEnvReader parses .env format from a reader and returns a map of key-value pairs. Supports standard .env syntax including comments, quoted values, export statements, and both = and : separators.
Example:
file, _ := os.Open("config.env")
defer file.Close()
envVars, err := env.ParseEnvReader(file)
func ParseEnvString ¶
ParseEnvString parses a .env format string and returns a map of key-value pairs. Useful for parsing inline .env configuration.
Example:
envVars, err := env.ParseEnvString(`
HOST=localhost
PORT=8080
DEBUG=true
`)
Example ¶
ExampleParseEnvString demonstrates parsing .env format from a string.
envData := `
# Database configuration
HOST=localhost
PORT=5432
DB_NAME=myapp
# API Keys
API_KEY="secret-key-here"
`
envVars, err := ParseEnvString(envData)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("HOST:", envVars["HOST"])
fmt.Println("PORT:", envVars["PORT"])
fmt.Println("DB_NAME:", envVars["DB_NAME"])
fmt.Println("API_KEY:", envVars["API_KEY"])
Output: HOST: localhost PORT: 5432 DB_NAME: myapp API_KEY: secret-key-here
func ParseInto ¶
ParseInto parses environment variables into an existing struct pointer. Unlike Parse, this modifies an existing struct in place, which is useful when you need to preserve unexported fields or embed configuration in a larger struct.
Example:
cfg := Config{internalField: "preserved"}
err := env.ParseInto(&cfg, env.WithEnvFile(".env"))
func ReadEnvFile ¶
ReadEnvFile reads a .env file and returns a map of key-value pairs. Does not modify the environment. Returns an error if the file cannot be opened.
Example:
envVars, err := env.ReadEnvFile(".env")
if err != nil {
log.Fatal(err)
}
fmt.Println(envVars["DATABASE_URL"])
func WriteEnvFile ¶
WriteEnvFile writes a map of environment variables to a .env file. Values are automatically quoted when necessary (spaces, special characters, etc.). Escape sequences are applied to quoted values. Uses default file permissions (0666 before umask). For sensitive data, use WriteEnvFileWithPerm with restrictive permissions like 0600.
Example:
envVars := map[string]string{
"HOST": "localhost",
"PORT": "8080",
"MESSAGE": "Hello, World!",
}
err := env.WriteEnvFile(envVars, ".env.output")
Example ¶
ExampleWriteEnvFile demonstrates writing environment variables to a .env file.
envVars := map[string]string{
"HOST": "localhost",
"PORT": "8080",
"DEBUG": "true",
"MESSAGE": "Hello, World!",
}
// In a real application, you would write to an actual file
// For this example, we'll just demonstrate the function signature
_ = WriteEnvFile(envVars, "/tmp/example.env")
fmt.Println("Environment variables written to file")
Output: Environment variables written to file
func WriteEnvFileWithPerm ¶ added in v0.0.2
WriteEnvFileWithPerm writes a map of environment variables to a .env file with the specified file permissions. Use this for sensitive configuration files that should have restrictive permissions.
Example:
envVars := map[string]string{
"API_KEY": "secret123",
}
// Only owner can read/write
err := env.WriteEnvFileWithPerm(envVars, ".env.secrets", 0600)
Types ¶
type AggregateError ¶
type AggregateError struct {
Errors []error
}
AggregateError collects multiple parsing errors that occur during configuration parsing. It implements the error interface and provides unwrapping support for errors.Is and errors.As. When multiple fields fail to parse, all errors are collected and reported together.
func (*AggregateError) Error ¶
func (e *AggregateError) Error() string
func (*AggregateError) Is ¶
func (e *AggregateError) Is(target error) bool
Is implements errors.Is support.
func (*AggregateError) Unwrap ¶
func (e *AggregateError) Unwrap() []error
Unwrap returns the list of errors for errors.Is/As support.
type EmptyVarError ¶
type EmptyVarError struct {
Field string // Name of the struct field
EnvVar string // Environment variable that was empty
}
EmptyVarError indicates an environment variable is set but empty when a non-empty value is required. This occurs when a field is marked with the "notEmpty" tag option.
func (*EmptyVarError) Error ¶
func (e *EmptyVarError) Error() string
type FieldError ¶
type FieldError struct {
Field string // Name of the struct field
EnvVar string // Environment variable name
Value string // Value that failed to parse
Err error // Underlying error
}
FieldError represents an error parsing a specific field in the configuration struct. It includes the field name, environment variable name, attempted value, and underlying error.
func (*FieldError) Error ¶
func (e *FieldError) Error() string
func (*FieldError) Unwrap ¶
func (e *FieldError) Unwrap() error
type FileLoadError ¶
type FileLoadError struct {
Field string // Name of the struct field
EnvVar string // Environment variable containing the file path
Filename string // Path to the file that failed to load
Err error // Underlying error from file read operation
}
FileLoadError indicates an error loading content from a file. This occurs when a field is marked with the "file" tag option and the file path cannot be read.
func (*FileLoadError) Error ¶
func (e *FileLoadError) Error() string
func (*FileLoadError) Unwrap ¶
func (e *FileLoadError) Unwrap() error
type OnSetFunc ¶
OnSetFunc is a callback function that is invoked when a field value is set during parsing. It receives the field name, environment variable name, parsed value, and whether the value came from a default.
type Option ¶
type Option func(*Options)
Option is a functional option for configuring the parsing behavior. Use the With* functions to create options for Parse and ParseInto.
func WithEnvFile ¶
WithEnvFile adds .env files to load (in order, later overrides earlier).
func WithEnvironment ¶
WithEnvironment uses a custom environment map instead of os.Environ().
func WithJSONFile ¶
WithJSONFile adds JSON config files to load (in order, later overrides earlier).
func WithOnSet ¶
WithOnSet registers a callback that is called whenever a field value is set during parsing. This is useful for logging, debugging, or tracking which configuration values came from defaults vs environment variables.
The callback receives:
- fieldName: the struct field name
- envVar: the full environment variable name (with prefix/stage if applicable)
- value: the parsed value that was set
- isDefault: true if the value came from envDefault tag, false if from environment
Example:
cfg, err := env.Parse[Config](
env.WithOnSet(func(field, envVar string, value any, isDefault bool) {
if isDefault {
log.Printf("%s using default value", field)
} else {
log.Printf("%s loaded from %s", field, envVar)
}
}),
)
func WithParser ¶
WithParser adds a custom parser for a specific type T. This allows you to define how to parse environment variable strings into custom types.
Example:
type IPAddr struct{ net.IP }
cfg, err := env.Parse[Config](
env.WithParser(func(s string) (IPAddr, error) {
ip := net.ParseIP(s)
if ip == nil {
return IPAddr{}, fmt.Errorf("invalid IP")
}
return IPAddr{ip}, nil
}),
)
func WithPrefix ¶
WithPrefix sets the environment variable prefix.
func WithRequireConfigFile ¶ added in v0.0.2
func WithRequireConfigFile() Option
WithRequireConfigFile causes parsing to fail if no config file is loaded. Use this when your application requires configuration from at least one .env or JSON file, but doesn't care which specific file provides it.
func WithRequiredIfNoDefault ¶
func WithRequiredIfNoDefault() Option
WithRequiredIfNoDefault makes fields without defaults required.
func WithStage ¶
WithStage enables stage-based variable resolution. When set, the parser looks for stage-prefixed variables before falling back to the base variable.
This is useful for multi-environment configurations where some variables differ per environment while others remain the same.
Example: WithStage("PROD") causes PORT to look for PROD_PORT first, then PORT.
func WithTagName ¶
WithTagName sets custom tag names.
func WithUseFieldName ¶
func WithUseFieldName() Option
WithUseFieldName uses field names as env var names when no tag is present.
type Options ¶
type Options struct {
// Environment is a custom map of environment variables.
// If nil, os.Environ() is used.
Environment map[string]string
// Prefix is prepended to all env var lookups.
// Example: Prefix="MYAPP" means HOST becomes MYAPP_HOST
Prefix string
// Stage enables stage-based variable resolution.
// If set, looks for STAGE_VARNAME before VARNAME.
// Example: Stage="PROD" means PORT looks for PROD_PORT first, then PORT.
Stage string
// EnvFiles is a list of .env files to load.
// Later files override earlier ones.
EnvFiles []string
// JSONFiles is a list of JSON config files to load.
// Later files override earlier ones.
JSONFiles []string
// TagName is the struct tag to use for env var names (default: "env").
TagName string
// DefaultTagName is the tag for default values (default: "default").
DefaultTagName string
// RequiredIfNoDefault makes fields without defaults required.
RequiredIfNoDefault bool
// UseFieldNameByDefault uses the field name (converted to UPPER_SNAKE_CASE)
// if no env tag is specified.
UseFieldNameByDefault bool
// FuncMap provides custom parsers for specific types.
FuncMap map[reflect.Type]ParserFunc
// OnSet is called whenever a field value is set.
OnSet OnSetFunc
// RequireConfigFile causes an error if no config file (.env or JSON) is loaded.
// By default, missing config files are silently skipped.
RequireConfigFile bool
}
Options configures the parsing behavior for Parse and ParseInto. Options are typically set using functional option helpers like WithPrefix, WithEnvFile, etc., rather than by constructing this struct directly.
type ParseError ¶
type ParseError struct {
Err error
}
ParseError represents a general parsing error that occurs during configuration loading. It wraps underlying errors for additional context.
func (*ParseError) Error ¶
func (e *ParseError) Error() string
func (*ParseError) Unwrap ¶
func (e *ParseError) Unwrap() error
type ParserFunc ¶
ParserFunc is a function that parses a string value into a typed value. It is used with WithParser to provide custom parsing logic for specific types.
type VarNotSetError ¶
type VarNotSetError struct {
Field string // Name of the struct field
EnvVar string // Environment variable that was not set
}
VarNotSetError indicates a required environment variable is not set. This occurs when a field is marked with the "required" tag option or when WithRequiredIfNoDefault is used and no default value is provided.
func (*VarNotSetError) Error ¶
func (e *VarNotSetError) Error() string