makeplane/plane · error · DRFValidationError
empty_filter_object
empty_filter_object
Error message
Filter objects must not be empty
What it means
Raised by _validate_structure when a node is an empty dict {}. Empty operator containers ({'and':[]}) are caught earlier as invalid_operator_children; this fires when the node itself has no keys, e.g. a stray {} in an or/and list or a top-level empty filter object that survived _normalize_filter_data (which only checks for falsy at line 82 and returns queryset).
Source
Thrown at apps/api/plane/utils/filters/filter_backend.py:343
"""
if current_depth > max_depth:
raise DRFValidationError(
{
"message": (f"Filter nesting is too deep (max {max_depth}); found depth {current_depth}"),
"code": "max_depth_exceeded",
}
)
if not isinstance(node, dict):
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]View on GitHub (pinned to 1c8a60f858)
Solutions
- Remove empty objects from the filter tree before sending.
- In the client rule builder, disable submit while any group has zero clauses.
- If a group is intentionally a no-op, drop it rather than emitting {}.
Example fix
// before
?filters={"or":[{"state":"open"},{}]}
// after
?filters={"or":[{"state":"open"}]} Defensive patterns
Strategy: validation
Validate before calling
def strip_empty_nodes(node):
if not isinstance(node, dict):
return node
if not node:
raise ValueError('empty filter object encountered')
return {k: (strip_empty_nodes(v) if isinstance(v, dict)
else [strip_empty_nodes(c) for c in v] if isinstance(v, list)
else v)
for k, v in node.items()} Type guard
function hasNoEmptyObjects(node) {
if (typeof node !== 'object' || node === null) return true;
if (Array.isArray(node)) return node.every(hasNoEmptyObjects);
const keys = Object.keys(node);
if (keys.length === 0) return false;
return keys.every(k => hasNoEmptyObjects(node[k]));
} Prevention
- Disable form submit while any rule group has zero clauses.
- Filter empty objects out of the tree before serializing.
When it happens
Trigger: GET .../?filters={} is short-circuited earlier (returns unfiltered queryset), but GET .../?filters={"or":[{}]} or {"not":{}} reaches _validate_structure on the empty inner dict and triggers this.
Common situations: Programmatic builders that push empty group placeholders; serialization libraries that emit {} for null sub-conditions; UI rule builder that allows adding a group with no clauses yet.
Related errors
- invalid_filter_node
- multiple_logical_operators
- 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/3f3dc88217c9dd4e.
Report an issue: GitHub.