gohugoio/hugo · error

permalinks: pattern must be a string, got %T

Error message

permalinks: pattern must be a string, got %T

What it means

Thrown by decodePermalinksSlice when a permalink rule has a 'pattern' key but its value is not a string (the type assertion patternVal.(string) fails). Patterns must be strings containing permalink tokens like /:year/:slug/.

Solutions

  1. Set 'pattern' to a single string value.
  2. If you need multiple patterns, add separate [[permalinks]] rules instead.
  3. Double-check quoting in YAML/TOML so the pattern is parsed as a string.

Example fix

# before
[[permalinks]]
target = { path = "/posts/**" }
pattern = ["/:year/:slug/"]
# after
[[permalinks]]
target = { path = "/posts/**" }
pattern = "/:year/:slug/"
Defensive patterns

Strategy: type-guard

Validate before calling

if pv, ok := m["pattern"]; ok {
    if _, ok := pv.(string); !ok {
        return fmt.Errorf("pattern must be string, got %T", pv)
    }
}

Type guard

func patternIsString(m map[string]any) bool {
    v, ok := m["pattern"]
    return ok && reflect.TypeOf(v).Kind() == reflect.String
}

Try / catch

cfg, err := page.DecodePermalinksConfig(raw)
if err != nil { /* quote pattern / split into multiple rules */ }

Prevention

When it happens

Trigger: A list-format permalink rule writes pattern as a number, list, or map, e.g. pattern = ["/:year/:slug/"] (a list) or pattern = 42.

Common situations: Quoting mistakes, YAML list-ifying a single pattern, or JSON config where the pattern value is an array.

Related errors


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

Appendix: source

Thrown at resources/page/permalinks.go:552

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 {
			cfg.Pattern, ok = patternVal.(string)
			if !ok {
				return nil, fmt.Errorf("permalinks: pattern must be a string, got %T", patternVal)
			}
		} else {
			return nil, fmt.Errorf("permalinks: missing pattern")
		}

		configs = append(configs, cfg)
	}

	return configs, nil
}

func decodePermalinksMap(m map[string]any) (PermalinksConfig, error) {
	var configs PermalinksConfig
	config := hmaps.CleanConfigStringMap(m)

	for k, v := range config {
		switch v := v.(type) {
		case string:

View on GitHub (pinned to 52c9bd7908)