gohugoio/hugo · error

permalinks: unsupported config type %T

Error message

permalinks: unsupported config type %T

What it means

Hugo's DecodePermalinksConfig accepts either the legacy map-based permalinks format or the newer slice-of-maps format. When the value under the `permalinks` config key is neither a map (map[string]any / Params) nor convertible to []map[string]any via hmaps.ToSliceStringMap, decoding fails with this error, reporting the Go type it received. It fires at config-load time, aborting the build.

Source

Thrown at resources/page/permalinks.go:531

// DecodePermalinksConfig decodes the permalinks configuration.
// It supports both the new slice-based format and the legacy map-based formats.
func DecodePermalinksConfig(in any) (PermalinksConfig, error) {
	if in == nil {
		return nil, nil
	}

	switch v := in.(type) {
	// Check legacy formats first.
	case map[string]any:
		return decodePermalinksMap(v)
	case hmaps.Params:
		return decodePermalinksMap(v)
	default:
		if ms, err := hmaps.ToSliceStringMap(in); err == nil {
			// New slice format.
			return decodePermalinksSlice(ms)
		}
		return nil, fmt.Errorf("permalinks: unsupported config type %T", in)
	}
}

func decodePermalinksSlice(ms []map[string]any) (PermalinksConfig, error) {
	var configs PermalinksConfig
	for _, m := range ms {
		m = hmaps.CleanConfigStringMap(m)
		var cfg PermalinkConfig

		if targetVal, ok := m["target"]; ok {
			if err := mapstructure.WeakDecode(targetVal, &cfg.Target); err != nil {
				return nil, fmt.Errorf("permalinks: failed to decode target: %w", err)
			}
			cfg.Target.Kind = strings.ToLower(cfg.Target.Kind)
			cfg.Target.Path = filepath.ToSlash(strings.ToLower(cfg.Target.Path))
		}

		if patternVal, ok := m["pattern"]; ok {

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Make `permalinks` a map of section→pattern, e.g. `[permalinks]\nposts = '/:year/:month/:slug/'`.
  2. Or use the new slice format: an array of tables each with `pattern` (and optional `target`).
  3. Check YAML indentation so the value parses as a mapping, not a scalar or string list.

Example fix

# before (hugo.toml)
permalinks = "/:year/:slug/"

# after
[permalinks]
  posts = "/:year/:slug/"
Defensive patterns

Strategy: type-guard

Validate before calling

// site config: ensure permalinks is a map of string→string or string→map
// permalinks:
//   page:
//     posts: /:year/:slug/

Type guard

func validPermalinksConfig(v any) bool {
    switch m := v.(type) {
    case map[string]any:
        for _, val := range m {
            switch val.(type) {
            case string, map[string]any:
            default:
                return false
            }
        }
        return true
    }
    return false
}

Prevention

When it happens

Trigger: Setting `permalinks` in hugo.toml/yaml/json to a scalar (string, number, bool) or a plain array of strings instead of a table/map or an array of tables; e.g. `permalinks = 'posts/:year/:slug'` at the top level, or passing a wrong type when composing config programmatically.

Common situations: Typos flattening the config (forgetting the section key so the pattern string sits directly under permalinks); YAML indentation errors turning a mapping into a scalar or list of strings; migrating between the legacy map format and the new slice format and mixing shapes.

Related errors


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