Skip to content

[bug]: Public API work item list silently ignores unknown or unsupported query parameters (e.g. ?parent=) #10220

Open

Description

@pitupi

Is there an existing issue for this?

Searched on 2026-11-07 for list filter parent / filters api: no match. #5578 is a UI request about view filters on sub-issues.

Current behavior

GET /api/v1/workspaces/{slug}/projects/{project_id}/issues/?parent=<id> returns every work item of the project, as if the parameter did not exist. ?any_name_at_all=1 does the same. The Community list endpoint already refuses pql and filters with a clear 400 ("filter results client-side", IssueListCreateAPIEndpoint.get, line 392), so unsupported filtering is meant to fail loudly — but for every other name it fails silently. A client that filters by parent and trusts the response gets wrong data (for example, treats all items as children of one parent).

Steps to reproduce

repro_limitacoes.py (section 3), v1.4.3. Output: without filter 7 issues; ?parent=<id> 7; ?unknown=1 7.

Expected behavior

Either (preferred, small): answer 400 listing the unsupported parameters, like the existing pql/filters branch, using an allow-list of the parameters the endpoint really reads (per_page, cursor, order_by, fields, expand, external_id, external_source, …).

Or (feature): support a few cheap filters that make incremental sync possible without PQL: parent=<id> and updated_at__gte=<ISO datetime>. With the second one a mirror can fetch only what changed instead of listing all items on each run.

Implementation sketch (not tested against the Plane test suite)

Pinned to 618d0eb7. apps/api/plane/api/views/issue.py, IssueListCreateAPIEndpoint.get, next to the existing check at line 392:

KNOWN = {"per_page", "cursor", "order_by", "fields", "expand", "external_id", "external_source"}
unknown = sorted(set(request.GET) - KNOWN)
if unknown:
    return Response({"error": "Unsupported query parameters", "unsupported_parameters": unknown},
                    status=status.HTTP_400_BAD_REQUEST)

(Complete the allow-list from the parameters the view and the pagination class read.) For the feature variant, add to the queryset before ordering:

if parent := request.GET.get("parent"):
    issue_queryset = issue_queryset.filter(parent_id=parent)
if since := request.GET.get("updated_at__gte"):
    issue_queryset = issue_queryset.filter(updated_at__gte=since)

Tests worth adding

  1. Unknown parameter → 400 with its name in unsupported_parameters.
  2. ?parent=<id> → only that parent's children.
  3. ?updated_at__gte=<t> → only items changed after t, including items whose only change was a new comment (we measured that a new comment bumps the work item's updated_at, also on completed items).

Environment / Edition / Version

Self-hosted, Community, v1.4.3.

Workaround we use meanwhile

List everything (per_page=100) and filter on the client; compare updated_at per item to decide what to re-read.

Activity

  1. ShivamJuyal24 commented on Nov 8, 2026

    @ShivamJuyal24

    I’d like to work on this. I plan to start with the preferred option: return a 400 listing unsupported query parameters, using an allow-list built from what the endpoint and paginator actually read. I’ll add tests for it. Is a 400 acceptable here, or would you rather have a softer behavior? I’ll treat the parent / updated_at__gte filters as a separate follow-up if you want them

  2. ShivamJuyal24 commented on Nov 8, 2026

    @ShivamJuyal24

    PR is up: #10226. It returns a 400 listing the unsupported parameters, using an allow-list built from what the view, base class and paginator read (plus DRF's format), and it covers both the issues/ and work-items/ routes. Happy to adjust if you'd prefer a softer behavior than a hard 400.

  3. pitupi commented on Nov 8, 2026

    @pitupi
    Author

    Thanks for picking this up. Yes, a 400 is what I'd want. It's the behaviour the issue asks for and it matches the existing pql/filters branch.

    Two things before merge:

    1. In test_external_id_parameters_accepted there is a leftover block after the last assert: a loose docstring and a second GET with no assertion. It looks like an editing artefact and can be removed.

    2. This is a visible change for clients that send parameters the endpoint has ignored so far (e.g. page and group_by, see Public API: project work-items list ignores ?page= and never terminates pagination (v1.3.0) #9340 and [bug]: Issue List API ignores group_by parameter and returns flat array with null grouped_by #9765). Could the PR description or release notes say so, so integrators know why they now get a 400?

    The parent and updated_at__gte filters can stay out of this PR and be tracked separately.

  4. ShivamJuyal24 commented on Nov 8, 2026

    @ShivamJuyal24

    Thanks for the review! I've made both changes:

    1. Removed the leftover editing artifact (the loose docstring and duplicate GET request with no assertion) in test_external_id_parameters_accepted.
    2. Updated the PR description to explicitly note the visible behavior change for clients sending parameters like page or group_by, as requested.

    The full test suite in test_issues.py passes (15 tests), and both ruff check and ruff format --check are clean. Let me know if it looks good to go!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions