envconfig

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 13, 2025 License: MIT Imports: 9 Imported by: 0

README

envconfig

A small, dependency-free Go library for loading configuration from environment variables directly into your structs.

Go Reference

It supports nested structs, prefixes, defaults, required fields, slices, maps, arrays, pointers, durations, and custom (un)marshalers. A helper is provided to read variables from a .env file.

  • Zero dependencies
  • Simple, tag-driven API
  • Works with standard os.LookupEnv or a custom lookups
  • Optional .env file loader (supports comments, export, quoting, inline comments)

Installation

go get github.com/struct0x/envconfig

Quick start

package main

import (
	"fmt"

	"github.com/struct0x/envconfig"
)

type HTTPServer struct {
	Host    string            `env:"HOST" envDefault:"127.0.0.1"`
	Port    int               `env:"PORT" envRequired:"true"`
	Enabled bool              `env:"ENABLED"`
	Tags    []string          `env:"TAGS"`    // "a,b,c" -> []string{"a","b","c"}
	Headers map[string]string `env:"HEADERS"` // "k1=v1,k2=v2"
}

func main() {
	var cfg HTTPServer

	// Use OS environment by default
	if err := envconfig.Read(&cfg); err != nil {
		panic(err)
	}

	fmt.Printf("%+v\n", cfg)
}

Example environment:

export PORT=8080
export ENABLED=true
export TAGS="alpha,beta"
export HEADERS="X-Req=abc,X-Trace=on"

Using a .env file

Use EnvFileLookup to source values from a .env file. Lines use KEY=VALUE, support comments and export statements, and handle quoted values with inline comments.

package main

import (
	"fmt"

	"github.com/struct0x/envconfig"
)

type App struct {
	Name string `env:"NAME" envDefault:"demo"`
	Port int    `env:"PORT" envRequired:"true"`
}

func main() {
	var cfg App
	
	if err := envconfig.Read(&cfg, envconfig.EnvFileLookup(".env")); err != nil {
		panic(err)
	}

	fmt.Printf("%+v\n", cfg)
}

Notes:

  • If both the .env file and the OS define a key, the .env value wins for that lookup.
  • EnvFileLookup panics if the file cannot be read.

Tags

Add struct field tags to control how values are loaded:

  • env: the env variable name. Use env:"-" to skip a field.
  • envDefault: fallback value if the variable is not set.
  • envRequired:"true": marks the field as required, returns error when not set, and no default provided.
  • envPrefix: only for struct-typed fields; prepends a prefix (with underscore) for all nested fields under that struct.

Precedence per field:

  1. Value from lookupEnv(name)
  2. envDefault (if present)
  3. Error if envRequired:"true"
Examples

Basic tags:

package main

type DB struct {
	Host string `env:"DB_HOST" envDefault:"localhost"`
	Port int    `env:"DB_PORT" envRequired:"true"`
}

Nested with prefix:

package main

type SubConfig struct {
	Enabled bool   `env:"ENABLED"`
	Mode    string `env:"MODE" envDefault:"safe"`
}

type Root struct {
	Name string     `env:"NAME"`
	Sub  *SubConfig `envPrefix:"SUB"` // Reads SUB_ENABLED, SUB_MODE
}

Skipping a field:

package main

type T struct {
	Ignored string `env:"-"`
}

Supported types

  • string, bool
  • Integers: int, int8, int16, int32, int64
  • Unsigned integers: uint, uint8, uint16, uint32, uint64
  • Floats: float32, float64
  • time.Duration via time.ParseDuration
  • Arrays and slices (comma-separated values): "a,b,c"
  • Maps (comma-separated key=value pairs): "k1=v1,k2=v2"
  • Pointers to supported types (allocated when needed)
  • Custom types implementing any of:
    • encoding.TextUnmarshaler
    • encoding.BinaryUnmarshaler
    • json.Unmarshaler

If a value cannot be parsed into the target type, Read returns a descriptive error.

