Architecture Overview
Plane's three-tier architecture — database, API, frontend, state management, and real-time services
Architecture Overview
For Product Managers
This page explains how Plane is structured as a software system. You don't need to understand every technical detail — focus on the diagrams and the "Why It Matters" callouts to build a mental model of how the product works under the hood.
System Architecture Diagram
Plane follows a three-tier architecture with a real-time collaboration layer, served through a Caddy reverse proxy:
┌─────────────────────────────────────────────────────────────────┐
│ USERS (Browser) │
└──────────────────────────────┬──────────────────────────────────┘
│
┌──────────▼──────────┐
│ Caddy Proxy (2.10) │ ← automatic HTTPS
│ Port 80 / 443 │ routes by path
└──┬──────┬───────┬───┘
│ │ │
┌───────────────▼─┐ ┌──▼─────┐ ┌▼───────────────┐
│ Web / Admin / │ │ API │ │ Live Server │
│ Space (React) │ │(Django │ │ (Hocuspocus) │
│ Nginx │ │ DRF) │ │ WebSocket CRDT │
│ :3000/3001/3002 │ │ :8000 │ │ :3100 │
└──────────────────┘ └──┬─────┘ └─────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌───────▼───────┐
│ PostgreSQL │ │ Valkey │ │ MinIO / S3 │
│ 15.7 │ │ 7.2.11 │ │ (File Storage)│
│ (Primary DB) │ │ (Cache) │ │ │
└──────────────┘ └─────────────┘ └───────────────┘
│
┌──────▼──────┐
│ RabbitMQ │
│ 3.13.6 │
│ (MQ Broker) │
└──────┬──────┘
│
┌──────▼──────┐
│ Celery │
│ Workers │
│ + Beat │
└─────────────┘Why It Matters
Every user request flows through Caddy → the appropriate service. REST API calls hit Django; real-time editing uses WebSockets to the Hocuspocus live server. Background work (email notifications, cleanup, imports) runs through Celery workers using RabbitMQ as the message broker. Valkey (Redis-compatible) handles caching and sessions. This separation means the API stays fast even when heavy async work is running.
The Three Tiers
1. Frontend Layer (React 18 + MobX)
The frontend is built with React 18, React Router 7 (with SSR support), and MobX for state management. Three separate apps share packages:
| App | Port | Purpose |
|---|---|---|
| web | 3000 | Main user-facing application (issues, boards, pages) |
| admin | 3001 | Instance administration (/god-mode/) |
| space | 3002 | Public-facing project views (shareable links) |
MobX Store Hierarchy (180+ files):
RootStore
├── router.store → routing state
├── theme.store → UI theme
├── instance.store → instance config
├── user/
│ ├── profile.store → current user
│ ├── account.store → auth account
│ └── settings.store → user preferences
├── workspace/
│ ├── index.ts → workspace data
│ ├── home.ts → home dashboard
│ ├── webhook.store → webhook management
│ └── api-token.store → API tokens
├── project/
│ ├── project.store → project CRUD
│ └── project_filter → view filters
├── issue/ → issue stores per context
│ ├── project/ → project issue list
│ ├── cycle/ → cycle issues
│ ├── module/ → module issues
│ └── workspace-draft/ → draft issues
├── pages/ → wiki pages
├── notifications/ → notification center
├── timeline/ → activity timeline
└── ...Why MobX + SWR?
MobX provides fine-grained reactivity: when a single issue's title changes, only the components rendering that title re-render. SWR complements MobX by handling data fetching and cache invalidation. MobX stores hold the canonical state; SWR fetches and revalidates from the API.
2. API Layer (Django 4.2 + DRF)
The backend is Django 4.2.29 LTS with Django REST Framework 3.15.2, living at apps/api/.
URL Routing Structure:
/api/ → plane.app.urls (main app — workspaces, projects, issues)
/api/public/ → plane.space.urls (public/anonymous endpoints)
/api/instances/ → plane.license.urls (instance management)
/api/v1/ → plane.api.urls (API key–based access)
/auth/ → plane.authentication (login, OAuth, magic links)App-level URL modules (20+):
plane/app/urls/
├── analytic.py, api.py, asset.py, cycle.py, estimate.py
├── external.py, intake.py, issue.py, module.py, notification.py
├── page.py, project.py, search.py, state.py, user.py
├── views.py, webhook.py, workspace.py, timezone.py, exporter.pyKey API Patterns:
| Pattern | Description |
|---|---|
| Hierarchical URLs | Workspaces → Projects → Issues/Cycles/Modules |
| Standard REST | GET, POST, PATCH, DELETE |
| Throttling | 30/min anon, 60/min API key, custom per-endpoint |
| Serializer validation | DRF serializers validate all input/output |
| API documentation | drf-spectacular (OpenAPI/Swagger) |
3. Database Layer (PostgreSQL 15.7)
PostgreSQL with 30+ model files and 126 migrations.
Core Entity-Relationship Map:
┌──────────────┐
│ Workspace │ ← multi-tenancy root
└──────┬───────┘
│ has many
┌───────────┼───────────┐
│ │ │
┌──────▼──────┐ │ ┌──────▼──────┐
│ Project │ │ │ Member │
└──────┬───────┘ │ └─────────────┘
│ │
┌──────────┼──────┬───┼──────┐
│ │ │ │ │
┌─────▼────┐ ┌──▼───┐ ┌▼───▼─┐ ┌──▼─────┐
│ Issue │ │Cycle │ │Module│ │ State │
│ (central)│ │ │ │ │ │ │
└────┬─────┘ └──┬───┘ └──┬───┘ └────────┘
│ │ │
│ CycleIssue ModuleIssue
│ (M2M) (M2M)
│
┌────┼────┬──────────┬─────────┐
│ │ │ │ │
Comment Activity Reaction AttachmentBase Model Pattern — all workspace models inherit:
| Field | Purpose |
|---|---|
id | UUID primary key |
created_at | Auto-set on creation |
updated_at | Auto-set on every save |
created_by | Foreign key to User |
updated_by | Foreign key to User |
deleted_at | Soft-delete timestamp (null = active) |
workspace | Foreign key enforcing multi-tenancy |
Key Design Patterns
Multi-Tenancy
Every query is scoped to a workspace — enforced at the view level so no data leaks between organizations.
Soft Deletion
Records are never physically deleted. deleted_at is set, and a daily Celery beat task permanently removes records older than HARD_DELETE_AFTER_DAYS.
Activity Audit Trail
Every change to an Issue generates an IssueActivity record tracking what changed, who changed it, and when.
Community / Enterprise Gating
The web app has ce/ (community edition) and ee/ (enterprise edition) directories, enabling feature gating between the free and paid versions.
Real-Time Collaboration
The Live server (apps/live/) runs Hocuspocus on port 3100:
| Component | Technology |
|---|---|
| Server | Hocuspocus 2.15.2 (Node.js) |
| CRDT | Yjs 13.6.20 |
| Editor binding | y-prosemirror (Yjs ↔ ProseMirror) |
| Persistence | @hocuspocus/extension-database + Redis |
| Transport | WebSocket via express-ws |
When users edit Pages collaboratively, the Hocuspocus server merges their changes using CRDT math, persists to the database, and broadcasts updates to all connected clients.
Performance Architecture
Database
- Indexes on frequently queried fields (workspace_id, project_id, state_id)
- Read replica support for horizontal scaling
API
- Valkey caching for frequently accessed data
- Throttling at multiple levels (anon, API key, asset, email verification)
- GZip middleware for response compression
Frontend
- MobX computed values — derived state only recalculates when dependencies change
- Code splitting — lazy-loaded routes via React Router
- SWR caching — stale-while-revalidate pattern for API data