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:
^buildnotation – When a task specifiesdependsOn: ["^build"], Turbo guarantees that thebuildtask of every direct dependency completes first.- Global dependencies – The
globalEnvandglobalDependenciesarrays define files and environment variables (likeVITE_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 managementpackages/i18n– Internationalization resources and locale helperspackages/logger– Centralized logging utilitiespackages/utils– Type guards, date utilities, and helper functionspackages/hooks– Custom React hooks for data fetching and theme handlingpackages/editor– ProseMirror/Tiptap editor integrationpackages/constants– Shared enums and static valuespackages/propel– Data-layer abstraction for API communicationpackages/decorators– TypeScript decorators used across the codebasepackages/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/*andpackages/*, excluding backend folders (apps/api,apps/proxy) from the Node.js graph. - Turbo orchestrates tasks using
dependsOnto build dependencies first, caches outputs likedist/**andbuild/**, and respectsglobalEnvfor cache invalidation. - The catalog in
pnpm-workspace.yamlpins 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →