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

  1. Use the slice form with a source: `{{ strings.ReplacePairs (slice "old" "new") $src }}`.
  2. For a single inline pair, supply three arguments: `{{ strings.ReplacePairs "old" "new" $src }}`.
  3. 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

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


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