{"record":{"id":"9d0ae64550348961","repo":"makeplane/plane","slug":"invalid-not-child","errorCode":"invalid_not_child","errorMessage":"'not' must be a single JSON object","messagePattern":"'not' must be a single JSON object","errorType":"validation","errorClass":"DRFValidationError","httpStatus":400,"severity":"error","filePath":"apps/api/plane/utils/filters/filter_backend.py","lineNumber":399,"sourceCode":"                    )\n                for child in value:\n                    if not isinstance(child, dict):\n                        raise DRFValidationError(\n                            {\n                                \"message\": f\"All children of '{op}' must be JSON objects\",\n                                \"code\": \"invalid_operator_child_type\",\n                            }\n                        )\n                    self._validate_structure(\n                        child,\n                        max_depth=max_depth,\n                        current_depth=current_depth + 1,\n                    )\n                return\n\n            if op == \"not\":\n                if not isinstance(value, dict):\n                    raise DRFValidationError(\n                        {\n                            \"message\": \"'not' must be a single JSON object\",\n                            \"code\": \"invalid_not_child\",\n                        }\n                    )\n                self._validate_structure(value, max_depth=max_depth, current_depth=current_depth + 1)\n                return\n\n        # Leaf node: validate fields and values\n        self._validate_leaf(node)\n\n    def _validate_leaf(self, leaf):\n        \"\"\"Validate a leaf dict containing field lookups and values.\"\"\"\n        if not isinstance(leaf, dict) or not leaf:\n            raise DRFValidationError(\n                {\n                    \"message\": \"Leaf filter must be a non-empty JSON object\",\n                    \"code\": \"invalid_leaf\",","sourceCodeStart":381,"sourceCodeEnd":417,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/apps/api/plane/utils/filters/filter_backend.py#L381-L417","documentation":"Raised by _validate_structure when a 'not' operator's value is not a dict. Unlike or/and (which take a list), 'not' takes a single JSON object that itself follows the filter grammar; lists, scalars, or null are rejected.","triggerScenarios":"GET .../?filters={\"not\":[{\"state\":\"open\"}]} (array given to not); GET .../?filters={\"not\":\"state\"} (string); GET .../?filters={\"not\":null}; GET .../?filters={\"not\":[{\"state\":\"open\"},{\"priority\":\"high\"}]} (using not as if it were or).","commonSituations":"Treating 'not' symmetrically with or/and (which take arrays); client builder that always wraps operands in a list; confusing 'not' with a NAND-style multi-operand operator.","solutions":["Give 'not' a single object: {\"not\":{\"state\":\"open\"}}.","To negate multiple conditions, wrap them in and/or inside the not: {\"not\":{\"and\":[...]}}.","Use 'or'/'and' for multi-operand composition; reserve 'not' for unary negation."],"exampleFix":"// before\n?filters={\"not\":[{\"state\":\"open\"}]}\n// after\n?filters={\"not\":{\"state\":\"open\"}}","handlingStrategy":"type-guard","validationCode":"def assert_not_operand(node):\n    for k, v in node.items():\n        if isinstance(k, str) and k.lower() == 'not':\n            if not isinstance(v, dict):\n                raise ValueError(\"'not' operand must be an object\")","typeGuard":"function notOperandIsObject(node) {\n  const op = Object.keys(node).find(k => k.toLowerCase() === 'not');\n  return !op || (typeof node[op] === 'object' && node[op] !== null && !Array.isArray(node[op]));\n}","tryCatchPattern":null,"preventionTips":["Reserve 'not' for unary negation — it takes one object, not a list.","To negate multiple clauses, wrap them in and/or inside the not operand."],"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"}