Skip to content

Viper

Developer UtilitiesUtilities/ConfigurationGo

What it is

Viper is a configuration library that merges defaults, config files, environment variables, flags and remote key stores into one lookup, with live reloading.

Viper reads JSON, YAML, TOML, HCL and .env files, binds environment variables and Cobra flags, applies defaults, and can watch a file for changes.

Installation

go get github.com/spf13/viper

Getting started

The smallest useful thing you can do with it, and what each part means.

Layered configuration
viper.SetDefault("port", 8080)
viper.SetDefault("log.level", "info")

viper.SetConfigName("config")
viper.AddConfigPath(".")
viper.AddConfigPath("/etc/myapp/")

// APP_PORT and APP_LOG_LEVEL override the file.
viper.SetEnvPrefix("APP")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()

if err := viper.ReadInConfig(); err != nil {
    var notFound viper.ConfigFileNotFoundError
    if !errors.As(err, &notFound) {
        return err // a malformed file is fatal; a missing one is fine
    }
}

port := viper.GetInt("port")
Precedence runs flag, then environment, then config file, then default. Distinguishing 'no config file' from 'broken config file' matters — the first is normal, the second should stop startup.
Unmarshalling into a struct
type Config struct {
    Port int `mapstructure:"port"`
    Log  struct {
        Level string `mapstructure:"level"`
    } `mapstructure:"log"`
}

var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
    return err
}
Unmarshalling once at startup is better than scattering viper.GetString calls through the codebase — it gives you a typed value and one place to validate.

Advanced usage

Where the library earns its place over a simpler alternative.

Watching for changes
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
    var next Config
    if err := viper.Unmarshal(&next); err != nil {
        log.Printf("ignoring bad config: %v", err)
        return // keep running with the old configuration
    }
    current.Store(&next) // atomic swap, readers see one or the other
})
Reload only after the new configuration validates, and swap it atomically. Applying a half-parsed config to a running service is worse than ignoring the change.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Environment variables are ignored
AutomaticEnv only applies to keys Viper knows about. Register them with SetDefault or BindEnv, and set a key replacer for nested names.
Unmarshal leaves fields zero
Viper uses mapstructure tags, not json. Add mapstructure:"name" to each field.

Best practices

  • Unmarshal into a typed struct once at startup rather than calling Get throughout the code.
  • Use SetEnvKeyReplacer so nested keys map to conventional SCREAMING_SNAKE environment variables.
  • Treat a missing config file as acceptable and a malformed one as fatal.
  • Validate the resulting struct before using it — Viper checks types, not business rules.

Background

Why it exists, and what it was reacting to.

Also from Steve Francia, Viper was designed as Cobra's companion so a CLI could accept the same setting from a flag, an environment variable or a YAML file without writing the precedence logic each time.