usememos/memos · error
text match argument must be a string
Error message
text match argument must be a string
What it means
After verifying a text-match call has exactly one argument, the parser requires that argument to be a string literal. getConstValue succeeds for ints, doubles, bools, and strings; anything other than a string cannot be compiled into the SQL text-match predicate, so the parse fails with this error.
Source
Thrown at internal/filter/parser.go:336
}
field, ok := schema.Field(targetName)
if !ok {
return nil, errors.Errorf("unknown identifier %q", targetName)
}
if !field.SupportsContains {
return nil, errors.Errorf("identifier %q does not support text matching", targetName)
}
if len(call.Args) != 1 {
return nil, errors.New("text match expects exactly one argument")
}
value, err := getConstValue(call.Args[0])
if err != nil {
return nil, errors.Wrap(err, "text match only supports literal arguments")
}
str, ok := value.(string)
if !ok {
return nil, errors.New("text match argument must be a string")
}
return &TextMatchCondition{
Field: targetName,
Mode: mode,
Value: str,
}, nil
}
func buildMatchesCondition(call *exprv1.Expr_Call, schema Schema) (Condition, error) {
if call.Target == nil {
return nil, errors.New("matches requires a target")
}
targetName, err := getIdentName(call.Target)
if err != nil {
return nil, err
}
field, ok := schema.Field(targetName)View on GitHub (pinned to 14d757ce1f)
Solutions
- Quote the argument so it is a string literal: content.contains("42")
- If interpolating values from code, serialize them as JSON strings (strconv.Quote / JSON.stringify) before embedding
- Use a plain comparison (row_status == 1) for non-text fields instead of a text matcher
Example fix
// before
content.contains(42)
// after
content.contains("42") Defensive patterns
Strategy: validation
Validate before calling
// TS: ensure interpolated text-match args are quoted strings
const arg = String(value);
if (!/^"(?:[^"\\]|\\.)*"$/.test(JSON.stringify(arg).replace(/^"|"$/g, '"'))) {
throw new Error('argument must be a quoted string');
}
const filter = `content.contains(${JSON.stringify(String(value))})`; Type guard
const isStringLiteral = (v: unknown): v is string => typeof v === 'string';
Try / catch
try { const conds = parseFilter(expr); } catch (e) { if (String(e).includes('must be a string')) showUserError('text match needs a quoted string'); else throw e; } Prevention
- Always serialize interpolated values with JSON.stringify / strconv.Quote
- Never interpolate raw numbers into text-match calls
- Keep a fixture suite of valid/invalid filter strings in client tests
When it happens
Trigger: Filter strings such as content.contains(42), content.contains(true), or content.contains(someIdent) where the argument is a non-string literal or an identifier instead of a quoted string.
Common situations: Programmatically interpolating a number or unquoted variable into the filter template, or assuming CEL will coerce types like JavaScript does.
Related errors
- equality argument must be a string
- startsWith argument must be a string
- endsWith argument must be a string
- contains argument must be a string
- filter must evaluate to a boolean value
AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15).
Data as JSON: /api/errors/87a4fb0785a8c1e4.
Report an issue: GitHub.