How the Django Backend API is Organized in Plane

Plane uses a modular, app-per-domain architecture where independent Django apps are stitched together via a root URL dispatcher located at apps/api/plane/urls.py, separating core CRUD operations, public read-only endpoints, licensing, authentication, and versioned experimental features into distinct namespaces.

The backend of the open-source project management tool Plane (makeplane/plane) is built as a classic Django project that eschews monolithic design in favor of clear functional separation. Each major domain—workspaces and issues, public sharing, licensing, authentication—lives in its own Django app with dedicated URL configurations, viewsets, and serializers. This organization provides a clean, versioned REST API for the React frontend while maintaining strict boundaries between internal business logic and external-facing resources.

High-Level Domain Architecture

Plane splits the backend into six distinct Django apps, each registered under a specific URL prefix in the root dispatcher. This separation ensures that public integrations, internal tools, and administrative functions operate in isolated contexts.

Core Business Logic (plane.app)

The primary API lives under the api/ prefix and handles authenticated CRUD operations for workspaces, projects, issues, cycles, and modules. Implemented in apps/api/plane/app/urls.py, this app registers viewsets using Django REST Framework routers. All endpoints here require authentication and enforce workspace-level permissions through custom classes imported from utils/permissions/.

Public Read-Only API (plane.space)

Exposed under api/public/, the Space app (apps/api/plane/space/urls.py) provides unauthenticated, read-only access to workspace and project data for embedding and external integrations. Viewsets here filter results based on public visibility flags, ensuring that only explicitly shared resources are accessible without a JWT token.

Instance Licensing (plane.license)

The license app (apps/api/plane/license/urls.py) manages multi-tenant instance provisioning under api/instances/. It handles creation, renewal, and validation of Plane licenses. Endpoints here are typically consumed by deployment automation or admin dashboards to check instance validity against the licensing server.

Versioned Experimental API (plane.api)

Future-proofing and experimental features reside in apps/api/plane/api/urls.py, mounted at api/v1/. This namespace hosts new endpoints—such as analytics or reporting features—that may evolve independently of the stable api/ surface, allowing the Plane team to iterate without breaking existing clients.

Authentication (plane.authentication)

All identity flows—JWT issuance, OAuth callbacks, magic-link login, and password reset—are encapsulated in apps/api/plane/authentication/urls.py under the auth/ prefix. This isolation centralizes security logic, including token refresh and session management, away from business data endpoints.

Web UI (plane.web)

Server-side rendered pages, including the Django admin panel and error templates, are served by apps/api/plane/web/urls.py from the root path /. While not part of the JSON API, this app completes the backend by handling HTML responses and admin interface routing.

Root URL Dispatcher

The entry point for all routing is apps/api/plane/urls.py. This file imports Django's include function and maps each domain to its dedicated sub-router:

urlpatterns = [
    path("api/", include("plane.app.urls")),
    path("api/public/", include("plane.space.urls")),
    path("api/instances/", include("plane.license.urls")),
    path("api/v1/", include("plane.api.urls")),
    path("auth/", include("plane.authentication.urls")),
    path("", include("plane.web.urls")),
]

When the ENABLE_DRF_SPECTACULAR setting is true, the dispatcher appends additional routes exposing the OpenAPI schema at api/schema/ and interactive documentation at api/schema/swagger-ui/ and api/schema/redoc/.

Shared Infrastructure and Utilities

All domain apps import common utilities from apps/api/plane/utils/, ensuring consistent behavior across the API surface:

  • Permissions (utils/permissions/base.py): Custom DRF permission classes that evaluate workspace, project, and page-level access controls before viewset execution.
  • Pagination (utils/paginator.py, utils/global_paginator.py): Unified pagination logic applied to all list endpoints, returning standardized count, next, and previous metadata.
  • OpenAPI Helpers (utils/openapi/): Decorators and response schemas consumed by drf-spectacular to generate accurate OpenAPI 3.0 specifications.
  • Error Handling (utils/error_codes.py, utils/exception_logger.py): Standardized error code constants and centralized exception logging for debugging production issues.

Request Lifecycle

