gohugoio/hugo · error

modulo operator can't be used with non integer value

Error message

modulo operator can't be used with non integer value

What it means

Hugo's `math.Mod` template function (Namespace.Mod in tpl/math/math.go) casts both operands to int64 via `cast.ToInt64E` before computing `n1 % n2`. If either cast fails — because the value is a non-numeric string, a nil, a map/slice, or otherwise not integer-convertible — Hugo returns this error instead of guessing. `ModBool` delegates to `Mod`, so it surfaces the same error.

Source

Thrown at tpl/math/math.go:166

}

// MaxInt64 returns the maximum value for a signed 64-bit integer.
func (ns *Namespace) MaxInt64() int64 {
	return math.MaxInt64
}

// Min returns the smaller of all numbers in inputs. Any slices in inputs are flattened.
func (ns *Namespace) Min(inputs ...any) (minimum float64, err error) {
	return ns.applyOpToScalarsOrSlices("Min", math.Min, inputs...)
}

// Mod returns n1 % n2.
func (ns *Namespace) Mod(n1, n2 any) (int64, error) {
	ai, erra := cast.ToInt64E(n1)
	bi, errb := cast.ToInt64E(n2)

	if erra != nil || errb != nil {
		return 0, errors.New("modulo operator can't be used with non integer value")
	}

	if bi == 0 {
		return 0, errors.New("the number can't be divided by zero at modulo operation")
	}

	return ai % bi, nil
}

// ModBool returns the boolean of n1 % n2.  If n1 % n2 == 0, return true.
func (ns *Namespace) ModBool(n1, n2 any) (bool, error) {
	res, err := ns.Mod(n1, n2)
	if err != nil {
		return false, err
	}

	return res == int64(0), nil
}

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Ensure both operands are numeric: convert with `{{ mod (int $a) (int $b) }}` when the value may be a string.
  2. Guard against missing params: `{{ with .Params.count }}{{ mod (int .) 2 }}{{ end }}` or provide a default: `{{ mod (int (default 0 .Params.count)) 2 }}`.
  3. Fix the data source so numbers are unquoted in JSON/YAML/TOML front matter.
  4. When using shortcode params (`.Get`), remember they are strings — always wrap in `int`.

Example fix

<!-- before -->
{{ if modBool .Params.rank 2 }}even{{ end }}
<!-- after -->
{{ if modBool (int (default 0 .Params.rank)) 2 }}even{{ end }}
Defensive patterns

Strategy: type-guard

Validate before calling

{{ if and (eq (printf "%T" $a) "int") (eq (printf "%T" $b) "int") }}{{ mod $a $b }}{{ end }}

Type guard

// Go caller side
func isInt(v any) bool {
    switch v.(type) {
    case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
        return true
    }
    return false
}

Try / catch

if _, err := ns.ModBool(a, b); err != nil {
    // fall back or surface template error
}

Prevention

When it happens

Trigger: Calling `{{ mod A B }}` or `{{ modBool A B }}` in a Hugo template where A or B is not convertible to int64: e.g. `{{ mod "abc" 3 }}`, `{{ mod .Params.count 2 }}` when the front-matter param is missing (nil) or a string like "10px", or passing a float string that cast.ToInt64E rejects. Note spf13/cast truncates plain floats, so the usual trigger is strings/nil, not floats.

Common situations: Alternating row striping with `{{ if modBool $index 2 }}` where the index variable is accidentally a string from a data file (JSON/YAML numbers quoted); front-matter params typed as strings; `.Params` lookups that return nil because the key is absent or cased differently; values piped from `printf` or `.Get` shortcode params which are always strings.

Related errors


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