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
- Build the param with JSON.stringify on the client: `?filters=${encodeURIComponent(JSON.stringify({state:'open'}))}`.
- Validate the JSON locally before sending: parse it in your dev console to surface the exact syntax error.
- 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
- Always build the query string with JSON.stringify + encodeURIComponent on the client.
- Run the filter through a local JSON.parse in the browser console before sending.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12).
Data as JSON: /api/errors/08c3355fc0ce991b.
Report an issue: GitHub.