# How the Backend of Plane Is Developed and Organized

> Discover how the Plane backend, a monolithic Django app, is developed and organized. Learn about its REST API, Celery background jobs, and domain-driven modules.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: architecture
- Published: 2026-08-25

---

**The Plane backend is a monolithic Django application located in `apps/api` that exposes a REST API via Django REST Framework, processes background jobs through Celery, and organizes code into domain-driven modules with environment-specific settings.**

The open-source project management tool [Plane](https://github.com/makeplane/plane) stores its server-side logic in a single, well-structured Django project. Understanding how the backend of Plane is developed and organized reveals a clean architecture that separates web concerns, data models, and asynchronous processing while maintaining a cohesive monolithic design.

## Project Structure and Entry Points

The backend lives inside the `apps/api` directory and follows standard Django conventions with custom organizational patterns for scalability.

### WSGI and ASGI Bootstrapping

Plane supports both synchronous and asynchronous server interfaces. The entry points reside in the core `plane` package:

- **[`plane/wsgi.py`](https://github.com/makeplane/plane/blob/main/plane/wsgi.py)** – Configures the WSGI application for traditional synchronous request handling.
- **[`plane/asgi.py`](https://github.com/makeplane/plane/blob/main/plane/asgi.py)** – Configures the ASGI application for asynchronous capabilities, required for WebSocket support or async views.

Both files import settings from the hierarchical configuration system and initialize the Django application accordingly.

### Hierarchical Settings Management

Instead of a single [`settings.py`](https://github.com/makeplane/plane/blob/main/settings.py), Plane uses a layered approach in `plane/settings/`:

- **[`common.py`](https://github.com/makeplane/plane/blob/main/common.py)** – Contains base configuration shared across all environments, including `INSTALLED_APPS`, middleware stack, DRF settings, and logging formatters.
- **[`local.py`](https://github.com/makeplane/plane/blob/main/local.py)** – Development overrides enabling debug mode, SQLite support, and the Django debug toolbar.
- **[`production.py`](https://github.com/makeplane/plane/blob/main/production.py)** – Production hardening with PostgreSQL database configurations, Sentry integration, and structured JSON logging.

This pattern allows developers to run `python manage.py runserver --settings=plane.settings.local` locally while production deployments automatically use the production module.

## API Layer Implementation

The Plane backend exposes functionality through a JSON API built on Django REST Framework (DRF), with clear separation between routing, business logic, and data serialization.

### URL Routing and Viewsets

Central dispatch happens in **[`plane/urls.py`](https://github.com/makeplane/plane/blob/main/plane/urls.py)**, which includes app-specific routers. The primary API implementation resides in the `plane/web/` package:

```python

# apps/api/plane/web/views.py

from rest_framework import viewsets
from plane.db.models import Issue
from plane.utils.serializers import IssueSerializer

class IssueViewSet(viewsets.ModelViewSet):
    """CRUD API for issues."""
    queryset = Issue.objects.select_related("project", "workspace")
    serializer_class = IssueSerializer
    permission_classes = [WorkspaceOwnerPermission]

```

Registration occurs in **[`plane/web/urls.py`](https://github.com/makeplane/plane/blob/main/plane/web/urls.py)** using DRF's router pattern:

```python

# apps/api/plane/web/urls.py

from rest_framework.routers import DefaultRouter
from .views import IssueViewSet

router = DefaultRouter()
router.register(r"issues", IssueViewSet, basename="issue")
urlpatterns = router.urls

```

### Permissions and Utilities

Reusable components live in **`plane/utils/`** to prevent code duplication:

- **Permissions** – `plane/utils/permissions/` contains classes like `WorkspaceOwnerPermission` that enforce workspace-level access control.
- **Pagination** – [`plane/utils/paginator.py`](https://github.com/makeplane/plane/blob/main/plane/utils/paginator.py) defines consistent pagination behavior across list endpoints.
- **Filtering** – `plane/utils/filters/` houses DRF filter backends for complex query operations.

## Data Layer and Domain Models

Plane organizes its database schema through Django models grouped by domain in **`plane/db/models/`**. This directory contains the core entities including:

- **Workspace** – Top-level organizational containers
- **Project** – Software projects within workspaces
- **Issue** – Tickets and work items with relationships to projects and users
- **User** – Authentication and profile data

The models directory follows a flat structure where each major domain object has its own file, making imports predictable and maintaining clear boundaries between business entities.

## Background Processing Architecture

Heavy or time-consuming operations are offloaded to Celery workers to keep HTTP responses fast.

### Celery Configuration

The task queue is configured in **[`plane/celery.py`](https://github.com/makeplane/plane/blob/main/plane/celery.py)**, which defines the Celery app instance, broker URLs (typically Redis), and task routing rules.

### Task Organization

Background tasks reside in **`plane/bgtasks/`** and are auto-discovered via `plane.bgtasks.apps.BgtasksConfig` listed in `INSTALLED_APPS`. Typical implementations follow this pattern:

```python

# apps/api/plane/bgtasks/email_notification_task.py

from celery import shared_task
from plane.utils.email import send_email

@shared_task
def send_issue_notification(user_id, issue_id):
    """Send an email when an issue gets assigned."""
    send_email(
        to_user_id=user_id,
        subject="You've been assigned a new issue",
        template_name="issue_assignment.html",
        context={"issue_id": issue_id},
    )

```

Common task categories include:
- Email notifications for issue assignments and mentions
- Webhook dispatch to external integrations
- Data export generation (CSV, JSON)
- Periodic cleanup and maintenance jobs

## Supporting Infrastructure

Beyond the core request-response cycle, the backend includes several specialized modules.

### Middleware and Request Handling

The **`plane/middleware/`** directory contains custom WSGI/ASGI middleware for:
- Request body size limits
- Request ID injection for distributed tracing
- Response time logging

### License and Feature Gating

The **`plane/license/`** app manages commercial features in the self-hosted edition. It stores instance-specific configuration and gates premium functionality through license key validation, keeping open-source and enterprise code paths separate but co-located.

### API Documentation

Plane uses `drf-spectacular` to generate OpenAPI 3.0 specifications automatically. Extensions and schema customizations live in **`plane/utils/openapi/`**, ensuring the Swagger UI always reflects the current state of the JSON API without manual maintenance.

## Summary

The backend of Plane demonstrates a mature Django monolith architecture that prioritizes:

- **Configuration inheritance** through layered settings files separating development from production concerns
- **Modular API design** using DRF viewsets with centralized permission and pagination utilities
- **Asynchronous scalability** via Celery for email, webhooks, and data exports
- **Domain-driven organization** with models, background tasks, and web views separated into focused packages
- **Automatic documentation** generated from code via OpenAPI extensions

All components reside under `apps/api/plane`, providing a single deployable unit while maintaining clear internal boundaries that facilitate testing and feature development.

## Frequently Asked Questions

### Is the Plane backend a microservices architecture?

No, the Plane backend is intentionally a **monolithic Django application**. All functionality—from issue management to webhook processing—runs within a single deployable unit in `apps/api`. Background tasks are handled asynchronously via Celery workers, but these share the same codebase and database, maintaining the operational simplicity of a monolith while gaining horizontal scaling for background work.

### Why does Plane use separate settings files instead of environment variables alone?

Plane uses a **hierarchical settings structure** ([`common.py`](https://github.com/makeplane/plane/blob/main/common.py), [`local.py`](https://github.com/makeplane/plane/blob/main/local.py), [`production.py`](https://github.com/makeplane/plane/blob/main/production.py)) to keep environment-specific logic organized and reviewable. While environment variables control secrets and hostnames, the settings files define structural differences like installed apps (debug toolbar in local), database engines (SQLite vs PostgreSQL), and logging formats. This approach prevents conditional spaghetti code and makes configuration differences explicit in version control.

### How does Plane handle database migrations and model changes?

Database models are defined in **`plane/db/models/`** and use Django's standard ORM migration system. Since the backend is a monolith, migrations live alongside the models in the respective app directories. The [`manage.py`](https://github.com/makeplane/plane/blob/main/manage.py) entry point provides the standard `makemigrations` and `migrate` commands, allowing both development and production deployments to evolve the schema predictably.

### What message broker does Plane use for Celery tasks?

According to the source configuration in [`plane/celery.py`](https://github.com/makeplane/plane/blob/main/plane/celery.py), Plane typically uses **Redis** as the Celery broker and result backend. This setup handles task queuing for background operations like email notifications and webhook dispatches, providing the durability and visibility required for production task processing while maintaining low latency for task submission from the web processes.