gohugoio/hugo · error
with 2 arguments, the first must be a slice of replacement p
Error message
with 2 arguments, the first must be a slice of replacement pairs, got %T
What it means
When `strings.ReplacePairs` receives exactly 2 arguments, it interprets them as `(pairs-slice, source-string)`. It checks the first argument with `hreflect.IsSlice`; if it isn't a slice, the call is ambiguous — two bare strings can't form both a complete pair and a source — so Hugo errors, reporting the actual Go type it received via `%T`.
Source
Thrown at tpl/strings/strings.go:277
// ReplacePairs returns a copy of a string with multiple replacements performed
// in a single pass. The last argument is the source string. Preceding arguments
// are old/new string pairs, either as a slice or as individual arguments.
func (ns *Namespace) ReplacePairs(args ...any) (string, error) {
if len(args) < 2 {
return "", fmt.Errorf("requires at least 2 arguments")
}
ss, err := cast.ToStringE(args[len(args)-1])
if err != nil {
return "", err
}
var p []string
if len(args) == 2 {
// slice form: ReplacePairs (slice "a" "b") "s"
if !hreflect.IsSlice(args[0]) {
return "", fmt.Errorf("with 2 arguments, the first must be a slice of replacement pairs, got %T", args[0])
}
p, err = cast.ToStringSliceE(args[0])
if err != nil {
return "", err
}
}
if p == nil {
// inline form: ReplacePairs "a" "b" "s"
p = make([]string, len(args)-1)
for i, v := range args[:len(args)-1] {
s, err := cast.ToStringE(v)
if err != nil {
return "", err
}
p[i] = s
}
}
View on GitHub ↗ (pinned to 8a468df065)
Solutions
- Use the slice form with a source: `{{ strings.ReplacePairs (slice "old" "new") $src }}`.
- For a single inline pair, supply three arguments: `{{ strings.ReplacePairs "old" "new" $src }}`.
- If pairs come from front matter, define them as a YAML list (`pairs: ["a","b"]`), not a map — the error's %T output tells you what type actually arrived.
Example fix
<!-- before -->
{{ strings.ReplacePairs "old" "new" }}
<!-- after -->
{{ strings.ReplacePairs "old" "new" .Content }} Defensive patterns
Strategy: type-guard
Validate before calling
{{ $pairs := slice "a" "b" "c" "d" }}{{ if reflect.IsSlice $pairs }}{{ strings.Replacer $s $pairs }}{{ end }} Type guard
func isPairSlice(v any) bool {
rv := reflect.ValueOf(v)
return rv.Kind() == reflect.Slice && rv.Len()%2 == 0
} Try / catch
out, err := ns.Replacer(s, pairs)
if err != nil {
// pairs wasn't a slice — check %T in the message and fix the data shape
} Prevention
- In the 2-argument form, pass a flat slice (`slice "old" "new" ...`), not a map or string
- Use `reflect.IsSlice` in templates to guard values loaded from params/data files
- Prefer the variadic form with literal arguments when pairs are static
When it happens
Trigger: `{{ strings.ReplacePairs "old" "new" }}` — two plain strings with no source; `{{ strings.ReplacePairs $pairs $src }}` where $pairs is actually a string, map/dict, or nil rather than a slice; passing a dict (`dict "a" "b"`) instead of `slice "a" "b"`.
Common situations: Thinking the function works like `replace` (old, new) and forgetting the source string; building pairs with `dict` instead of `slice`; a front-matter param expected to be a YAML list but written as a string or map.
Related errors
- failed to convert content to string: %w
- requires at least 2 arguments
- uneven number of replacement pairs
- modulo operator can't be used with non integer value
- Pow operator can't be used with non-float value
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/0ca229cf4857dbbe.json.
Report an issue: GitHub ↗.