{"record":{"id":"bc415bb84e7df2e3","repo":"makeplane/plane","slug":"invalid-filter-node","errorCode":"invalid_filter_node","errorMessage":"Each filter node must be a JSON object","messagePattern":"Each filter node must be a JSON object","errorType":"validation","errorClass":"DRFValidationError","httpStatus":400,"severity":"error","filePath":"apps/api/plane/utils/filters/filter_backend.py","lineNumber":335,"sourceCode":"        - Each object may contain only one logical operator:\n          or/and/not (case-insensitive)\n        - Logical operator objects cannot contain field keys alongside the\n          operator\n        - or/and values must be non-empty lists of dicts\n        - not value must be a dict\n        - Leaf objects must only contain field keys and acceptable values\n        - Depth must not exceed max_depth\n        \"\"\"\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(","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/apps/api/plane/utils/filters/filter_backend.py#L317-L353","documentation":"Raised by _validate_structure when a node in the filter tree is not a Python dict. Every node — whether a logical operator container, a not operand, a child of or/and, or a leaf — must be a JSON object; JSON arrays or scalars at a node position are rejected.","triggerScenarios":"GET .../?filters=[\"state\",\"open\"] (top-level array); GET .../?filters={\"or\":[\"state\",\"open\"]} (children of or are strings); GET .../?filters={\"not\":[\"state\"]} (not operand is a list instead of an object).","commonSituations":"Client treats the filter as a flat list of field names; misunderstanding that 'or'/'and' take a list of *objects* not strings; misreading the schema after migrating from a legacy filter format.","solutions":["Wrap every node in an object: {\"or\":[{\"state\":\"open\"},{\"state\":\"closed\"}]} not [\"state\",\"open\"].","For 'not', supply a single object: {\"not\":{\"state\":\"open\"}}.","Validate the top-level shape with a JSON-schema checker on the client before sending."],"exampleFix":"// before\n?filters={\"or\":[\"state\",\"priority\"]}\n// after\n?filters={\"or\":[{\"state\":\"open\"},{\"priority\":\"high\"}]}","handlingStrategy":"type-guard","validationCode":"def assert_all_nodes_are_dicts(node):\n    if not isinstance(node, dict):\n        raise ValueError(f'expected object, got {type(node).__name__}')\n    for k, v in node.items():\n        if isinstance(k, str) and k.lower() in ('or', 'and'):\n            for c in v:\n                assert_all_nodes_are_dicts(c)\n        elif isinstance(k, str) and k.lower() == 'not':\n            assert_all_nodes_are_dicts(v)","typeGuard":"function isFilterNode(v) {\n  if (typeof v !== 'object' || v === null || Array.isArray(v)) return false;\n  return true;\n}","tryCatchPattern":null,"preventionTips":["Top-level filter must be an object, not an array.","or/and children must be objects; wrap bare strings/fields before pushing them."],"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"}