How Plane Organizes Apps and Packages in Its PNPM Monorepo Workspace
Plane structures its codebase as a PNPM monorepo where apps/* contains deployable applications and packages/* houses shared libraries, with non-Node.js services explicitly excluded from the workspace.
The open-source project management platform Plane manages its TypeScript frontend architecture using a PNPM monorepo workspace. This configuration centralizes shared code while allowing each application to maintain independent build tooling and deployment lifecycles.
Workspace Configuration in pnpm-workspace.yaml
The monorepo boundary is defined at the repository root in pnpm-workspace.yaml. This file declares which directories PNPM should treat as workspace members:
packages:
- apps/*
- packages/*
- "!apps/api"
- "!apps/proxy"
The apps/* and packages/* globs instruct PNPM to treat every subdirectory under these paths as an individual workspace member. The exclusions (!apps/api and !apps/proxy) deliberately omit the Django backend server and Nginx proxy configuration from the JavaScript dependency tree, as these services do not use Node.js or PNPM.
App Structure Under apps/
Each folder under apps/ represents a standalone application with its own package.json, build configuration, and deployment pipeline. These applications consume shared packages via the workspace:* protocol.
Key Applications and Their Roles
apps/web– The primary React single-page application built with Vite, serving as the main client interface.apps/admin– A dedicated administrative panel using React and Vite for management functions.apps/space– A specialized workspace UI application with its own routing and state management.apps/live– A Node.js-based real-time collaboration server handling WebSocket connections.
The apps/web/package.json demonstrates how applications declare internal dependencies:
{
"dependencies": {
"@plane/constants": "workspace:*",
"@plane/editor": "workspace:*",
"@plane/hooks": "workspace:*",
"@plane/i18n": "workspace:*",
"@plane/propel": "workspace:*",
"@plane/services": "workspace:*",
"@plane/shared-state": "workspace:*",
"@plane/types": "workspace:*",
"@plane/ui": "workspace:*",
"@plane/utils": "workspace:*",
"@plane/tailwind-config": "workspace:*",
"@plane/typescript-config": "workspace:*"
}
}
Package Structure Under packages/
All reusable code lives under packages/, organized by technical concern. Each package compiles independently (typically using tsup or vite) and exposes specific functionality to consuming applications.
Shared Libraries Overview
| Package | Path | Purpose |
|---|---|---|
| utils | packages/utils |
General-purpose helpers for strings, files, dates, and emoji handling. |
| constants | packages/constants |
Enum definitions, theme constants, and UI configuration values. |
| hooks | packages/hooks |
React hooks such as useLocalStorage and useOutsideClickDetector. |
| i18n | packages/i18n |
Internationalization utilities and locale data management. |
| editor | packages/editor |
Rich-text editor components and associated styling. |
| types | packages/types |
Centralized TypeScript declarations re-exported as @plane/types. |
| shared-state | packages/shared-state |
MobX stores for cross-application state management. |
| tailwind-config | packages/tailwind-config |
Shared Tailwind CSS configuration presets. |
| typescript-config | packages/typescript-config |
Base TypeScript compiler configurations inherited by apps. |
For example, workspace constants are defined in packages/constants/src/workspace.ts, while date formatting utilities reside in packages/utils/src/date.ts.
Cross-Package Dependency Resolution
PNPM creates a virtual node_modules structure for each application, automatically linking internal packages via the workspace:* protocol. This approach provides several architectural benefits:
- Zero-publish workflow – Changes to
packages/utilsare immediately available toapps/webwithout npm publishing or version bumping. - Single source of truth – The
packages/typesmodule centralizes API interfaces, ensuring all applications reference identical TypeScript definitions. - Deduplicated builds – Shared packages compile once, and each application consumes the same JavaScript bundle, reducing runtime overhead.
Practical Implementation Example
Applications import shared functionality using scoped package names:
// apps/web/src/components/Example.tsx
import { formatDate } from '@plane/utils';
function TimestampDisplay({ isoString }: { isoString: string }) {
return <time dateTime={isoString}>{formatDate(isoString)}</time>;
}
This import resolves directly to packages/utils/src/date.ts, with PNPM handling the filesystem link during development and the build-time resolution for production bundles.
Summary
- Plane defines its monorepo scope in
pnpm-workspace.yamlusingapps/*andpackages/*globs, explicitly excluding Django and Nginx directories. - Applications under
apps/(web, admin, space, live) maintain independent configurations while consuming shared libraries. - Packages under
packages/provide utilities, constants, types, and UI components via theworkspace:*protocol. - TypeScript configurations and Tailwind presets are distributed as internal packages to ensure consistency across the codebase.
Frequently Asked Questions
What does the workspace:* protocol do in Plane's monorepo?
The workspace:* protocol tells PNPM to resolve the dependency from the local filesystem rather than the npm registry. In Plane, this links applications like apps/web directly to source code in packages/, enabling instantaneous reflection of changes without publishing packages or updating version numbers.
Why are apps/api and apps/proxy excluded from the PNPM workspace?
These directories contain the Django backend API and Nginx proxy configuration, respectively. Since they do not use Node.js or require JavaScript dependency management, excluding them prevents PNPM from attempting to install Node modules in these directories and keeps the JavaScript workspace focused on the frontend codebase.
How are TypeScript configurations managed across the monorepo?
Plane distributes shared compiler settings through packages/typescript-config, which contains base tsconfig.json files. Applications extend these configurations in their local tsconfig.json files, ensuring consistent strictness rules and path aliases while allowing app-specific overrides for build targets.
What build tools does Plane use for apps versus packages?
Applications typically use Vite for bundling (evident in apps/web/vite.config.ts), while shared packages often use tsup or lighter TypeScript compilation. This separation allows applications to benefit from Vite's hot module replacement and optimization, while packages prioritize fast compilation and minimal bundle sizes for library distribution.
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 →