gohugoio/hugo · error

failed convert config to map: %s

Error message

failed convert config to map: %s

What it means

Thrown by output.DecodeConfig (output/config.go:47) when hmaps.ToStringMapE(in) fails to convert the 'outputFormats' configuration value into a map[string]any. The %s inserts the cast error. Hugo expects the [outputFormats] section to be a map keyed by format name; this error means the top-level value is an array, a scalar, or otherwise not a map.

Source

Thrown at output/config.go:47

type OutputFormatConfig struct {
	// The MediaType string. This must be a configured media type.
	MediaType string
	Format
}

var defaultOutputFormat = Format{
	BaseName: "index",
	Rel:      "alternate",
}

func DecodeConfig(mediaTypes media.Types, in any) (*config.ConfigNamespace[map[string]OutputFormatConfig, Formats], error) {
	buildConfig := func(in any) (Formats, any, error) {
		f := make(Formats, len(DefaultFormats))
		copy(f, DefaultFormats)
		if in != nil {
			m, err := hmaps.ToStringMapE(in)
			if err != nil {
				return nil, nil, fmt.Errorf("failed convert config to map: %s", err)
			}
			m = hmaps.CleanConfigStringMap(m)

			for k, v := range m {
				found := false
				for i, vv := range f {
					// Both are lower case.
					if k == vv.Name {
						// Merge it with the existing
						if err := decode(mediaTypes, v, &f[i]); err != nil {
							return f, nil, err
						}
						found = true
					}
				}
				if found {
					continue
				}

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Ensure [outputFormats] in hugo.toml is a table/map: [outputFormats.NAME] with each format as a sub-table, not an inline array.
  2. In YAML config use outputFormats: { NAME: {...} } rather than outputFormats: [{...}].
  3. Run hugo config to dump the resolved config and confirm outputFormats is a map.
  4. Check any --config overrides or theme config mounts for a conflicting non-map definition.

Example fix

# before (array instead of map)
outputFormats = [
  { name = "MYFORMAT", mediaType = "text/html" }
]

# after (map keyed by name)
[outputFormats.MYFORMAT]
mediaType = "text/html"
baseName = "index"
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the outputFormats config value is a map before DecodeConfig.
func ensureOutputFormatsMap(in any) error {
    if in == nil { return nil }
    if _, ok := in.(map[string]any); !ok {
        return fmt.Errorf("outputFormats must be a map, got %T", in)
    }
    return nil
}

Type guard

func isConfigMap(in any) bool {
    _, ok := in.(map[string]any)
    return ok || in == nil
}

Try / catch

ns, err := output.DecodeConfig(mediaTypes, raw)
if err != nil {
    return fmt.Errorf("invalid outputFormats config: %w", err)
}

Prevention

When it happens

Trigger: Defining [outputFormats] as an array (outputFormats = [{name="x"}]) or a scalar in hugo.toml; loading a config via a source that yields a slice instead of a map; a config merge/overlay producing a non-map at that key.

Common situations: Migrating from an older Hugo config syntax that used a different shape; typos in TOML that create an array-of-tables instead of a table; YAML config using a list where a mapping is expected; environment-injected config overrides that replace the map with a scalar.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/ea5c0bbe4640e033. Report an issue: GitHub.