{"record":{"id":"3f3dc88217c9dd4e","repo":"makeplane/plane","slug":"empty-filter-object","errorCode":"empty_filter_object","errorMessage":"Filter objects must not be empty","messagePattern":"Filter objects must not be empty","errorType":"validation","errorClass":"DRFValidationError","httpStatus":400,"severity":"error","filePath":"apps/api/plane/utils/filters/filter_backend.py","lineNumber":343,"sourceCode":"        \"\"\"\n        if current_depth > max_depth:\n            raise DRFValidationError(\n                {\n                    \"message\": (f\"Filter nesting is too deep (max {max_depth}); found depth {current_depth}\"),\n                    \"code\": \"max_depth_exceeded\",\n                }\n            )\n\n        if not isinstance(node, dict):\n            raise DRFValidationError(\n                {\n                    \"message\": \"Each filter node must be a JSON object\",\n                    \"code\": \"invalid_filter_node\",\n                }\n            )\n\n        if not node:\n            raise DRFValidationError(\n                {\n                    \"message\": \"Filter objects must not be empty\",\n                    \"code\": \"empty_filter_object\",\n                }\n            )\n\n        logical_keys = [k for k in node.keys() if isinstance(k, str) and k.lower() in (\"or\", \"and\", \"not\")]\n\n        if len(logical_keys) > 1:\n            raise DRFValidationError(\n                {\n                    \"message\": (\"A filter object cannot contain multiple logical operators at the same level\"),\n                    \"code\": \"multiple_logical_operators\",\n                }\n            )\n\n        if len(logical_keys) == 1:\n            op_key = logical_keys[0]","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/apps/api/plane/utils/filters/filter_backend.py#L325-L361","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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 {}."],"exampleFix":"// before\n?filters={\"or\":[{\"state\":\"open\"},{}]}\n// after\n?filters={\"or\":[{\"state\":\"open\"}]}","handlingStrategy":"validation","validationCode":"def strip_empty_nodes(node):\n    if not isinstance(node, dict):\n        return node\n    if not node:\n        raise ValueError('empty filter object encountered')\n    return {k: (strip_empty_nodes(v) if isinstance(v, dict)\n               else [strip_empty_nodes(c) for c in v] if isinstance(v, list)\n               else v)\n            for k, v in node.items()}","typeGuard":"function hasNoEmptyObjects(node) {\n  if (typeof node !== 'object' || node === null) return true;\n  if (Array.isArray(node)) return node.every(hasNoEmptyObjects);\n  const keys = Object.keys(node);\n  if (keys.length === 0) return false;\n  return keys.every(k => hasNoEmptyObjects(node[k]));\n}","tryCatchPattern":null,"preventionTips":["Disable form submit while any rule group has zero clauses.","Filter empty objects out of the tree before serializing."],"tags":["filters","schema","validation","api"],"backgroundTag":null,"analyzedSha":"1c8a60f858d8472aa56e29994ec1c7926da2c6ce","analyzedAt":"2026-08-12T14:44:31.636Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}