makeplane/plane · error · DRFValidationError

invalid_not_child

invalid_not_child

Error message

'not' must be a single JSON object

What it means

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.

Source

Thrown at apps/api/plane/utils/filters/filter_backend.py:399

                    )
                for child in value:
                    if not isinstance(child, dict):
                        raise DRFValidationError(
                            {
                                "message": f"All children of '{op}' must be JSON objects",
                                "code": "invalid_operator_child_type",
                            }
                        )
                    self._validate_structure(
                        child,
                        max_depth=max_depth,
                        current_depth=current_depth + 1,
                    )
                return

            if op == "not":
                if not isinstance(value, dict):
                    raise DRFValidationError(
                        {
                            "message": "'not' must be a single JSON object",
                            "code": "invalid_not_child",
                        }
                    )
                self._validate_structure(value, max_depth=max_depth, current_depth=current_depth + 1)
                return

        # Leaf node: validate fields and values
        self._validate_leaf(node)

    def _validate_leaf(self, leaf):
        """Validate a leaf dict containing field lookups and values."""
        if not isinstance(leaf, dict) or not leaf:
            raise DRFValidationError(
                {
                    "message": "Leaf filter must be a non-empty JSON object",
                    "code": "invalid_leaf",

View on GitHub (pinned to 1c8a60f858)

Solutions

  1. Give 'not' a single object: {"not":{"state":"open"}}.
  2. To negate multiple conditions, wrap them in and/or inside the not: {"not":{"and":[...]}}.
  3. Use 'or'/'and' for multi-operand composition; reserve 'not' for unary negation.

Example fix

// before
?filters={"not":[{"state":"open"}]}
// after
?filters={"not":{"state":"open"}}
Defensive patterns

Strategy: type-guard

Validate before calling

def assert_not_operand(node):
    for k, v in node.items():
        if isinstance(k, str) and k.lower() == 'not':
            if not isinstance(v, dict):
                raise ValueError("'not' operand must be an object")

Type guard

function notOperandIsObject(node) {
  const op = Object.keys(node).find(k => k.toLowerCase() === 'not');
  return !op || (typeof node[op] === 'object' && node[op] !== null && !Array.isArray(node[op]));
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12). Data as JSON: /api/errors/9d0ae64550348961. Report an issue: GitHub.