usememos/memos · error

tags membership requires string literal

Error message

tags membership requires string literal

What it means

Thrown by the memo filter compiler (internal/filter/render.go) when a tag-membership expression like `tag in [...]` or `tag CONTAINS <value>` uses a non-string literal element. The renderer must embed the tag value into a SQL comprehension over a JSON list column, so only string literals are supported. Any variable, numeric literal, or nested expression as the element triggers this error.

Source

Thrown at internal/filter/render.go:492

	}, nil
}

func (r *renderer) renderElementInCondition(cond *ElementInCondition) (renderResult, error) {
	field, ok := r.schema.Field(cond.Field)
	if !ok {
		return renderResult{}, errors.Errorf("unknown field %q", cond.Field)
	}
	if field.Kind != FieldKindJSONList {
		return renderResult{}, errors.Errorf("field %q is not a tag list", cond.Field)
	}

	lit, err := expectLiteral(cond.Element)
	if err != nil {
		return renderResult{}, err
	}
	str, ok := lit.(string)
	if !ok {
		return renderResult{}, errors.New("tags membership requires string literal")
	}

	return r.renderJSONListContains(field, str)
}

func (r *renderer) renderJSONListContains(field Field, value string) (renderResult, error) {
	return r.renderTagComprehension(field, &EqualsPredicate{Value: value}, ComprehensionExists)
}

func (r *renderer) renderScalarInCondition(field Field, values []ValueExpr) (renderResult, error) {
	placeholders := make([]string, 0, len(values))

	for _, v := range values {
		lit, err := expectLiteral(v)
		if err != nil {
			return renderResult{}, err
		}
		switch field.Type {

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. Quote the tag values: `tag in ["work", "personal"]` instead of `tag in [work]` or `tag in [1]`
  2. Verify the field you reference is actually a tag-list field (FieldKindJSONList); scalar fields use different operators
  3. If you need dynamic values, resolve them to concrete string literals before building the filter string

Example fix

// before
filter := `tag in [work, 1]`
// after
filter := `tag in ["work", "1"]`
Defensive patterns

Strategy: validation

Validate before calling

// Validate tag-list elements are string literals before submitting a filter
import (
  "regexp"
  "strings"
)
var nonStringMember = regexp.MustCompile(`(?i)\b(?:tag|tags)\s+(?:in|contains)\s*\[([^\]]*)\]`)
func tagMembersAreStrings(filter string) bool {
  for _, m := range nonStringMember.FindAllStringSubmatch(filter, -1) {
    for _, part := range strings.Split(m[1], ",") {
      p := strings.TrimSpace(part)
      if p == "" { continue }
      if !strings.HasPrefix(p, "\"") || !strings.HasSuffix(p, "\"") { return false }
    }
  }
  return true
}

Try / catch

// Compile the filter during authoring and show the parser error inline
if _, err := engine.CompileToStatement(ctx, filter, opts); err != nil {
  return fmt.Errorf("invalid filter: %w", err) // surface 'tags membership requires string literal'
}

Prevention

When it happens

Trigger: A CEL/SQL-like filter string such as `tag in [1, 2]`, `tag in [true]`, or `content.tag == some_expr` compiled via the filter engine's tag-list rendering path (renderJSONListContains -> expectLiteral -> type assertion to string fails).

Common situations: Users writing saved filters with unquoted tag names (`tag in [work]` parsed as identifier instead of `"work"`), numeric tag IDs, or copying filters from a different filter dialect that permits non-string members.

Related errors


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