Plane Monorepo Architecture: How pnpm Workspaces and Turbo Power the Codebase

The Plane monorepo uses pnpm workspaces to organize apps and packages into a unified dependency graph, orchestrated by Turbo for cached, parallel task execution across the entire repository.

The makeplane/plane repository manages multiple frontend applications and shared libraries within a single codebase using a monorepo architecture. By combining pnpm workspaces for dependency management and Turbo for build orchestration, the project ensures consistent tooling and fast incremental builds across all workspaces.

pnpm Workspace Configuration

The monorepo’s workspace boundaries are defined in pnpm-workspace.yaml at the repository root. This file declares which directories participate in the workspace while explicitly excluding backend-specific folders.

packages:
  - apps/*
  - packages/*
  - "!apps/api"
  - "!apps/proxy"

The exclusion of apps/api and apps/proxy keeps the Django backend services outside the Node.js dependency graph, preventing pnpm from managing Python dependencies. The same file contains a catalog section that pins exact versions for external dependencies—such as react@18.3.1, turbo@2.9.18, and @atlaskit/pragmatic-drag-and-drop@1.7.4—ensuring every workspace package uses identical dependency versions.

Turbo Task Orchestration and Caching

Task execution and caching logic reside in turbo.json. Turbo constructs a directed acyclic graph (DAG) of tasks based on the dependsOn field, enabling parallel execution where possible while respecting dependency order.

Key configuration patterns include:

  • ^build notation – When a task specifies dependsOn: ["^build"], Turbo guarantees that the build task of every direct dependency completes first.
  • Global dependencies – The globalEnv and globalDependencies arrays define files and environment variables (like VITE_API_BASE_URL) that invalidate cached results when changed.
  • Output caching – Tasks declare output globs such as dist/**, build/**, and .react-router/**, allowing Turbo to store and reuse build artifacts from previous runs.
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": ["dist/**", "build/**", ".react-router/**"]
    },
    "dev": {
      "cache": false,
      "dependsOn": ["^build"],
      "persistent": true
    }
  }
}

The dev task disables caching (cache: false) and marks the process as persistent, enabling live reloads while still ensuring dependencies are built beforehand.

Monorepo Structure: Apps and Packages

Source code divides into two primary groups under the repository root:

Group Location Purpose
Apps apps/* Standalone frontend applications (Web, Space, Live, Admin)
Packages packages/* Reusable libraries consumed by multiple apps

Core Packages in packages/*

Each package maintains its own package.json and extends the monorepo’s base TypeScript configuration. Key packages include:

  • packages/ui – Shared React components with Storybook integration (@plane/ui)
  • packages/shared-state – MobX stores for cross-application state management
  • packages/i18n – Internationalization resources and locale helpers
  • packages/logger – Centralized logging utilities
  • packages/utils – Type guards, date utilities, and helper functions
  • packages/hooks – Custom React hooks for data fetching and theme handling
  • packages/editor – ProseMirror/Tiptap editor integration
  • packages/constants – Shared enums and static values
  • packages/propel – Data-layer abstraction for API communication
  • packages/decorators – TypeScript decorators used across the codebase
  • packages/codemods – Automated code transformation scripts

Frontend Applications in apps/*

Applications are standalone Next.js projects that consume the shared packages:

App Path Technology
Web apps/web Next.js primary user interface
Space apps/space Next.js workspace-focused UI
Live apps/live Real-time collaboration interface
Admin apps/admin Administrative console with Storybook

The root package.json delegates commands to Turbo via scripts like "dev": "turbo run dev --concurrency=18", which launches development servers for all apps simultaneously.

How Dependencies and Tasks Flow

Dependency Resolution – When apps/web imports from @plane/ui, pnpm creates a symlink in node_modules pointing to the source folder. This allows TypeScript to resolve the package as if it were published to npm, eliminating the need for separate publish steps during development.

Task Execution – Turbo reads each workspace’s package.json scripts, builds a dependency graph based on dependsOn declarations, and schedules tasks to maximize parallelism. Running pnpm dev from the root triggers Turbo to start all applications in watch mode, rebuilding only packages that changed.

Caching and Invalidation – Task outputs are stored in the local .turbo folder. Subsequent runs reuse these artifacts unless inputs (source files, environment variables, or .env* files) change. The globalEnv list ensures that changes to variables like VITE_API_BASE_URL correctly invalidate stale caches.

Lint and Format – The root lint-staged configuration runs oxfmt and oxlint on staged files, enforcing consistent style across JavaScript and TypeScript code without manual intervention.

Common Development Workflows

Running the Entire Monorepo in Development Mode

pnpm install          # installs all workspace dependencies

pnpm dev              # starts Turbo, launching dev servers for every app

Turbo concurrently starts the Next.js development servers for apps/web, apps/space, apps/live, and apps/admin.

Building a Single Package

pnpm -F @plane/ui build

The -F (filter) flag targets only the @plane/ui workspace. Turbo automatically builds any dependencies of this package first, then executes the build script.

Adding a New Shared Package

mkdir packages/my-new-lib
pnpm init -w          # creates a workspace package.json

# edit package.json to set name: "@plane/my-new-lib"

pnpm add -w @plane/shared-state   # add a workspace dependency if needed

After creation, the package automatically joins the Turbo task graph because it matches the packages/* pattern.

Running Lint Across the Repository

pnpm check:lint

This executes oxlint --fix --deny-warnings on every workspace source file, ensuring consistent code quality.

Importing Workspace Packages

import { Button } from '@plane/ui'

export const MyPage = () => (
  <div>
    <Button>Click me</Button>
  </div>
)

Because @plane/ui is a workspace package, the import resolves to the local source without requiring a separate npm publish cycle.

Summary

  • pnpm workspaces manage dependencies across apps/* and packages/*, excluding backend folders (apps/api, apps/proxy) from the Node.js graph.
  • Turbo orchestrates tasks using dependsOn to build dependencies first, caches outputs like dist/** and build/**, and respects globalEnv for cache invalidation.
  • The catalog in pnpm-workspace.yaml pins exact versions of React, Turbo, and other externals to ensure consistency.
  • Workspace imports resolve via symlinks, allowing apps to consume shared packages as if they were published modules.
  • Root scripts delegate to Turbo with flags like --concurrency=18, enabling parallel development across all frontend applications.

Frequently Asked Questions

What is the purpose of the catalog section in pnpm-workspace.yaml?

The catalog section pins exact versions for external dependencies—such as react@18.3.1, turbo@2.9.18, and @atlaskit/pragmatic-drag-and-drop@1.7.4—ensuring that every workspace package uses identical versions. This eliminates version drift and "works on my machine" issues across the monorepo.

Why are apps/api and apps/proxy excluded from the pnpm workspace?

These directories contain Django backend services that do not participate in the Node.js/pnpm ecosystem. Excluding them prevents pnpm from attempting to manage Python dependencies or include backend code in the JavaScript build graph, keeping frontend and backend tooling strictly separated.

How does Turbo handle caching for development servers?

The dev task in turbo.json sets cache: false and persistent: true, which prevents Turbo from caching live server outputs while still requiring dependencies to build first via dependsOn: ["^build"]. This ensures hot reloading works correctly without stale cache interference.

Can I build a single package without building the entire monorepo?

Yes. Using the pnpm filter flag (-F or --filter), you can target a specific workspace package. For example, pnpm -F @plane/ui build triggers Turbo to build only the UI package and its dependencies, skipping unrelated apps and packages to save time.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →