{"id":"1a62d30630e04a7e","repo":"gohugoio/hugo","slug":"permalinks-pattern-must-be-a-string-got-t","errorCode":null,"errorMessage":"permalinks: pattern must be a string, got %T","messagePattern":"permalinks: pattern must be a string, got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"resources/page/permalinks.go","lineNumber":552,"sourceCode":"\nfunc decodePermalinksSlice(ms []map[string]any) (PermalinksConfig, error) {\n\tvar configs PermalinksConfig\n\tfor _, m := range ms {\n\t\tm = hmaps.CleanConfigStringMap(m)\n\t\tvar cfg PermalinkConfig\n\n\t\tif targetVal, ok := m[\"target\"]; ok {\n\t\t\tif err := mapstructure.WeakDecode(targetVal, &cfg.Target); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"permalinks: failed to decode target: %w\", err)\n\t\t\t}\n\t\t\tcfg.Target.Kind = strings.ToLower(cfg.Target.Kind)\n\t\t\tcfg.Target.Path = filepath.ToSlash(strings.ToLower(cfg.Target.Path))\n\t\t}\n\n\t\tif patternVal, ok := m[\"pattern\"]; ok {\n\t\t\tcfg.Pattern, ok = patternVal.(string)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"permalinks: pattern must be a string, got %T\", patternVal)\n\t\t\t}\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"permalinks: missing pattern\")\n\t\t}\n\n\t\tconfigs = append(configs, cfg)\n\t}\n\n\treturn configs, nil\n}\n\nfunc decodePermalinksMap(m map[string]any) (PermalinksConfig, error) {\n\tvar configs PermalinksConfig\n\tconfig := hmaps.CleanConfigStringMap(m)\n\n\tfor k, v := range config {\n\t\tswitch v := v.(type) {\n\t\tcase string:","sourceCodeStart":534,"sourceCodeEnd":570,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/resources/page/permalinks.go#L534-L570","documentation":"In the new slice-based permalinks format, each entry's `pattern` key must hold a string. decodePermalinksSlice does a direct type assertion `patternVal.(string)` and errors with the actual Go type if the value is anything else. This is a strict check — no coercion of numbers, booleans, or nested maps is attempted.","triggerScenarios":"An array-format permalinks entry where `pattern` is a non-string, e.g. `pattern = 2024`, `pattern = true`, an array, or an inline table; also YAML values that parse as numbers/dates instead of strings when unquoted.","commonSituations":"Unquoted YAML values that the parser types as int/bool/date; accidentally nesting a map under `pattern`; generating config from templates/scripts that emit typed values.","solutions":["Quote the pattern so it parses as a string: `pattern = \"/:year/:slug/\"`.","Ensure each entry in the permalinks array has `pattern` as a plain string, with target options under `target` instead."],"exampleFix":"# before\n[[permalinks]]\npattern = 2024\n\n# after\n[[permalinks]]\npattern = \"/2024/:slug/\"","handlingStrategy":"type-guard","validationCode":"// each permalink value must be a string:\n// [permalinks.page]\n// posts = \"/:year/:month/:slug/\"","typeGuard":"func isStringPattern(v any) bool { _, ok := v.(string); return ok }","tryCatchPattern":null,"preventionTips":["Always quote permalink patterns so YAML/TOML doesn't coerce them (e.g. bare `/:year/` may parse oddly)","Avoid nesting deeper than kind→section→string","Lint config in CI with `hugo config --format json | jq '.permalinks'`"],"tags":["hugo","config","permalinks","type-error"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}