Visitar URL original
[self-host v1.4.2] /project-views/ POST only persists per-user view_props — no public API to create named project-wide saved views (ref #8816) · Issue #10088 · makeplane/plane · GitHub
Skip to content

[self-host v1.4.2] /project-views/ POST only persists per-user view_props — no public API to create named project-wide saved views (ref #8816) #10088

Open

Description

@Apfelkringel

Summary

On self-hosted makeplane/plane v1.4.2, the REST API exposes a path called POST /api/v1/workspaces/{slug}/projects/{project_id}/project-views/, but the handler only persists per-user UI state into ProjectMember.view_props (and friends). It does not create a named, project-wide saved view, and it is not reachable with API-key auth.

This is the same underlying product gap as #8816 (Public API support for creating and managing Views), but the angle here is the misleadingly-named /project-views/ endpoint in the current self-host master / v1.4.2: the path looks like a public REST endpoint for project views, but the implementation is per-user state, not a saved view, and it is not part of the documented /api/v1/ public surface.

Repro on a running self-host instance (HTTP against http://localhost/)

The following curl probes all return HTTP 404 against a healthy self-host v1.4.2 stack with valid auth cookies/API keys. They cover the natural guesses for a "create named project-wide saved view" endpoint:

# Natural guesses that all return 404 in self-host v1.4.2:
curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
  "http://localhost/api/v1/workspaces/test-slug/projects/00000000-0000-0000-0000-000000000001/project-views/" \
  -X POST
# -> HTTP 404

curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
  "http://localhost/api/v1/workspaces/test-slug/projects/00000000-0000-0000-0000-000000000001/views/" \
  -X POST
# -> HTTP 404

curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
  "http://localhost/api/v1/workspaces/test-slug/projects/00000000-0000-0000-0000-000000000001/saved-views/" \
  -X POST
# -> HTTP 404

curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
  "http://localhost/api/v1/workspaces/test-slug/projects/00000000-0000-0000-0000-000000000001/project-saved-views/" \
  -X POST
# -> HTTP 404

curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
  "http://localhost/api/v1/workspaces/test-slug/projects/00000000-0000-0000-0000-000000000001/named-views/" \
  -X POST
# -> HTTP 404

curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
  "http://localhost/api/v1/workspaces/test-slug/projects/00000000-0000-0000-0000-000000000001/project-named-views/" \
  -X POST
# -> HTTP 404

(404 here is the auth-wall + no-such-route response; the request is well-formed but the routes are not registered, or they are registered but gated to session-cookie auth and not API-key.)

What does exist in self-host v1.4.2 source (read from the running plane-app-api image, tag v1.4.2)

plane/app/views/project/base.py, class ProjectUserViewsEndpoint:

class ProjectUserViewsEndpoint(BaseAPIView):
    def post(self, request, slug, project_id):
        project = Project.objects.get(pk=project_id, workspace__slug=slug)
        project_member = ProjectMember.objects.filter(
            member=request.user, project=project, is_active=True
        ).first()
        if project_member is None:
            return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN)
        view_props = project_member.view_props
        default_props = project_member.default_props
        preferences = project_member.preferences
        sort_order = project_member.sort_order
        project_member.view_props = request.data.get("view_props", view_props)
        project_member.default_props = request.data.get("default_props", default_props)
        project_member.preferences = request.data.get("preferences", preferences)
        project_member.sort_order = request.data.get("sort_order", sort_order)
        project_member.save()
        return Response(status=status.HTTP_204_NO_CONTENT)

This handler accepts view_props, default_props, preferences, sort_order. It does not accept name, description, access, query, filters, display_filters, display_properties. It writes to ProjectMember (per-user state), not to IssueView. The ProjectMember columns are JSONField defaults like {view_id, list, kanban, calendar, gantt, spreadsheet, …} — UI column visibility per layout, not a saved filter.

Routing — plane/app/urls/project.py:

path(
    "workspaces/<str:slug>/projects/<uuid:project_id>/project-views/",
    ProjectUserViewsEndpoint.as_view(),
    name="project-view",
),

This is the only /project-views/ route in the entire plane codebase. The name in the URL is project-view, but the semantics are ProjectMember.view_props, not a saved view. There is no symmetric GET.

Comparison with the SaaS feature surface

Plane SaaS documents a public POST /api/v2/workspaces/{slug}/projects/{project_id}/views/ endpoint under scope projects.views:write. Body fields include name (≤255 chars), access, display_filters, display_properties, filters, description, is_locked, logo_props, pql_filters, sort_order. Response fields include id, name, description, access, query, filters, display_filters, display_properties, owned_by_id, created_at, updated_at, etc. The backing model is IssueView (db_table issue_views) with columns name, description, query, filters, display_filters, display_properties, rich_filters, access, sort_order, logo_props, owned_by, is_locked, archived_at.

Self-host v1.4.2 ships the same IssueView model in plane/db/models/view.py. The data layer is therefore present. The problem is the public-API surface:

  • self-host: /api/v1/.../project-views/ → per-user ProjectMember.view_props save (no name, no description, no query/filters, no public route for the IssueView model).
  • SaaS: /api/v2/.../views/ → IssueView CRUD under API-key auth, with full filter/layout payload.

So self-host users on /api/v1/ cannot script the creation of a named, project-wide saved view (IssueView) through the public API. The only ways to create a saved view in self-host v1.4.2 are:

  1. Click through the React UI (which calls internal session-cookie endpoints).
  2. Hit the internal session-cookie IssueViewViewSet.create directly (not documented as public, not API-key-scoped, not stable).

Why this matters for self-host users

The parent feature request #8816 captures the general ask. The specific concrete problem this surfaces is for ops automations on a self-host install:

  • Teams want to script recurring views (e.g. "waiting on", "due this week", "needs review") from CI, repo automation, or other Plane-adjacent tooling.
  • The only /api/v1/ endpoint whose name suggests this — /project-views/ — is a footgun: it does not create a saved view, it just persists per-user UI state. There is no public-route way to create a IssueView record via API key.

Suggested fix

Promote the existing IssueViewViewSet (already present in plane/app/views/view/base.py and wired in plane/app/urls/views.py) to the /api/v1/ public surface (or expose a /api/v1/.../views/ POST under API-key auth), with the same payload shape documented for SaaS v2. Until that lands, the misleadingly-named /project-views/ POST should either:

  • be moved/renamed to make its per-user semantics explicit (e.g. /project-view-state/ or /project-member-view-props/), or
  • be documented as per-user state in the public API reference.

Related

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