Learn from OSS
Plane

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-render

Workflow 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

  1. Session middleware validates the session cookie
  2. Permission class checks the user has Member or Admin role on the project
  3. DRF Serializer validates the payload (required fields, foreign key existence, data types)
  4. View creates the Issue model in PostgreSQL, along with IssueSequence for the issue number
  5. IssueActivity record is created to track the creation event
  6. Celery task is queued (via RabbitMQ) to send email notifications to watchers
  7. Webhook task is queued if the project has webhook integrations configured
  8. 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

  1. The local Yjs document is updated immediately (no round-trip)
  2. The change is encoded as a Yjs update message (binary diff)
  3. 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:

  1. Finds or creates the corresponding Plane issue
  2. Maps GitHub labels → Plane labels
  3. Syncs title, description, and comments
  4. Maps GitHub open/closed → Plane states

Sync from Plane → GitHub

When a Plane issue linked to GitHub is updated, a Celery worker:

  1. Calls the GitHub API to update the linked issue
  2. Syncs state changes (Plane "Done" → GitHub "closed")
  3. 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 integrations

Why 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

FromToMechanismExample
Browser → APIREST over HTTPSCreating/updating issues
Browser ↔ Live ServerWebSocket (CRDT)Real-time page editing
API → DatabaseDjango ORM (TypeORM)All data persistence
API → ValkeyCache set/getSession data, workspace settings
API → RabbitMQ → CeleryTask queue (AMQP)Email, webhooks, cleanup jobs
GitHub → APIWebhook (HTTP POST)Issue sync from GitHub
API → GitHubREST API callIssue sync to GitHub
API → S3/MinIOboto3 SDKFile upload/download
Live → ValkeyRedis pub/subBroadcasting CRDT updates

What's Next