makeplane/plane · error · DRFValidationError
invalid_filterset
invalid_filterset
Error message
Invalid filter parameters
What it means
Raised by _build_leaf_q when filterset_class(data=..., queryset=...).is_valid() returns False. The underlying django_filters FilterSet runs its own per-field validation (range ordering, choice constraints, type coercion), and the errors dict is forwarded through translate_validation and embedded in the exception under 'errors'.
Source
Thrown at apps/api/plane/utils/filters/filter_backend.py:279
qd = QueryDict(mutable=True)
for key, value in processed_conditions.items():
# Default serialization to string; QueryDict expects strings
if isinstance(value, list):
# Repeat key for list values (e.g., __in)
qd.setlist(key, [str(v) for v in value])
else:
qd[key] = "" if value is None else str(value)
qd = qd.copy()
qd._mutable = False
# Instantiate the filterset with the actual queryset
# Custom filter methods may need access to the queryset for filtering
fs = filterset_class(data=qd, queryset=queryset)
if not fs.is_valid():
ve = translate_validation(fs.errors)
raise DRFValidationError(
{
"message": "Invalid filter parameters",
"code": "invalid_filterset",
"errors": ve.detail,
}
)
# Build and return the combined Q object
if not hasattr(fs, "build_combined_q"):
raise DRFValidationError(
{
"message": ("FilterSet must have build_combined_q method for complex filtering"),
"code": "missing_build_combined_q",
}
)
return fs.build_combined_q()
View on GitHub (pinned to 1c8a60f858)
Solutions
- Inspect the 'errors' field of the response — it carries per-field detail from the filterset.
- Sort __range bounds so the smaller value comes first: [5, 10] not [10, 5].
- Confirm the value is a member of the ChoiceFilter choices or a valid PK for ModelChoiceFilter.
- If the value should be permitted, widen the filterset's choices or field definition.
Example fix
// before
?filters={"sequence_id__range":[10,5]}
// after
?filters={"sequence_id__range":[5,10]} Defensive patterns
Strategy: try-catch
Validate before calling
fs = view.filterset_class(data=qd, queryset=qs)
if not fs.is_valid():
# fs.errors is a dict of per-field error lists
raise ValueError(f'filterset invalid: {fs.errors}') Try / catch
try:
return backend.filter_queryset(request, qs, view)
except DRFValidationError as e:
detail = e.detail
if isinstance(detail, dict) and detail.get('code') == 'invalid_filterset':
return Response({'field_errors': detail.get('errors')}, status=400)
raise Prevention
- Sort __range bounds ascending on the client.
- Mirror the server's ChoiceFilter choices on the frontend pickers.
- Send IDs only where ModelChoiceFilter is used.
When it happens
Trigger: GET .../?filters={"sequence_id__range":[10,5]} (range where start>end); GET .../?filters={"state":"deleted"} when state is a ChoiceFilter that does not include 'deleted'; non-numeric string sent to a NumberFilter.
Common situations: Semantic mismatch between client enum and server ChoiceFilter; reversed __range bounds; sending an ID that does not exist for a ModelChoiceFilter; type drift after a field changed from CharFilter to NumberFilter.
Related errors
AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12).
Data as JSON: /api/errors/4c7b2de34773ae76.
Report an issue: GitHub.