$npx -y skills add samber/cc-skills-golang --skill golang-spf13-viperGolang configuration library using spf13/viper — layered precedence (flag > env > file > KV > default), BindPFlag/BindPFlags, SetEnvPrefix + SetEnvKeyReplacer + AutomaticEnv, ReadInConfig + ConfigFileNotFoundError, Unmarshal + mapstructure struct tags, Sub for sub-trees, WatchCon
| 1 | **Persona:** You are a Go engineer who treats configuration as a layered system. Flag beats env beats file beats default — and you bind every key so all four layers stay reachable through one API. |
| 2 | |
| 3 | # Using spf13/viper for layered configuration in Go |
| 4 | |
| 5 | Viper resolves configuration values from multiple sources in a fixed precedence order. It has no user-facing surface — it doesn't define commands or flags. Its job is to answer "what is the value of key X right now?" by walking its source layers from highest to lowest priority. |
| 6 | |
| 7 | **Official Resources:** |
| 8 | |
| 9 | - [pkg.go.dev/github.com/spf13/viper](https://pkg.go.dev/github.com/spf13/viper) |
| 10 | - [github.com/spf13/viper](https://github.com/spf13/viper) |
| 11 | |
| 12 | This skill is not exhaustive. Please refer to library documentation and code examples for more information. For Go package docs, symbols, versions, importers, and known vulnerabilities, → See `samber/cc-skills-golang@golang-pkg-go-dev` skill (`godig`) — prefer it over Context7 for Go package facts. To navigate this library's usage in your own code (definitions, call sites, diagnostics), → See `samber/cc-skills-golang@golang-gopls` skill (`gopls`). Context7 remains a fallback for docs not indexed on pkg.go.dev. |
| 13 | |
| 14 | ```bash |
| 15 | go get github.com/spf13/viper@latest |
| 16 | ``` |
| 17 | |
| 18 | ## Viper vs. cobra |
| 19 | |
| 20 | Cobra owns the command tree — subcommands, flags, arg validation, completions. Viper owns configuration resolution — it answers "what is the value of key X?" by walking its source layers. Viper has no user-facing surface; it is purely a key-value resolver. Use cobra alone for flag-only CLIs; viper alone for config-file daemons; both when you need both, binding flags at `PersistentPreRunE` via `BindPFlag`. |
| 21 | |
| 22 | → See `samber/cc-skills-golang@golang-spf13-cobra` for the cobra side of this integration. |
| 23 | |
| 24 | ## The precedence pipeline |
| 25 | |
| 26 | Viper resolves a key by walking sources in this order (first set value wins): |
| 27 | |
| 28 | ``` |
| 29 | 1. explicit Set() — viper.Set("key", val) highest priority |
| 30 | 2. flag — bound pflag.Flag |
| 31 | 3. env var — BindEnv / AutomaticEnv |
| 32 | 4. config file — ReadInConfig / MergeInConfig |
| 33 | 5. KV remote — etcd / Consul |
| 34 | 6. default — viper.SetDefault("key", val) lowest priority |
| 35 | ``` |
| 36 | |
| 37 | This pipeline is fixed and cannot be reordered. Understanding it prevents most viper bugs: a key that "should" come from a config file may be shadowed by an env var or a flag with a default value. |
| 38 | |
| 39 | ## Sources and config files |
| 40 | |
| 41 | ```go |
| 42 | viper.SetConfigName("config") |
| 43 | viper.AddConfigPath("$HOME/.myapp") |
| 44 | if err := viper.ReadInConfig(); err != nil { |
| 45 | var notFound *viper.ConfigFileNotFoundError |
| 46 | if !errors.As(err, ¬Found) { |
| 47 | return fmt.Errorf("reading config: %w", err) // propagate real errors only |
| 48 | } |
| 49 | } |
| 50 | ``` |
| 51 | |
| 52 | `ConfigFileNotFoundError` must be handled gracefully — config files are usually optional. An unhandled error from a missing file crashes programs that are perfectly valid when run with only flags or env vars. |
| 53 | |
| 54 | For supported formats (JSON, TOML, YAML, HCL, INI, properties), `MergeInConfig`, and remote KV, see [sources-and-formats.md](references/sources-and-formats.md). |
| 55 | |
| 56 | ## Env binding and key replacers |
| 57 | |
| 58 | This is the highest-bug-density area in viper. All three settings must be wired together — missing any one breaks nested key resolution: |
| 59 | |
| 60 | ```go |
| 61 | // ✓ Good — all three wired together at startup |
| 62 | viper.SetEnvPrefix("MYAPP") // prevent collisions: PORT → MYAPP_PORT |
| 63 | viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // database.host → MYAPP_DATABASE_HOST |
| 64 | viper.AutomaticEnv() |
| 65 | |
| 66 | // ✗ Bad — without SetEnvKeyReplacer, viper looks for MYAPP_DATABASE.HOST (dot preserved) |
| 67 | ``` |
| 68 | |
| 69 | For `BindEnv`, `AllowEmptyEnv`, and env-vs-default interaction, see [binding-and-env. |