gohugoio/hugo · error
operator argument must be string type
Error message
operator argument must be string type
What it means
When `where` is given two trailing arguments (operator + match value), the first must be a string so it can be used as the operator. `parseWhereArgs` at where.go:395 type-asserts `args[0].(string)`; if it fails (e.g. an int, struct, or slice is passed as the operator position), this error fires.
Solutions
- Insert the operator string as the first trailing argument: `{{ where .Pages "Date" "ge" $startDate }}`.
- If only a match value is intended (implicit equality), pass exactly one trailing argument: `{{ where .Pages "Section" "blog" }}`.
- Verify the operator variable is always a string before the call.
Example fix
// before
{{ where .Pages "Date" $start $end }}
// after
{{ where .Pages "Date" "ge" $start }} Defensive patterns
Strategy: type-guard
Type guard
func isStringOperator(args []any) bool {
if len(args) != 2 { return true }
_, ok := args[0].(string)
return ok
} Prevention
- When passing two trailing args, the first must be a string operator.
- For implicit equality, pass exactly one match value.
- Double-check variable types in the operator slot.
When it happens
Trigger: Calling `{{ where .Pages "Date" 20240101 20241231 }}` where the operator position holds a number instead of a string operator, or passing a variable that is not a string in the operator slot.
Common situations: A developer intends a two-value comparison but forgets the operator string, effectively passing two match values. The second value lands in the operator position and fails the string assertion.
Related errors
- can't iterate over %T
- arguments must be slices or arrays
- arguments to complement must be slices or arrays
- arguments to symdiff must be slices or arrays
- can't evaluate an invalid value
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/2ec9fb27aba1ebca.
Report an issue: GitHub.
Appendix: source
Thrown at tpl/collections/where.go:396
kv := reflect.ValueOf(elemName)
if kv.Type().AssignableTo(obj.Type().Key()) {
return obj.MapIndex(kv), nil
}
return zero, fmt.Errorf("%s isn't a key of map type %s", elemName, typ)
}
return zero, fmt.Errorf("%s is neither a struct field, a method nor a map element of type %s", elemName, typ)
}
// parseWhereArgs parses the end arguments to the where function. Return a
// match value and an operator, if one is defined.
func parseWhereArgs(args ...any) (mv reflect.Value, op string, err error) {
switch len(args) {
case 1:
mv = reflect.ValueOf(args[0])
case 2:
var ok bool
if op, ok = args[0].(string); !ok {
err = errors.New("operator argument must be string type")
return
}
op = strings.TrimSpace(strings.ToLower(op))
mv = reflect.ValueOf(args[1])
default:
err = errors.New("can't evaluate the array by no match argument or more than or equal to two arguments")
}
return
}
// elemResolver resolves a sub-element from a reflect.Value.
// Built once before the loop to avoid repeated type checks and reflect.ValueOf allocations.
type elemResolver func(reflect.Value) (reflect.Value, error)
// newElemResolver returns a resolver optimized for the given element type and path.
// Returns nil if optimization isn't possible, in which case the caller should
// fall back to evaluateSubElem.
func (ns *Namespace) newElemResolver(ctxv reflect.Value, elemType reflect.Type, path []string) elemResolver {View on GitHub (pinned to 52c9bd7908)