spf13/viper · error

cannot decode configuration: unable to determine config type

Error message

cannot decode configuration: unable to determine config type

What it means

Returned by unmarshalReader (viper.go:1781-1785) when getConfigType() returns "". getConfigType returns "" if v.configType is unset and either getConfigFile errors (no file located) or the resolved filename has no extension (viper.go:2121-2137). Without a format, Viper cannot pick a decoder.

Source

Thrown at viper.go:1784

		flags |= os.O_EXCL
	}
	f, err := v.fs.OpenFile(filename, flags, v.configPermissions)
	if err != nil {
		return err
	}
	defer f.Close()

	if err := v.marshalWriter(f, configType); err != nil {
		return err
	}

	return f.Sync()
}

func (v *Viper) unmarshalReader(in io.Reader, c map[string]any) error {
	format := strings.ToLower(v.getConfigType())
	if format == "" {
		return errors.New("cannot decode configuration: unable to determine config type")
	}

	buf := new(bytes.Buffer)
	_, err := buf.ReadFrom(in)
	if err != nil {
		return fmt.Errorf("failed to read configuration from input: %w", err)
	}

	// TODO: remove this once SupportedExts is deprecated/removed
	if !slices.Contains(SupportedExts, format) {
		return UnsupportedConfigError(format)
	}

	// TODO: return [UnsupportedConfigError] if the registry does not contain the format
	// TODO: consider deprecating this error type
	decoder, err := v.decoderRegistry.Decoder(format)
	if err != nil {
		return ConfigParseError{err}

View on GitHub (pinned to 528f7416c4)

Solutions

  1. Call v.SetConfigType("yaml") (or json/toml/env) before v.ReadConfig(reader).
  2. If reading from disk, ensure SetConfigName + AddConfigPath resolve a file with a recognized extension so getConfigType can infer it.
  3. For remote providers, call SetConfigType after AddRemoteProvider.

Example fix

// before
v := New()
_ = v.ReadConfig(strings.NewReader(`{"port": 8080}`)) // -> cannot decode configuration: unable to determine config type

// after
v := New()
v.SetConfigType("json")
_ = v.ReadConfig(strings.NewReader(`{"port": 8080}`))
Defensive patterns

Strategy: validation

Validate before calling

// getConfigType() is unexported; mirror its logic to validate before reading.
func hasConfigType(v *viper.Viper, readerOnly bool) bool {
    if v.GetConfigType() != "" { return true }
    if readerOnly { return false } // no filename to infer from
    // rely on SetConfigName + AddConfigPath resolving an extensioned file
    return false
}

// before ReadConfig(reader):
if v.GetConfigType() == "" {
    v.SetConfigType("yaml") // or json/toml/env
}
v.ReadConfig(reader)

Try / catch

if err := v.ReadConfig(r); err != nil {
    if strings.Contains(err.Error(), "unable to determine config type") {
        v.SetConfigType("yaml")
        err = v.ReadConfig(r)
    }
}

Prevention

When it happens

Trigger: Calling v.ReadConfig(reader) or v.ReadConfigAs(reader) without first calling v.SetConfigType(...). Also when WatchConfig fires on a Viper instance whose config file has no recognized extension and SetConfigType was never called, or when reading remote/env config with no type set.

Common situations: Reading config from an io.Reader (string, bytes, env blob, remote KV) where there is no filename to infer the type from; refactoring that removed the SetConfigType call; reading a file literally named 'config' with no extension.

Related errors


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