Custom lookup (probably don't need this)

You can provide any lookup function with signature func(string) (string, bool) — for example, a map-based lookup in tests:

package main

import (
	"github.com/struct0x/envconfig"
)

func mapLookup(m map[string]string) func(string) (string, bool) {
	return func(k string) (string, bool) { v, ok := m[k]; return v, ok }
}

type C struct {
	N int `env:"N"`
}

func main() {
	var c C
	_ = envconfig.Read(&c, mapLookup(map[string]string{"N": "42"}))
}

Error handling

Read returns an error when:

  • The holder is not a non-nil pointer to a struct
  • A required field is missing and no default is provided
  • A value cannot be parsed into the target type

Errors include the env variable name and context to aid debugging.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EnvFileLookup

func EnvFileLookup(filePath string) func(string) (string, bool)

EnvFileLookup returns a lookup function that reads environment variables from a .env file. It panics if a file cannot be read. The .env file should have lines in the format KEY=VALUE. Comments starting with # are ignored. Empty lines are ignored. Notes:

  • If both the .env file and OS environment define a key, the OS environment value wins (current behavior).
  • Lines like `export KEY=VALUE` are supported.

func IgnoreEmptyEnvLookup

func IgnoreEmptyEnvLookup(key string) (string, bool)

IgnoreEmptyEnvLookup wraps os.LookupEnv but treats empty values as unset. If the variable is present but "", it returns ok == false.

func Read

func Read[T any](holder *T, lookupEnv ...func(string) (string, bool)) error

Read populates holder (a pointer to struct) using the provided lookup function to resolve values.

Usage:

type C struct {
  Port int    `env:"PORT" envDefault:"8080"`
  TLS  struct {
    Enabled bool   `env:"ENABLED"`
    Cert    string `env:"CERT" envRequired:"true"`
  } `envPrefix:"TLS"` // effective keys: TLS_ENABLED, TLS_CERT
}
var cfg C
if err := envconfig.Read(&cfg); err != nil { log.Fatal(err) }

Lookup source:

By default Read uses os.LookupEnv. You may pass a custom lookup function,
e.g., envconfig.Read(&cfg, myLookup) where myLookup has signature func(string) (string, bool).

Tags (per field):

  • `env:"NAME"` : the environment variable name for this field. Use `env:"-"` to skip the field entirely.
  • `envDefault:"VAL"` : fallback used only when the variable is UNSET (i.e., lookup returns ok == false). If the variable is present but empty ("", ok == true), the empty value is used and default does NOT apply.
  • `envRequired:"true"`: if the variable is UNSET and no envDefault is provided, Read returns an error. Only the literal "true" enables this behavior.
  • `envPrefix:"PFX"` : for struct-typed fields (including embedded/ anonymous ones). Applies a prefix to all descendant leaf env names. Prefixes are joined with "_". Example: `envPrefix:"DB"` -> DB_HOST, DB_PORT.

Embedded vs named struct fields:

  • Embedded (anonymous) struct fields are treated "flat" by default (no extra prefix). To prefix an embedded subtree, put `envPrefix` on the embedded field.
  • Named struct fields may also carry `envPrefix:"PFX"`; they must NOT also have an `env` tag.

Whole-struct (single-key) decoding:

If a struct-typed field (or embedded struct) has an effective prefix PFX_,
and the holder type implements one of the standard decoders below, a single
env variable named "PFX" (without the trailing underscore) can be used to
populate the entire struct at once. When present, this whole-struct value
takes precedence and field-by-field decoding is skipped.
Supported decoders:
  - encoding.TextUnmarshaler
  - encoding.BinaryUnmarshaler
  - json.Unmarshaler

Supported field types:

  • primitives: string, bool, all int/uint sizes, float32/64
  • time.Duration (parsed via time.ParseDuration)
  • arrays, slices: comma-separated values (e.g. "a,b,c")
  • maps: comma-separated k=v pairs (e.g. "k1=v1,k2=v2"); split on first "="
  • pointers to any supported type (allocated as needed)
  • any type implementing encoding.TextUnmarshaler / BinaryUnmarshaler / json.Unmarshaler

Precedence per leaf field:

  1. If lookupEnv returns (value, ok==true), that value is used as-is (even if value is the empty string "").
  2. Else, if `envDefault` is present, it is used.
  3. Else, if `envRequired:"true"`, Read returns an error.
  4. Else, the field is left at its zero value.

Validation & errors:

  • holder must be a non-nil pointer to a struct.
  • Non-embedded struct fields must have either `env` or `envPrefix` (or be explicitly skipped with `env:"-"`); otherwise an error is returned.
  • Struct fields must not specify both `env` and `envPrefix`.
  • `envPrefix` must not be empty when present.
  • Parsing/conversion failures return errors that include the env key.
  • Unsupported leaf types (that do not implement a supported unmarshal interface) cause an error.

Note on empties:

An env var that is present but empty (lookup ok == true, value == "") is
considered "set": it suppresses `envDefault` and does not trigger
`envRequired`. If you want defaulting on empty strings, use IgnoreEmptyEnvLookup,
which wraps os.LookupEnv and treats empty values as unset (returns ok == false when value == "").

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL