getsentry / getsentry/sentry

feat(dashboards): add `forProject` filter to dashboard list API

Open
#117,393 2 comments 0 reactions 1 assignee View on GitHub

@dcramer is already working on this.

Since Jun 11, 2026.

Feature Product Area: Dashboards
Dominant language
Python
Stars
44.8k
Forks
4.9k
Avg merge
21h 23m
Merged PRs (30d)
607

Description

Summary

Add a forProject filter value to the GET /api/0/organizations/{org}/dashboards/ endpoint so callers can scope the dashboard list to a specific project context.

Motivation

When rendering dashboards in a project-scoped context (e.g. a project sidebar or project-level insights surface), the full org dashboard list is too broad. Dashboards explicitly pinned to other projects are irrelevant noise and should be excluded.

Proposed API

Use the existing multi-value filter query param (consistent with onlyFavorites, owned, excludePrebuilt, etc.) combined with the standard platform project selectors:

GET /api/0/organizations/{org}/dashboards/?filter=forProject&project=123
GET /api/0/organizations/{org}/dashboards/?filter=forProject&projectSlug=my-project

Filter Semantics

filter=forProject returns dashboards that are relevant to the requested project context:

Dashboard type Included
Explicitly bound to this project (via DashboardProject)
Bound to this project and others
Bound only to other projects
Unbound / org-wide (no project bindings)
filters.all_projects = true

all_projects dashboards have no DashboardProject rows so they fall naturally under "no project bindings" and are included.

Implementation Notes

Validation:

  • Requires exactly one concrete project or projectSlug param — return 400 otherwise.
  • Resolve via self.get_projects(request, organization) (handles perms, slug, numeric ID).
  • Reject project=-1 (all-access sentinel).

ORM — use Exists subqueries, not M2M join filters (M2M joins create duplicate rows for multi-project dashboards):

from django.db.models import Exists, OuterRef, Q
from sentry.models.dashboard import DashboardProject

has_this_project = Exists(
    DashboardProject.objects.filter(dashboard_id=OuterRef("pk"), project_id=project.id)
)
has_any_project = Exists(
    DashboardProject.objects.filter(dashboard_id=OuterRef("pk"))
)

# forProject
dashboards = dashboards.filter(Q(~has_any_project) | Q(has_this_project))

Integration point in OrganizationDashboardsEndpoint.get() — parse before the existing for f in filters loop, skip forProject inside the loop, apply after.

Test Matrix

Given: d_p1 (bound to p1), d_p2 (bound to p2), d_p1_p2 (bound to both), d_unbound (no bindings), d_all (all_projects=True)

filter=forProject&project=p1d_p1, d_p1_p2, d_unbound, d_all

Error cases:

  • Missing project/projectSlug → 400
  • project=-1 → 400
  • Inaccessible project → 403/404 via get_projects
  • Combining with filter=onlyFavorites, sort, pagination → still works; no duplicate rows for d_p1_p2

Files

  • src/sentry/dashboards/endpoints/organization_dashboards.py
  • src/sentry/apidocs/parameters.py (add filter value to schema)
  • tests/sentry/dashboards/endpoints/test_organization_dashboards.py

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.