makeplane/plane · error · DRFValidationError

multiple_logical_operators

multiple_logical_operators

Error message

A filter object cannot contain multiple logical operators at the same level

What it means

Raised by _validate_structure when a single node contains more than one logical operator key (case-insensitive). The grammar permits at most one of or/and/not per object; mixing them — e.g. {'or':[...], 'and':[...]} — is ambiguous and rejected so the backend never has to choose an evaluation order.

Source

Thrown at apps/api/plane/utils/filters/filter_backend.py:353

            raise DRFValidationError(
                {
                    "message": "Each filter node must be a JSON object",
                    "code": "invalid_filter_node",
                }
            )

        if not node:
            raise DRFValidationError(
                {
                    "message": "Filter objects must not be empty",
                    "code": "empty_filter_object",
                }
            )

        logical_keys = [k for k in node.keys() if isinstance(k, str) and k.lower() in ("or", "and", "not")]

        if len(logical_keys) > 1:
            raise DRFValidationError(
                {
                    "message": ("A filter object cannot contain multiple logical operators at the same level"),
                    "code": "multiple_logical_operators",
                }
            )

        if len(logical_keys) == 1:
            op_key = logical_keys[0]
            # must not mix operator with other keys
            if len(node) != 1:
                raise DRFValidationError(
                    {
                        "message": (f"Cannot mix logical operator '{op_key}' with field keys at the same level"),
                        "code": "mixed_operator_and_fields",
                    }
                )

            op = op_key.lower()

View on GitHub (pinned to 1c8a60f858)

Solutions

  1. Nest operators so each object has exactly one: {"and":[{"or":[...]},{...}]} instead of {"or":[...],"and":[...]}.
  2. When merging filter fragments, wrap each in an explicit and/or group rather than dict-merging them together.
  3. Use lower-case keys consistently; remember matching is case-insensitive so 'OR' and 'or' still collide.

Example fix

// before
?filters={"or":[{"state":"open"}],"and":[{"priority":"high"}]}
// after
?filters={"and":[{"or":[{"state":"open"}]},{"priority":"high"}]}
Defensive patterns

Strategy: validation

Validate before calling

def count_operators(node):
    return sum(1 for k in node if isinstance(k, str) and k.lower() in ('or', 'and', 'not'))

def assert_single_operator(node):
    if count_operators(node) > 1:
        raise ValueError(f'node has multiple operators: {[k for k in node if k.lower() in ("or","and","not")]}')

Type guard

function singleOperatorOnly(node) {
  const ops = Object.keys(node).filter(k => ['or','and','not'].includes(k.toLowerCase()));
  return ops.length <= 1;
}

Prevention

When it happens

Trigger: GET .../?filters={"or":[...],"and":[...]} ; GET .../?filters={"AND":{...},"not":{...}} (case-insensitive match); clients that combine operators in one object intending SQL-style precedence.

Common situations: Translating an SQL WHERE (a OR b AND c) directly into a single object instead of nesting; merging two filter fragments with dict.update(); schema misunderstanding after migrating from a legacy AND/OR-flat format.

Related errors


AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12). Data as JSON: /api/errors/bf680df319e29e2a. Report an issue: GitHub.