Viper — API Surface#
API types#
Library — pure Go library with no HTTP server, no gRPC service, no CLI binary.
All exposure is through exported Go symbols (functions, types, interfaces, methods).
Library API#
Public packages#
| Package | Purpose |
|---|
github.com/spf13/viper | Core library — the only public-facing package |
github.com/spf13/viper/remote | Optional remote k/v store integration (separate module, blank-import self-registration) |
There are no pkg/ or cmd/ directories. All internal packages (internal/encoding/*, internal/features, internal/testutil) are hidden from consumers.
API style#
Dual-surface design: every capability is exposed both as a package-level function (targeting a global singleton initialised in init()) and as a method on *Viper (for multi-instance use). Zero logic duplication — global functions are one-liners delegating to the singleton.
// Global (singleton) form
viper.Get("key")
viper.ReadInConfig()
// Instance form
v := viper.New()
v.Get("key")
v.ReadInConfig()
Constructor functions#
| Function | Signature | Notes |
|---|
New | () *Viper | Allocates and initialises a fresh Viper with sensible defaults |
NewWithOptions | (opts ...Option) *Viper | Calls New() then applies each Option |
GetViper | () *Viper | Returns the package-level singleton |
SetOptions | (opts ...Option) | Applies options to the singleton after construction |
Reset | () | Resets the singleton to a fresh New() state (testing aid) |
Functional options (Option interface)#
Options inject configuration into Viper at construction time via NewWithOptions.
| Option constructor | Effect |
|---|
KeyDelimiter(d string) | Sets the key-path delimiter (default ".") |
EnvKeyReplacer(r StringReplacer) | Replaces characters in env key lookups |
WithDecodeHook(h mapstructure.DecodeHookFunc) | Overrides the default mapstructure decode hook |
WithEncoderRegistry(r EncoderRegistry) | Injects a custom encoder registry |
WithDecoderRegistry(r DecoderRegistry) | Injects a custom decoder registry |
WithCodecRegistry(r CodecRegistry) | Injects a custom codec registry (combines both) |
WithLogger(l *slog.Logger) | Injects a structured logger |
WithFinder(f Finder) | Injects a custom config-file finder (experimental feature path) |
ExperimentalFinder() | Enables the XDG-compliant locafero-based finder (build tag viper_finder) |
ExperimentalBindStruct() | Enables BindStruct capability (build tag viper_bind_struct) |
Source binding API#
These methods attach config sources to the registry. They do not immediately read values; values are resolved lazily when Get* is called.
Config file#
| Method | Signature | Description |
|---|
SetConfigFile | (in string) | Pin an explicit config file path |
SetConfigName | (in string) | Set config filename without extension (searched across AddConfigPath dirs) |
SetConfigType | (in string) | Force a format (yaml/json/toml/dotenv/env) regardless of extension |
SetConfigPermissions | (perm os.FileMode) | File permissions for written configs (default 0644) |
AddConfigPath | (in string) | Add a directory to the config search path |
SetFs | (fs afero.Fs) | Replace the underlying filesystem abstraction |
ConfigFileUsed | () string | Return the path of the config file that was read |
Config read/write#
| Method | Signature | Description |
|---|
ReadInConfig | () error | Find and decode config file into v.config |
MergeInConfig | () error | Merge a config file on top of existing v.config |
ReadConfig | (in io.Reader) error | Decode from an arbitrary io.Reader |
MergeConfig | (in io.Reader) error | Merge from an arbitrary io.Reader |
MergeConfigMap | (cfg map[string]any) error | Merge from an in-memory map |
WriteConfig | () error | Write current config to the file set via SetConfigFile |
SafeWriteConfig | () error | Like WriteConfig but errors if file already exists |
WriteConfigAs | (filename string) error | Write to a specific path, overwriting if present |
WriteConfigTo | (w io.Writer) error | Encode current config to an io.Writer |
SafeWriteConfigAs | (filename string) error | Like WriteConfigAs but errors if file already exists |
Environment variables#
| Method | Signature | Description |
|---|
AutomaticEnv | () | Enable automatic env var lookup for every key (with prefix if set) |
SetEnvPrefix | (in string) | Prefix for automatic env lookups |
GetEnvPrefix | () string | Return the current prefix |
SetEnvKeyReplacer | (r *strings.Replacer) | Transform key to env var name (e.g. - → _) |
AllowEmptyEnv | (allowEmptyEnv bool) | Treat empty env var values as set (default: ignore empty) |
BindEnv | (input ...string) error | Bind a key to one or more env var names |
MustBindEnv | (input ...string) | Like BindEnv but panics on error |
Flags (pflag integration)#
| Method | Signature | Description |
|---|
BindPFlags | (flags *pflag.FlagSet) error | Bind all flags in a pflag.FlagSet |
BindPFlag | (key string, flag *pflag.Flag) error | Bind a single pflag.Flag to a key |
BindFlagValues | (flags FlagValueSet) error | Bind via the abstract FlagValueSet interface (pflag-agnostic) |
BindFlagValue | (key string, flag FlagValue) error | Bind a single abstract FlagValue to a key |
Remote key/value stores (via blank-imported viper/remote)#
| Function | Signature | Description |
|---|
AddRemoteProvider | (provider, endpoint, path string) error | Register a k/v backend (etcd/Consul/NATS/Firestore) |
AddSecureRemoteProvider | (provider, endpoint, path, secretkeyring string) error | Register an encrypted backend |
ReadRemoteConfig | () error | Fetch and decode config from registered remote providers |
WatchRemoteConfig | () error | Block and continuously sync remote config |
Aliases#
| Method | Signature | Description |
|---|
RegisterAlias | (alias, key string) | Register a key alias; lookups of alias resolve to key |
Value read API#
All Get* variants follow the same waterfall: override → changed pflags → env → config file → kvstore → defaults → pflag default.
| Function | Return type | Notes |
|---|
Get(key string) | any | Generic; callers must assert the type |
GetString(key) | string | |
GetBool(key) | bool | |
GetInt(key) | int | |
GetInt32(key) | int32 | |
GetInt64(key) | int64 | |
GetUint8(key) | uint8 | |
GetUint(key) | uint | |
GetUint16(key) | uint16 | |
GetUint32(key) | uint32 | |
GetUint64(key) | uint64 | |
GetFloat64(key) | float64 | |
GetTime(key) | time.Time | Via cast |
GetDuration(key) | time.Duration | Via cast |
GetIntSlice(key) | []int | |
GetStringSlice(key) | []string | |
GetStringMap(key) | map[string]any | Nested subsection |
GetStringMapString(key) | map[string]string | |
GetStringMapStringSlice(key) | map[string][]string | |
GetSizeInBytes(key) | uint | Parses human-readable sizes like "1MB" |
All type coercions go through github.com/spf13/cast, which gives silent best-effort conversion without panicking.
Navigation#
| Method | Signature | Description |
|---|
Sub | (key string) *Viper | Returns a new *Viper scoped to a nested key; all its values are relative |
AllKeys | () []string | Return all known keys (all layers, flattened) |
AllSettings | () map[string]any | Return a deep map snapshot of all resolved values |
IsSet | (key string) bool | True if the key exists in any layer at or above defaults |
InConfig | (key string) bool | True if the key exists specifically in the config file layer |
Value write API#
| Method | Signature | Description |
|---|
Set | (key string, value any) | Write to the override map (highest precedence) |
SetDefault | (key string, value any) | Write to the defaults map (lowest precedence) |
SetTypeByDefaultValue | (enable bool) | When true, type-coerce env/flag values to match the default’s type |
Unmarshal (struct binding) API#
| Function | Signature | Description |
|---|
Unmarshal | (rawVal any, opts ...DecoderConfigOption) error | Decode the full config into a struct using mapstructure |
UnmarshalKey | (key string, rawVal any, opts ...DecoderConfigOption) error | Decode a subsection into a struct |
UnmarshalExact | (rawVal any, opts ...DecoderConfigOption) error | Like Unmarshal but errors on unknown fields |
DecodeHook | (hook mapstructure.DecodeHookFunc) DecoderConfigOption | Return a DecoderConfigOption that overrides the decode hook |
The DecoderConfigOption type (func(*mapstructure.DecoderConfig)) is part of the public API, allowing callers to tune mapstructure’s behaviour without Viper exposing every knob.
Live reload API#
| Method | Signature | Description |
|---|
OnConfigChange | (run func(in fsnotify.Event)) | Register a callback invoked when the config file changes |
WatchConfig | () | Start a background goroutine (uses fsnotify) watching the config file directory |
WatchConfig handles Kubernetes ConfigMap atomic symlink swaps correctly by watching the directory rather than the file path.
Codec / extension API#
Interfaces#
| Interface | Methods | Purpose |
|---|
Encoder | Encode(v map[string]any) ([]byte, error) | Serialize Viper’s internal map to a format |
Decoder | Decode(b []byte, v map[string]any) error | Deserialize bytes into Viper’s internal map |
Codec | Encoder + Decoder | Combined encode/decode for a single format |
EncoderRegistry | Encoder(format string) (Encoder, error) | Look up an encoder by format name |
DecoderRegistry | Decoder(format string) (Decoder, error) | Look up a decoder by format name |
CodecRegistry | EncoderRegistry + DecoderRegistry | Combined registry |
FlagValue | HasChanged() bool; Name() string; ValueString() string; ValueType() string | Abstraction over pflag (or any flag library) |
FlagValueSet | VisitAll(fn func(FlagValue)) | Iterate over a set of flags |
Finder | Find(fsys afero.Fs) ([]string, error) | Locate config files in a filesystem |
StringReplacer | Replace(string) string | Transform env key names (satisfied by *strings.Replacer) |
FileLookupError | error + fileLookup() | Marker interface for config-file-not-found errors |
Concrete types#
| Type | Constructor | Purpose |
|---|
DefaultCodecRegistry | NewCodecRegistry() | Built-in codec registry; pre-wired with yaml/json/toml/dotenv |
| — | (r *DefaultCodecRegistry).RegisterCodec(format string, codec Codec) error | Register a custom codec at runtime |
Finders(finders ...Finder) Finder composes multiple Finder implementations into one (used with WithFinder).
Error types#
| Type | Implements | Description |
|---|
ConfigFileNotFoundError | FileLookupError (deprecated) | Wraps FileNotFoundFromSearchError; preserved for backward compat |
FileNotFoundFromSearchError | FileLookupError | Config file not found in any search path |
FileNotFoundError | FileLookupError | A specific config file path was not found |
ConfigFileAlreadyExistsError | error | Returned by SafeWriteConfig* when file exists |
ConfigMarshalError | error | Failed to encode the config (write path) |
UnsupportedConfigError | error | Format string has no registered codec |
Callers can use errors.As / errors.Is against these concrete types, or errors.As against FileLookupError as a common supertype for any not-found scenario.
Debugging#
| Method | Signature | Description |
|---|
Debug | () | Print a state dump to stdout |
DebugTo | (w io.Writer) | Print a state dump to an arbitrary writer |
Backward compatibility#
- No explicit versioning strategy (
v2 branch or go module major version suffix) in the current codebase; the module path is github.com/spf13/viper (v1). - Experimental features are introduced behind build-tag-gated compile-time feature flags (
internal/features), and are activated via opt-in Option values (ExperimentalFinder(), ExperimentalBindStruct()). This allows unstable APIs to be tested without polluting the stable surface and without any runtime overhead for users who do not opt in. ConfigFileNotFoundError was deprecated in favour of FileNotFoundFromSearchError; the old type is preserved and wraps the new one via Unwrap(), so errors.As chains work correctly for both old and new callers.
Key API design observations#
Dual-surface pattern (global + instance) is explicit and zero-cost. Every global function is a one-liner wrapper. The design avoids the common mistake of duplicating logic for global vs. instance forms.
FlagValue/FlagValueSet decouple the core from pflag. Consumers using other flag libraries (stdlib flag, urfave/cli, etc.) can implement the two-interface pair and bind without importing pflag. The concrete pflagValue/pflagValueSet wrappers ship with Viper but are not the only path.
io.Reader/io.Writer for config I/O. ReadConfig(io.Reader) and WriteConfigTo(io.Writer) accept arbitrary byte streams, making it trivial to feed Viper from embedded files, HTTP responses, or test strings — no file on disk required.
Sub(key) for scoped registries. Returns a new *Viper rooted at the given sub-key. All layer-awareness is preserved; this is the intended mechanism for component-local config without namespace collisions.
Codec registry as the sole extension point for formats. DefaultCodecRegistry.RegisterCodec is the only extension seam for adding new file formats (e.g., HCL, INI). The built-in four (yaml, json, toml, dotenv) share the same Codec interface and live in internal/encoding/* — swappable in tests or in production via WithCodecRegistry.