usememos/memos · error

endsWith argument must be a string

Error message

endsWith argument must be a string

What it means

The argument to endsWith() in a comprehension predicate must be a compile-time string constant. A constant of any other type (int, bool, timestamp, ...) triggers this error; non-constant operands surface as the wrapped 'endsWith argument must be a constant string'.

Source

Thrown at internal/filter/parser.go:993

// buildEndsWithPredicate extracts the pattern from t.endsWith("suffix").
func buildEndsWithPredicate(call *exprv1.Expr_Call, iterVar string) (PredicateExpr, error) {
	if target := call.Target.GetIdentExpr(); target == nil || target.GetName() != iterVar {
		return nil, errors.Errorf("endsWith target must be the iteration variable %q", iterVar)
	}

	if len(call.Args) != 1 {
		return nil, errors.New("endsWith expects exactly one argument")
	}

	suffix, err := getConstValue(call.Args[0])
	if err != nil {
		return nil, errors.Wrap(err, "endsWith argument must be a constant string")
	}

	suffixStr, ok := suffix.(string)
	if !ok {
		return nil, errors.New("endsWith argument must be a string")
	}

	return &EndsWithPredicate{Suffix: suffixStr}, nil
}

// buildContainsPredicate extracts the pattern from t.contains("substring").
func buildContainsPredicate(call *exprv1.Expr_Call, iterVar string) (PredicateExpr, error) {
	if target := call.Target.GetIdentExpr(); target == nil || target.GetName() != iterVar {
		return nil, errors.Errorf("contains target must be the iteration variable %q", iterVar)
	}

	if len(call.Args) != 1 {
		return nil, errors.New("contains expects exactly one argument")
	}

	substring, err := getConstValue(call.Args[0])
	if err != nil {
		return nil, errors.Wrap(err, "contains argument must be a constant string")

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. Quote the suffix: tag.exists(t, t.endsWith(".draft"))
  2. Check the field is a string list before using endsWith

Example fix

// before
`tag.exists(t, t.endsWith(2024))`

// after
`tag.exists(t, t.endsWith(".draft"))`
Defensive patterns

Strategy: validation

Validate before calling

var badEndsWith = regexp.MustCompile(`endsWith\s*\(\s*[^"']`)

func validateEndsWith(expr string) error {
    if badEndsWith.MatchString(expr) {
        return errors.New("endsWith needs a quoted string suffix")
    }
    return nil
}

Try / catch

if err := r.Render(cond); err != nil {
    if strings.Contains(err.Error(), "endsWith argument must be a string") {
        return userErr("quote the endsWith suffix, e.g. t.endsWith(\".draft\")")
    }
    return err
}

Prevention

When it happens

Trigger: tag.exists(t, t.endsWith(2024)) or tag.all(t, t.endsWith(true)) — non-string literal passed to endsWith.

Common situations: Unquoted suffix values; users copying numeric suffix patterns from docs of other systems.

Related errors


AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15). Data as JSON: /api/errors/83124ef7e81c2040. Report an issue: GitHub.