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
- Nest operators so each object has exactly one: {"and":[{"or":[...]},{...}]} instead of {"or":[...],"and":[...]}.
- When merging filter fragments, wrap each in an explicit and/or group rather than dict-merging them together.
- 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
- Never dict-merge two operator fragments; nest them under and/or instead.
- Operator key matching is case-insensitive — pick one casing and stay consistent.
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
- invalid_filter_node
- empty_filter_object
- mixed_operator_and_fields
- invalid_operator_children
- invalid_operator_child_type
AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12).
Data as JSON: /api/errors/bf680df319e29e2a.
Report an issue: GitHub.