makeplane/plane · error · DRFValidationError

invalid_json

invalid_json

Error message

Invalid JSON for '{source_label}'. Expected a valid JSON object.

What it means

Raised by _normalize_filter_data when json.loads() throws JSONDecodeError on a string filter input. The query parameter `filters` (filter_param = 'filters') or the explicit filter_data string must decode to a valid JSON object; trailing commas, single quotes, unquoted keys, or truncated payloads all fail here.

Source

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

    def _normalize_filter_data(self, raw_filter, source_label):
        """Return a dict from raw filter input or raise a ValidationError.

        - raw_filter may be a dict or a JSON string
        - source_label is used in error messages (e.g., 'filter_data' or 'filter')
        """
        try:
            if isinstance(raw_filter, str):
                return json.loads(raw_filter)
            if isinstance(raw_filter, dict):
                return raw_filter
            raise DRFValidationError(
                {
                    "message": f"'{source_label}' must be a dict or a JSON string.",
                    "code": "invalid_filter_type",
                }
            )
        except json.JSONDecodeError:
            raise DRFValidationError(
                {
                    "message": (f"Invalid JSON for '{source_label}'. Expected a valid JSON object."),
                    "code": "invalid_json",
                }
            )

    def _apply_json_filter(self, queryset, filter_data, view):
        """Process a JSON filter structure using Q object composition."""
        if not filter_data:
            return queryset

        # Validate structure and depth before field allowlist checks
        max_depth = self._get_max_depth(view)
        self._validate_structure(filter_data, max_depth=max_depth, current_depth=1)

        # Validate against the view's FilterSet (only declared filters are allowed)
        self._validate_fields(filter_data, view)

View on GitHub (pinned to 1c8a60f858)

Solutions

  1. Build the param with JSON.stringify on the client: `?filters=${encodeURIComponent(JSON.stringify({state:'open'}))}`.
  2. Validate the JSON locally before sending: parse it in your dev console to surface the exact syntax error.
  3. Watch for trailing commas, single-quoted strings, and bare keys; standard JSON requires double quotes everywhere.

Example fix

// before
fetch(`/issues/?filters={state:open}`)
// after
fetch(`/issues/?filters=${encodeURIComponent(JSON.stringify({state:'open'}))}`)
Defensive patterns

Strategy: validation

Validate before calling

import json

def safe_json_filter(raw: str):
    try:
        parsed = json.loads(raw)
    except json.JSONDecodeError as e:
        raise ValueError(f'filters param is not valid JSON: {e}') from e
    if not isinstance(parsed, dict):
        raise ValueError('filters param must decode to a JSON object')
    return parsed

Type guard

// JS, before constructing the URL
function buildFiltersParam(obj) {
  if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
    throw new Error('filters must be a plain object');
  }
  return encodeURIComponent(JSON.stringify(obj));
}

Try / catch

try:
    normalized = backend._normalize_filter_data(raw, 'filters')
except DRFValidationError as e:
    if e.detail.get('code') == 'invalid_json':
        return Response({'error': 'malformed filters param'}, status=400)
    raise

Prevention

When it happens

Trigger: GET /api/workspaces/<slug>/issues/?filters=state%3Dopen ; GET .../?filters={state:open} (unquoted keys); GET .../?filters={"state":"open",} (trailing comma); URL truncation producing a partial JSON body.

Common situations: Hand-built query strings in tests or Postman; URL-encoding mistakes (forgetting to encode braces/quotes); clients that build the filter with string concatenation instead of JSON.stringify; copy-paste from a JSON view that omits outer braces.

Understand the failure class

Related errors


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