Plane Django API Backend Architecture: A Deep Dive into Service Layers and Code Organization
Plane's Django API backend follows a modular, layered architecture that separates routing, business logic, data access, and background processing into distinct Django apps within the makeplane/plane repository.
This architecture leverages Django REST Framework (DRF) for API endpoints, Celery for asynchronous tasks, and a comprehensive middleware stack for cross-cutting concerns. The codebase organizes functionality into logical domains—such as plane.api, plane.db, and plane.bgtasks—promoting clear separation of concerns and maintainable service layers.
Routing Layer and URL Resolution
The entry point for all HTTP requests is the root URL dispatcher located in plane/urls.py. This module mounts sub-routers for different API surfaces and administrative interfaces.
# plane/urls.py
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")),
]
Source: [plane/urls.py](https://github.com/makeplane/plane/blob/preview/apps/api/plane/urls.py)
This design isolates public APIs, authenticated internal endpoints, and web frontend routes into separate URL configurations, enabling independent versioning and access control policies for each surface area.
Core Application Structure
The Django project organizes functionality into discrete apps registered in INSTALLED_APPS within plane/settings/common.py. This modular approach separates concerns across analytics, authentication, database models, and utility functions.
# plane/settings/common.py
INSTALLED_APPS = [
# Django core
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.staticfiles",
# In-house apps
"plane.analytics",
"plane.app",
"plane.space",
"plane.bgtasks",
"plane.db",
"plane.utils",
"plane.web",
"plane.middleware",
"plane.license",
"plane.api",
"plane.authentication",
# Third-party
"rest_framework",
"corsheaders",
"django_celery_beat",
]
Source: [plane/settings/common.py](https://github.com/makeplane/plane/blob/preview/apps/api/plane/settings/common.py#L96-L114)
The plane.db app houses core domain models for issues, cycles, and projects, while plane.app and plane.api contain the primary business logic and REST endpoints respectively.
Views, Serializers, and Data Flow
The API layer implements the Model-View-Serializer pattern using Django REST Framework. Viewsets in plane/api/views/ handle HTTP semantics, while serializers in plane/api/serializers/ manage validation and JSON conversion.
View Implementation:
# plane/api/views/issue.py
class IssueViewSet(viewsets.ModelViewSet):
queryset = Issue.objects.all()
serializer_class = IssueSerializer
permission_classes = [IsAuthenticated, IssuePermission]
Source: [issue.py](https://github.com/makeplane/plane/blob/preview/apps/api/plane/api/views/issue.py)
Corresponding Serializer:
# plane/api/serializers/issue.py
class IssueSerializer(serializers.ModelSerializer):
class Meta:
model = Issue
fields = "__all__"
Source: [issue.py](https://github.com/makeplane/plane/blob/preview/apps/api/plane/api/serializers/issue.py)
This separation ensures that data validation rules remain decoupled from HTTP transport concerns, allowing serializers to be reused across different view contexts.
Access Control and Query Filtering
Fine-grained authorization logic resides in plane/utils/permissions/, implementing DRF's BasePermission interface for object-level access control.
# plane/utils/permissions/workspace.py
class WorkspacePermission(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.workspace.is_member(request.user)
Source: [workspace.py](https://github.com/makeplane/plane/blob/preview/apps/api/plane/utils/permissions/workspace.py)
Dynamic query filtering utilizes django-filter backends defined in plane/utils/filters/:
# plane/utils/filters/filterset.py
class IssueFilter(FilterSet):
status = CharFilter(field_name="status")
assigned_to = NumberFilter(field_name="assignee__id")
Source: [filterset.py](https://github.com/makeplane/plane/blob/preview/apps/api/plane/utils/filters/filterset.py)
These filters integrate directly with viewsets to enable complex query parameters without cluttering view logic.
Middleware Stack and Request Processing
The middleware pipeline in plane/settings/common.py processes requests through a chain of security, logging, and routing layers.
# plane/settings/common.py
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"plane.authentication.middleware.session.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"plane.middleware.request_body_size.RequestBodySizeLimitMiddleware",
"plane.middleware.logger.APITokenLogMiddleware",
"plane.middleware.logger.RequestLoggerMiddleware",
]
Source: [plane/settings/common.py](https://github.com/makeplane/plane/blob/preview/apps/api/plane/settings/common.py#L20-L36)
Custom middleware components handle cross-cutting concerns such as request body size limiting, API token logging, and database routing for read-replicas, keeping these concerns out of business logic.
Background Task Processing with Celery
Asynchronous operations—such as email notifications, webhook deliveries, and data exports—are offloaded to Celery workers defined in plane/bgtasks/.
# plane/bgtasks/email_notification_task.py
@app.task
def email_notification_task(user_id, subject, body):
user = User.objects.get(id=user_id)
send_mail(subject, body, from_email, [user.email])
Source: [email_notification_task.py](https://github.com/makeplane/plane/blob/preview/apps/api/plane/bgtasks/email_notification_task.py)
Celery configuration specifies the broker URL and task imports:
# plane/settings/common.py
CELERY_BROKER_URL = os.getenv("AMQP_URL", f"amqp://{RABBITMQ_USER}:{RABBITMQ_PASSWORD}@{RABBITMQ_HOST}:{RABBITMQ_PORT}/{RABBITMQ_VHOST}")
CELERY_IMPORTS = (
"plane.bgtasks.issue_automation_task",
"plane.bgtasks.exporter_expired_task",
)
Source: [plane/settings/common.py](https://github.com/makeplane/plane/blob/preview/apps/api/plane/settings/common.py#L26-L38)
This architecture prevents long-running operations from blocking HTTP request/response cycles.
External Services and Configuration
The backend integrates with external infrastructure through dedicated settings modules:
- Storage:
plane/settings/storage.pyconfigures S3/MinIO backends for static and media files - Caching:
plane/settings/redis.pydefines Redis connections for caching and Celery result storage - Documentation:
plane/settings/openapi.pyconfigures DRF Spectacular for OpenAPI schema generation whenENABLE_DRF_SPECTACULARis enabled
These configurations are centralized in plane/settings/common.py, ensuring environment-specific variables remain separated from application code.
Summary
- Modular app structure: The backend splits functionality across
plane.api,plane.app,plane.db, andplane.bgtasksfor clear separation of concerns - DRF-based API layer: Viewsets and serializers in
plane/api/implement REST endpoints with standardized validation and permission checks - Comprehensive middleware: Custom middleware in
plane/middleware/handles logging, body-size limits, and database routing - Async task processing: Celery workers in
plane/bgtasks/handle notifications and background jobs via RabbitMQ/Redis - Centralized configuration: Settings modules organize database, cache, storage, and authentication parameters in
plane/settings/
Frequently Asked Questions
How does Plane handle database routing and read replicas?
The middleware stack includes a custom database routing layer that directs read queries to replica databases while maintaining write operations on the primary instance. This configuration lives within the MIDDLEWARE list in plane/settings/common.py and works alongside Django's database router configuration to distribute query load.
What permission system does Plane use for workspace and project access?
Plane implements object-level permissions through DRF's BasePermission classes located in plane/utils/permissions/. The WorkspacePermission and ProjectPermission classes check membership relationships via methods like is_member() on workspace and project objects, ensuring users can only access resources within their authorized organizations.
How are background tasks triggered from API views?
Views enqueue Celery tasks using the .delay() method on task functions imported from plane.bgtasks/. For example, when an issue is created, the view calls email_notification_task.delay() to asynchronously send notifications without blocking the HTTP response, as implemented in the viewsets within plane/api/views/.
Where does Plane store uploaded files and static assets?
File storage configuration resides in plane/settings/storage.py, which supports S3-compatible storage via MinIO. The system uses Django's storages backend abstraction to handle media uploads and static file collection, allowing deployment flexibility across different cloud providers or on-premises infrastructure.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →