feat(dashboards): add `forProject` filter to dashboard list API
@dcramer is already working on this.
Since Jun 11, 2026.
- 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
projectorprojectSlugparam — 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=p1 → d_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 ford_p1_p2
Files
src/sentry/dashboards/endpoints/organization_dashboards.pysrc/sentry/apidocs/parameters.py(add filter value to schema)tests/sentry/dashboards/endpoints/test_organization_dashboards.py
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.