An incoming HTTP request flows through the following stages, as implemented in the Plane Django backend:

  1. URL Resolution: Django's resolver matches the request path against the prefixes defined in apps/api/plane/urls.py and delegates to the included sub-router.
  2. Router Dispatch: The DRF router (defined in the specific app's urls.py) forwards the request to the appropriate viewset method (e.g., list, create, retrieve).
  3. Permission Enforcement: Custom permission classes from utils/permissions/ validate the user's JWT token and workspace membership before executing business logic.
  4. Serialization: The viewset's serializer validates incoming data against model constraints and converts outgoing ORM instances to JSON.
  5. Business Logic Execution: The viewset interacts with Django models (e.g., Issue, Project) and utility functions to perform the requested operation.
  6. Response Rendering: DRF renders the response as JSON, optionally wrapping list results with pagination metadata from the global paginator.

Configuration and API Documentation

Global settings reside in apps/api/plane/settings/common.py. The boolean flag ENABLE_DRF_SPECTACULAR controls whether the auto-generated OpenAPI schema endpoints are exposed. When enabled, the drf-spectacular library introspects all registered viewsets to produce interactive documentation at /api/schema/swagger-ui/.

Practical API Usage Examples

The following examples demonstrate how clients interact with the Plane Django backend API.

List Workspaces (Authenticated)

Request workspace data through the core app:

GET /api/workspaces/
Authorization: Bearer <JWT_TOKEN>

Response includes pagination metadata:

{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {"id":"ws_01","name":"Acme Corp"},
    {"id":"ws_02","name":"Beta Ltd"}
  ]
}

This endpoint is handled by the WorkspaceViewSet registered in apps/api/plane/app/urls.py.

Create Issue via Public API

Insert issues into a public project without authentication:

POST /api/public/projects/{project_id}/issues/
Content-Type: application/json

{
  "title": "Bug in login flow",
  "description": "Users cannot log in after password reset."
}

The plane.space app processes this request, filtering by public permissions defined in apps/api/plane/space/urls.py.

Initiate passwordless login through the authentication app:

POST /auth/magic-signin/
Content-Type: application/json

{
  "email": "user@example.com"
}

The backend generates a one-time link and emails the user; confirmation hits POST /auth/magic-signin/confirm/ to issue a JWT, as implemented in apps/api/plane/authentication/views.py.

Summary

  • Modular Design: Plane separates core CRUD, public access, licensing, authentication, and web UI into distinct Django apps (plane.app, plane.space, plane.license, plane.api, plane.authentication, plane.web).
  • Central Routing: The root dispatcher at apps/api/plane/urls.py maps URL prefixes to sub-routers, maintaining clean namespace separation (e.g., api/, api/public/, auth/).
  • Shared Utilities: Common permissions, pagination, and error handling live in apps/api/plane/utils/ and are reused across all viewsets.
  • Lifecycle Consistency: Every request passes through standardized permission checks, serialization, and pagination layers managed by Django REST Framework.
  • Optional Documentation: The ENABLE_DRF_SPECTACULAR setting toggles auto-generated OpenAPI schemas and Swagger UI endpoints.

Frequently Asked Questions

Where is the main entry point for Plane's API routing?

The root URL configuration is defined in apps/api/plane/urls.py. This file includes all sub-applications—such as plane.app for core endpoints and plane.space for public APIs—mapping them to distinct prefixes like api/ and api/public/.

How does Plane separate authenticated internal APIs from public integrations?

Plane isolates public endpoints in the plane.space app mounted at api/public/, which uses read-only viewsets and public-only permissions. Internal CRUD operations reside in plane.app under api/ and enforce authentication via custom permission classes in apps/api/plane/utils/permissions/base.py.

What controls the auto-generated API documentation in Plane?

The ENABLE_DRF_SPECTACULAR boolean in apps/api/plane/settings/common.py determines whether drf-spectacular exposes the OpenAPI schema at api/schema/ and interactive UIs at api/schema/swagger-ui/ and api/schema/redoc/. This feature is optional and typically enabled in development environments.

Which Django app handles instance licensing and provisioning?

The plane.license app, configured in apps/api/plane/license/urls.py, manages multi-tenant instance lifecycle under the api/instances/ prefix. It handles license validation, creation, and renewal separate from project management data.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →