HumanSignal/label-studio · error · ValidationError

List-valued user filters support only these operators: {allo

Error message

List-valued user filters support only these operators: {allowed}.

What it means

validate_user_filter_operator guards filters on built-in user fields (USER_FILTER_FIELDS). A list-valued filter value may only be used with operators in USER_FILTER_VALUE_OPERATORS (list-style operators such as contains/not_contains); anything else raises Django ValidationError listing the allowed operators.

Source

Thrown at label_studio/data_manager/managers.py:106

class ResolvedUserFilterIds(list):
    """Marker for IDs expanded by trusted backend code after client-input validation."""


def validate_user_filter_operator(field_name, operator, value):
    """Reject unsupported operators for user-list filters (FIT-2435).

    List-valued membership filters only support contains / not_contains.
    Empty is allowed only for fields in USER_FILTER_EMPTY_FIELDS.
    Legacy scalar equal/not_equal/in_list remain accepted so normalize_persisted_user_filter
    can recover historical views.
    """
    if field_name not in USER_FILTER_FIELDS:
        return

    if isinstance(value, list):
        if operator not in USER_FILTER_VALUE_OPERATORS:
            allowed = ', '.join(sorted(USER_FILTER_VALUE_OPERATORS))
            raise ValidationError(f'List-valued user filters support only these operators: {allowed}.')
        return

    allowed = allowed_user_filter_operators(field_name)
    if operator in allowed:
        return
    if operator in LEGACY_USER_FILTER_OPERATORS:
        return

    allowed_label = ', '.join(sorted(allowed))
    raise ValidationError(
        f'User filter "{field_name}" does not support operator "{operator}". Allowed: {allowed_label}.'
    )


def normalize_persisted_user_filter(field_name, operator, value):
    """Recover historical user-filter shapes without relaxing validation for new writes."""
    if field_name not in USER_FILTER_FIELDS:
        return operator, value

View on GitHub (pinned to 0b49e9b539)

Solutions

  1. Use a list operator (e.g. contains / not_contains) whenever the filter value is a list
  2. Convert the value to a scalar if an equality operator is intended
  3. Normalize legacy shapes with normalize_persisted_user_filter before validating
  4. Check USER_FILTER_VALUE_OPERATORS in managers.py for the exact allowed set

Example fix

// before
{"filter": "filter:tasks:created_by", "operator": "equal", "value": ["u1","u2"]}
// after
{"filter": "filter:tasks:created_by", "operator": "contains", "value": ["u1","u2"]}
Defensive patterns

Strategy: validation

Validate before calling

from data_manager.managers import USER_FILTER_FIELDS, USER_FILTER_VALUE_OPERATORS
if f['filter'] in USER_FILTER_FIELDS and isinstance(f['value'], list) and f['operator'] not in USER_FILTER_VALUE_OPERATORS:
    raise ValueError(f"list value needs one of {sorted(USER_FILTER_VALUE_OPERATORS)}")

Type guard

def is_valid_list_user_filter(f): return isinstance(f.get('value'), list) and f.get('operator') in USER_FILTER_VALUE_OPERATORS

Try / catch

try:
    apply_filters(...)
except ValidationError as e:
    if 'List-valued user filters' in str(e): coerce_value_to_scalar_or_list_operator(e)
    else: raise

Prevention

When it happens

Trigger: Applying a user filter whose value is a list (e.g. value: ["a","b"]) with a scalar operator like equal, not_equal, or empty via apply_filters or the DataManager validate() path.

Common situations: A saved/dashboard filter built by an older frontend being replayed with list values; hand-written API filters copying list values onto = operators; tests exercising invalid operator/value combos.

Related errors


AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29). Data as JSON: /api/errors/5717445244af48ac. Report an issue: GitHub.