gohugoio/hugo · error

failed to decode related config: %w

Error message

failed to decode related config: %w

What it means

Emitted when the [related] config section is set but related.DecodeConfig cannot decode it into Hugo's related-content configuration (index definitions, thresholds, etc.). Hugo only decodes this section when explicitly present (otherwise it falls back to related.DefaultConfig), so this error always points at a user-authored [related] block.

Source

Thrown at config/allconfig/alldecoders.go:360

				for k, v := range m {
					if k == "" || v == "" {
						delete(m, k)
					}
				}
				p.c.Taxonomies = m
			}
			return nil
		},
	},
	"related": {
		key:    "related",
		weight: 100, // This needs to be decoded after taxonomies.
		decode: func(d decodeWeight, p decodeConfig) error {
			if p.p.IsSet(d.key) {
				var err error
				p.c.Related, err = related.DecodeConfig(p.p.GetParams(d.key))
				if err != nil {
					return fmt.Errorf("failed to decode related config: %w", err)
				}
			} else {
				p.c.Related = related.DefaultConfig
				if _, found := p.c.Taxonomies["tag"]; found {
					p.c.Related.Add(related.IndexConfig{Name: "tags", Weight: 80, Type: related.TypeBasic})
				}
			}
			return nil
		},
	},

	"cascade": {
		key: "cascade",
		decode: func(d decodeWeight, p decodeConfig) error {
			var err error
			p.c.Cascade, err = page.DecodeCascadeConfig(p.p.Get(d.key))
			return err
		},

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Structure indices as an array of tables: [[related.indices]] entries each with name/weight, not fields directly under [related].
  2. Make threshold, weight and toLower the correct types (numbers/booleans, not strings).
  3. Compare your block against the current docs at gohugo.io/content-management/related/ for your Hugo version.
  4. Delete the [related] section entirely to fall back to Hugo's sensible defaults if you don't need customization.

Example fix

# before
[related]
name = 'keywords'
weight = '100'
# after
[related]
threshold = 80
[[related.indices]]
name = 'keywords'
weight = 100
Defensive patterns

Strategy: validation

Validate before calling

// related config must decode into related.Config: check shape first
rel, ok := cfg.Get("related").(map[string]any)
if ok {
    if idx, ok := rel["indices"]; ok {
        if _, ok := idx.([]any); !ok {
            return errors.New("related.indices must be an array of tables")
        }
    }
}

Try / catch

conf, err := allconfig.LoadConfig(opts)
if err != nil && strings.Contains(err.Error(), "related config") {
    // point the user at the [related] section specifically
    return err
}

Prevention

When it happens

Trigger: A [related] section whose shape related.DecodeConfig rejects: 'indices' not being a list of maps, an index entry missing 'name', invalid 'type' values, non-numeric threshold/weight values, or wrong nesting such as putting index fields directly under [related] instead of [[related.indices]].

Common situations: Copying a related-content snippet between YAML and TOML and losing the array-of-tables structure ([[related.indices]] in TOML vs a YAML list); setting threshold or weight as strings; using fields from newer Hugo versions (e.g. type = 'fragments' options, cardinalityThreshold) on an older Hugo.

Related errors


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/da111fb8c783e624.json. Report an issue: GitHub ↗.