A convenient, type-safe configuration library for Go that populates your structs from multiple sources including environment variables, command-line flags, JSON files, and more.
- Multiple Providers: Load configuration from environment variables, flags, JSON, or static values
- Type Safety: Automatic type conversion for primitive types, slices, maps, and custom types
- Struct Tags: Simple tag-based configuration using
env,flag,jsontags - Composable: Chain multiple providers with fallback behavior
- Extensible: Create custom providers for any configuration source
- CLI Support: Built-in CLI framework with command hierarchies and help generation
package main
import (
"context""fmt""os""github.com/upfluence/cfg"
)
typeConfigstruct {
Hoststring`env:"HOST" flag:"host"`Portint`env:"PORT" flag:"port"`Debugbool`env:"DEBUG" flag:"debug"`Timeout time.Duration`env:"TIMEOUT" flag:"timeout"`
}
funcmain() {
varcConfigctx:=context.Background()
iferr:=cfg.NewDefaultConfigurator().Populate(ctx, &c); err!=nil {
fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
os.Exit(1)
}
fmt.Printf("Server: %s:%d (debug=%v, timeout=%s)\n",
c.Host, c.Port, c.Debug, c.Timeout)
}The default configurator uses both environment variables and command-line flags:
# Using environment variables
$ HOST=localhost PORT=8080 DEBUG=true TIMEOUT=30s ./app
Server: localhost:8080 (debug=true, timeout=30s)
# Using flags
$ ./app --host localhost --port 8080 --debug --timeout 30s
Server: localhost:8080 (debug=true, timeout=30s)
# Boolean flags support --no- prefix
$ ./app --host localhost --no-debug
Server: localhost: (debug=false, timeout=0s)The env provider reads from environment variables with automatic uppercasing and underscore conversion.
typeConfigstruct {
APIKeystring`env:"API_KEY"`BaseURLstring`env:"BASE_URL"`
}You can also use prefixed environment variables:
provider:=env.NewProvider("MYAPP") // Looks for MYAPP_* variablesconfigurator:=cfg.NewConfigurator(provider)The flags provider parses command-line arguments with support for short flags, equals syntax, and boolean negation.
typeConfigstruct {
Verbosebool`flag:"verbose,v"`// --verbose or -vOutputstring`flag:"output,o"`// --output file or -o fileForcebool`flag:"force"`// --force or --no-force
}Supported formats:
--flag value--flag=value-f value--flag(boolean true)--no-flag(boolean false)
Load configuration from JSON files:
file, _:=os.Open("config.json")
deferfile.Close()
jsonProvider:=json.NewProviderFromReader(file)
configurator:=cfg.NewConfigurator(jsonProvider)The JSON provider supports nested structures using dot notation:
{
"database": {
"host": "localhost",
"port": 5432
}
}typeConfigstruct {
DBHoststring`json:"database.host"`DBPortint`json:"database.port"`
}Provide configuration from Go values directly:
staticProvider:=static.NewProvider(map[string]interface{}{
"host": "localhost",
"port": 8080,
})
configurator:=cfg.NewConfigurator(staticProvider)Create your own provider by implementing the Provider interface:
typeProviderinterface {
StructTag() stringProvide(context.Context, string) (string, bool, error)
}Example custom provider:
typeConsulProviderstruct{}
func (p*ConsulProvider) StructTag() string { return"consul" }
func (p*ConsulProvider) Provide(ctx context.Context, keystring) (string, bool, error) {
// Fetch from Consulvalue, err:=consulClient.Get(key)
iferr==ErrNotFound {
return"", false, nil
}
returnvalue, true, err
}Providers are evaluated in order. The first provider that returns a value wins:
configurator:=cfg.NewConfigurator(
flags.NewDefaultProvider(), // Check flags firstenv.NewDefaultProvider(), // Then environment variablesjsonProvider, // Then JSON filestaticProvider, // Finally defaults
)The library automatically handles type conversion for:
- Primitives:
string,bool, allintandfloattypes - Time:
time.Duration(viatime.ParseDuration),time.Time(RFC3339 format) - Slices: Comma-separated values (
"a,b,c"→[]string{"a", "b", "c"}) - Maps: Key-value pairs (
"k1=v1,k2=v2"→map[string]string{"k1": "v1", "k2": "v2"}) - Nested Structs: Dot notation for nested fields
- Custom Types: Any type implementing:
json.Unmarshalerencoding.TextUnmarshalerinterface { Parse(string) error }
configurator:=cfg.NewConfiguratorWithOptions(
cfg.WithProviders(myProvider),
cfg.IgnoreMissingTag, // Don't error on fields without tags
)Use the default struct tag to provide fallback values when no other provider
supplies one:
typeConfigstruct {
Hoststring`env:"HOST" flag:"host" default:"localhost"`Portint`env:"PORT" flag:"port" default:"8080"`Verbosebool`env:"VERBOSE" default:"false"`
}The default provider has the lowest priority — any value from environment
variables, flags, or other providers takes precedence. It is included
automatically in NewDefaultConfigurator. Nested structs work as expected:
only fields with an explicit default tag receive a value.
Use the required struct tag together with the HonorRequired option to
enforce that a field receives a value from at least one provider:
typeConfigstruct {
APIKeystring`env:"API_KEY" required:"true"`Debugbool`env:"DEBUG"`
}
configurator:=cfg.NewConfiguratorWithOptions(
cfg.WithProviders(env.NewDefaultProvider()),
cfg.HonorRequired,
)
// Returns a *cfg.RequiredError if API_KEY is not seterr:=configurator.Populate(ctx, &cfg)The tag accepts any truthy value (true, yes, y, 1, t).
Fields that receive a value from any provider — including the default tag —
satisfy the requirement. HonorRequired is turned on by default in
NewDefaultConfigurator.
typeDatabasestruct {
Hoststring`env:"DB_HOST" flag:"db-host"`Portint`env:"DB_PORT" flag:"db-port"`
}
typeConfigstruct {
DatabaseDatabase// Automatically traversedAppNamestring`env:"APP_NAME" flag:"app-name"`
}For more control, create a configurator without the default providers:
configurator:=cfg.NewConfigurator(
myProvider1,
myProvider2,
)The x/cli package provides a framework for building CLI applications with automatic help generation:
import"github.com/upfluence/cfg/x/cli"typeRunConfigstruct {
Hoststring`flag:"host,h"`Portint`flag:"port,p"`
}
cmd:= cli.StaticCommand{
Help: cli.HelpWriter(&RunConfig{}),
Synopsis: cli.SynopsisWriter(&RunConfig{}),
Execute: func(ctx context.Context, cctx cli.CommandContext) error {
varcfgRunConfig// Configuration is automatically populatedfmt.Printf("Running on %s:%d\n", cfg.Host, cfg.Port)
returnnil
},
}
app:=cli.NewApp(
cli.WithName("myapp"),
cli.WithCommand(cmd),
)
app.Run(context.Background())Contributions are welcome! Please feel free to submit issues or pull requests.