spf13/viper · error

decoder not found for this format

Error message

decoder not found for this format

What it means

Returned by DefaultCodecRegistry.Decoder (encoding.go:144-150) when r.codec(format) finds no match. Same codec table as the encoder: only yaml/yml, json, toml, dotenv/env are built in. It surfaces through unmarshalReader (viper.go:1800) wrapped as ConfigParseError whenever Viper reads/parses a config stream.

Source

Thrown at encoding.go:147

// Encoder implements the [EncoderRegistry] interface.
//
// Format is case-insensitive.
func (r *DefaultCodecRegistry) Encoder(format string) (Encoder, error) {
	encoder, ok := r.codec(format)
	if !ok {
		return nil, errors.New("encoder not found for this format")
	}

	return encoder, nil
}

// Decoder implements the [DecoderRegistry] interface.
//
// Format is case-insensitive.
func (r *DefaultCodecRegistry) Decoder(format string) (Decoder, error) {
	decoder, ok := r.codec(format)
	if !ok {
		return nil, errors.New("decoder not found for this format")
	}

	return decoder, nil
}

func (r *DefaultCodecRegistry) codec(format string) (Codec, bool) {
	r.mu.Lock()
	defer r.mu.Unlock()

	format = strings.ToLower(format)

	if r.codecs != nil {
		codec, ok := r.codecs[format]
		if ok {
			return codec, true
		}
	}

View on GitHub (pinned to 528f7416c4)

Solutions

  1. Convert the config to a built-in format (json/yaml/toml/env) or read from one.
  2. Register a decoder for the format: r := NewCodecRegistry(); r.RegisterCodec("ini", iniCodec{}); v := NewWithOptions(WithDecoderRegistry(r)).
  3. Double-check that SetConfigType matches an actually-registered codec, not merely an entry in SupportedExts (which lists more formats than the default registry supports).

Example fix

// before
v.SetConfigType("ini")
_ = v.ReadConfig(bytes.NewReader(data)) // -> ConfigParseError: decoder not found for this format

// after
r := NewCodecRegistry()
_ = r.RegisterCodec("ini", myIniCodec{})
v := NewWithOptions(WithDecoderRegistry(r))
v.SetConfigType("ini")
_ = v.ReadConfig(bytes.NewReader(data))
Defensive patterns

Strategy: validation

Validate before calling

var hasBuiltinDecoder = map[string]bool{"json": true, "yaml": true, "yml": true, "toml": true, "env": true, "dotenv": true}
func canDecode(format string) bool { return hasBuiltinDecoder[strings.ToLower(format)] }

// usage before ReadConfig
cfgType := strings.ToLower(v.GetConfigType())
if cfgType == "" { cfgType = strings.TrimPrefix(filepath.Ext(configPath), ".") }
if !canDecode(cfgType) {
    // register a decoder or convert the source to a built-in format
}

Try / catch

if err := v.ReadConfig(r); err != nil {
    var parseErr viper.ConfigParseError
    if errors.As(err, &parseErr) && strings.Contains(parseErr.Error(), "decoder not found") {
        // format lacks a registered decoder; register one or change SetConfigType
    }
}

Prevention

When it happens

Trigger: Calling ReadConfig, ReadConfigAs, WatchConfig (on change), or unmarshaling a remote/env source whose getConfigType() resolves to a format with no built-in decoder — 'ini', 'hcl', 'tfvars', 'properties', 'props', 'prop' — or a custom format with no codec registered via WithDecoderRegistry.

Common situations: Reading a legacy .ini/.hcl/.properties config file after a Viper upgrade that dropped the bundled parser; remote KV store (consul/etcd) with SetConfigType("hcl"); custom decoder registry missing RegisterCodec for the format you read.

Related errors


AI-assisted analysis of spf13/viper@528f7416c4 (2026-08-04). Data as JSON: /data/errors/954661f2785aa203.json. Report an issue: GitHub.