Workflows & Data Flows
Key user journeys in Plane traced through the frontend, API, and database layers
Workflows & Data Flows
This page traces the most important user journeys through Plane's codebase — from a button click in the browser all the way to the database and back. Understanding these flows is the fastest way to build a mental model of how Plane works.
Reading Guide
Each workflow is presented as a numbered sequence showing what happens at each layer. The Frontend → API → Database → back pattern repeats across almost every feature.
How a Request Flows Through Plane
Before diving into specific workflows, here's the general pattern that every user action follows:
Browser (User Action)
│
▼
React Component ← captures user input
│ calls
▼
MobX Store Action ← optimistic UI update
│ calls
▼
Service Class ← HTTP request via Axios
│
▼
Caddy Proxy ← routes to correct backend
│
▼
Django View (DRF) ← session auth → permission check → validate → process
│
▼
PostgreSQL ← read/write data
│
▼
Django Response (JSON) ← serialize and return
│
▼
Service Class ← parse response
│
▼
MobX Store ← update store with server data
│
▼
React Component ← MobX reactivity triggers re-renderWorkflow 1: Creating an Issue
This is the most common action in Plane and touches almost every architectural layer.
User fills the create-issue form
The user opens the issue creation modal, fills in title, description, assignees, priority, state, and labels. The rich text editor uses Tiptap (built on ProseMirror) for formatting.
Component calls the MobX store
CreateIssueModal → issueStore.createIssue(workspaceSlug, projectId, issueData)The store may perform an optimistic update — adding the issue to the local list immediately so the UI feels instant.
Store calls the service layer
The @plane/services package wraps Axios calls:
IssueService.create(workspaceSlug, projectId, issueData)
→ POST /api/workspaces/{slug}/projects/{id}/issues/Django processes the request
- Session middleware validates the session cookie
- Permission class checks the user has Member or Admin role on the project
- DRF Serializer validates the payload (required fields, foreign key existence, data types)
- View creates the Issue model in PostgreSQL, along with
IssueSequencefor the issue number - IssueActivity record is created to track the creation event
- Celery task is queued (via RabbitMQ) to send email notifications to watchers
- Webhook task is queued if the project has webhook integrations configured
- Serialized response is returned as JSON
Store updates with server response
The service returns the created issue with its server-generated id, created_at, and sequence number. The MobX store replaces the optimistic entry with the real data.
UI re-renders automatically
MobX's fine-grained reactivity detects the store change. Any component observing the issue list (Kanban board, list view, etc.) re-renders to show the new issue.
Why Optimistic Updates?
When you create an issue, it appears in the list before the server responds. This makes the UI feel instant. If the server returns an error, the store rolls back the optimistic update and shows an error notification.
Workflow 2: Real-Time Collaborative Editing
This is Plane's most technically sophisticated workflow — multiple users editing the same Page document simultaneously.
User opens a Page
The React component initializes a Yjs document — a CRDT data structure that can be merged conflict-free.
WebSocket connection established
The Tiptap editor (via @hocuspocus/provider) connects to the Live server (apps/live/) via WebSocket:
wss://plane.example.com/live/collaboration/{page_id}Hocuspocus loads the document
The live server uses @hocuspocus/extension-database to fetch the latest document state from the Django API and initializes the shared Yjs document.
User A types text
- The local Yjs document is updated immediately (no round-trip)
- The change is encoded as a Yjs update message (binary diff)
- The update is sent over the WebSocket to Hocuspocus
Hocuspocus broadcasts to all clients
The server receives the update, applies it to the authoritative document, and broadcasts via Redis pub/sub to all other connected clients.
Other clients merge automatically
Each client's Yjs instance receives the update and merges it with their local state. CRDT guarantees: the merge is automatic, conflict-free, and order-independent.
Periodic persistence
Hocuspocus periodically saves the document state back to the Django API (which writes to PostgreSQL). This ensures durability even if the live server restarts.
For PMs: Why CRDTs Over Operational Transform?
Google Docs uses Operational Transform (OT), which requires a central server to order operations. CRDTs are mathematically guaranteed to converge — no central ordering needed. This makes them simpler to scale and more resilient to network issues.
Workflow 3: Sprint Cycle Management
How a team plans and tracks a sprint using Plane's Cycles feature.
Team lead creates a Cycle
A Cycle has a name, start date, and end date. Created via:
POST /api/workspaces/{slug}/projects/{id}/cycles/Issues are added to the Cycle
Issues link to cycles through the CycleIssue join table (many-to-many). An issue can belong to multiple cycles.
POST /api/.../cycles/{cycle_id}/cycle-issues/
Body: { "issues": ["issue-uuid-1", "issue-uuid-2"] }Progress is tracked automatically
As team members update issue states (To Do → In Progress → Done), the Cycle's progress metrics update:
- Completion percentage — based on issue state categories
- Scope changes — tracks issues added/removed mid-cycle
Cycle completes
At the end date, incomplete issues can be bulk-transferred to the next cycle.
Workflow 4: GitHub Bidirectional Sync
How Plane keeps issues in sync with GitHub repositories.
Admin configures GitHub integration
The admin authenticates with GitHub OAuth and selects which repository to sync with which Plane project. Configuration is stored via the integration models (integration/github.py).
Sync from GitHub → Plane
When a GitHub issue is created or updated, a webhook from GitHub hits Plane's API. A Celery worker processes the webhook:
- Finds or creates the corresponding Plane issue
- Maps GitHub labels → Plane labels
- Syncs title, description, and comments
- Maps GitHub open/closed → Plane states
Sync from Plane → GitHub
When a Plane issue linked to GitHub is updated, a Celery worker:
- Calls the GitHub API to update the linked issue
- Syncs state changes (Plane "Done" → GitHub "closed")
- Syncs new comments bidirectionally
Conflict resolution
If both sides change simultaneously, the most recent update wins. The sync tracks timestamps to detect conflicts.
Workflow 5: Notification Pipeline
How Plane delivers notifications when something changes.
Issue Updated (state changed, comment added, assignment changed)
│
▼
IssueActivity created in DB ← audit trail
│
▼
Celery task queued via RabbitMQ ← async, non-blocking
│
├──► In-App Notification ← stored in Notification table
│
├──► Email Notification ← sent via configured SMTP
│ (batched every 5 min via Celery Beat)
│
└──► Webhook ← POST to configured URL
external integrationsWhy Async via RabbitMQ?
Notifications are processed asynchronously through Celery workers using RabbitMQ as the message broker. This means the API response to "update issue state" returns instantly — the user doesn't wait for emails to be sent. Celery Beat batches email notifications every 5 minutes to avoid flooding inboxes.
Information Flow Summary
| From | To | Mechanism | Example |
|---|---|---|---|
| Browser → API | REST over HTTPS | Creating/updating issues | |
| Browser ↔ Live Server | WebSocket (CRDT) | Real-time page editing | |
| API → Database | Django ORM (TypeORM) | All data persistence | |
| API → Valkey | Cache set/get | Session data, workspace settings | |
| API → RabbitMQ → Celery | Task queue (AMQP) | Email, webhooks, cleanup jobs | |
| GitHub → API | Webhook (HTTP POST) | Issue sync from GitHub | |
| API → GitHub | REST API call | Issue sync to GitHub | |
| API → S3/MinIO | boto3 SDK | File upload/download | |
| Live → Valkey | Redis pub/sub | Broadcasting CRDT updates |