# How to Set Up pnpm Workspaces in a Monorepo: Complete Configuration Guide

> Master pnpm workspaces in your monorepo. Configure pnpm workspaces easily with our guide and streamline development for efficient dependency management.

- Repository: [Yangshun Tay/tech-interview-handbook](https://github.com/yangshun/tech-interview-handbook)
- Tags: how-to-guide
- Published: 2026-02-25

---

**Configure pnpm workspaces by creating a [`pnpm-workspace.yaml`](https://github.com/yangshun/tech-interview-handbook/blob/main/pnpm-workspace.yaml) file at your repository root that defines glob patterns for your `apps/*` and `packages/*` directories, then run `pnpm install` to link dependencies across all workspaces.**

Setting up pnpm workspaces in a monorepo allows you to manage multiple applications and shared libraries from a single repository while maintaining a unified dependency store. The yangshun/tech-interview-handbook repository demonstrates this architecture by organizing runnable code into `apps/` and `packages/` directories, using pnpm to handle cross-package linking and dependency deduplication. This guide walks through the exact configuration files and commands used in that production monorepo.

## Understanding the Monorepo Structure

The yangshun/tech-interview-handbook organizes code into two primary directories that pnpm treats as workspace members.

### The apps/ Directory

The `apps/*` glob pattern captures standalone applications that can be deployed independently. In this repository, this includes the Docusaurus website and the Next.js portal, each with their own [`package.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/package.json) and entry points.

### The packages/ Directory

The `packages/*` glob pattern contains reusable libraries and configuration packages shared across applications. This includes shared ESLint configurations (`eslint-config-tih`), Tailwind CSS presets (`tailwind-config`), and centralized TypeScript configurations (`tsconfig`).

## Configuring pnpm Workspaces

Setting up pnpm workspaces requires two configuration files at the repository root to define workspace boundaries and ensure tooling compatibility.

### Step 1: Create the Workspace Manifest

Create a [`pnpm-workspace.yaml`](https://github.com/yangshun/tech-interview-handbook/blob/main/pnpm-workspace.yaml) file at the repository root to declare which directories contain workspace packages:

```yaml
packages:
  - 'apps/*'
  - 'packages/*'

```

This manifest tells pnpm to treat every subdirectory within `apps/` and `packages/` as an independent workspace member while sharing a single `node_modules` store.

### Step 2: Declare Workspaces in package.json

Add a `workspaces` field to your root [`package.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/package.json) that mirrors the manifest patterns:

```json
{
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}

```

This ensures compatibility with tools like npm and Turborepo that read workspace definitions from [`package.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/package.json) rather than the pnpm-specific manifest.

### Step 3: Pin the pnpm Version

Specify the exact pnpm version in your root [`package.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/package.json) `engines` field to ensure consistency across environments:

```json
{
  "engines": {
    "pnpm": "8.15.8"
  }
}

```

Enable and install this version using Corepack:

```bash
corepack enable
corepack prepare pnpm@8.15.8 --activate

```

## Installing Dependencies and Running Commands

Once configured, pnpm manages dependencies across all workspaces from the repository root.

### Initial Installation

Run the following command at the repository root to install all dependencies:

```bash
pnpm install

```

This creates a single `.pnpm-store` at the root and links each workspace's `node_modules` to the appropriate versions, automatically hoisting shared dependencies to save disk space.

### Running Scripts Across Workspaces with Turborepo

The yangshun/tech-interview-handbook uses Turborepo to orchestrate commands across workspaces. The root [`package.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/package.json) defines scripts that invoke `turbo`:

```json
{
  "scripts": {
    "dev": "turbo dev",
    "build": "turbo build"
  }
}

```

Run development servers for all workspaces:

```bash
pnpm dev

```

Turborepo respects the workspace dependency graph, launching the portal UI (`--filter=portal...`) and UI library (`--filter=ui...`) in the correct order based on the [`turbo.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/turbo.json) pipeline configuration.

## Adding New Packages to the Monorepo

To extend the monorepo with a new shared library:

1. Create a directory under `packages/` or `apps/`:

```bash
mkdir -p packages/utils
cd packages/utils
pnpm init -y

```

2. Write your TypeScript or JavaScript code and configure the [`package.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/package.json) with the appropriate name and entry points.

3. Return to the repository root and run:

```bash
pnpm install

```

The new package is instantly linked into the workspace graph and can be imported by other workspaces using the declared package name.

## Shared Tooling and Configuration

The monorepo centralizes development tooling to ensure consistency across all workspaces.

### Turborepo Pipeline Configuration

The [`turbo.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/turbo.json) file defines reusable pipelines for `build`, `test`, `lint`, and `dev` tasks, ensuring that Turborepo caches outputs and runs tasks in dependency order.

### Centralized TypeScript Configurations

Shared TypeScript presets live in `packages/tsconfig/` and are referenced by individual apps via `extends`:

```json
{
  "extends": "tsconfig/react-library.json"
}

```

This guarantees identical compiler options across the Docusaurus website, Next.js portal, and shared libraries.

### Unified Linting and Styling

ESLint and Tailwind configurations are packaged as `packages/eslint-config-tih` and `packages/tailwind-config`, then consumed by apps to eliminate duplication and maintain code style consistency.

## Summary

Setting up pnpm workspaces in a monorepo requires configuring the workspace manifest and root package.json to define workspace boundaries, then using pnpm's unified installation to link dependencies across packages.

- Create [`pnpm-workspace.yaml`](https://github.com/yangshun/tech-interview-handbook/blob/main/pnpm-workspace.yaml) at the repository root with glob patterns for `apps/*` and `packages/*`
- Mirror these patterns in the root [`package.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/package.json) `workspaces` field for tooling compatibility
- Pin the pnpm version (e.g., `8.15.8`) in `engines` and enable via Corepack
- Run `pnpm install` from the root to create a shared store and link all workspace dependencies
- Use Turborepo to orchestrate scripts across workspaces while respecting dependency graphs

## Frequently Asked Questions

### What is the difference between pnpm-workspace.yaml and the workspaces field in package.json?

The [`pnpm-workspace.yaml`](https://github.com/yangshun/tech-interview-handbook/blob/main/pnpm-workspace.yaml) file is the primary manifest that pnpm reads to determine which directories contain workspace packages, while the `workspaces` field in [`package.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/package.json) exists primarily for compatibility with npm and tools like Turborepo that expect workspace definitions in the package manifest. Both should contain identical glob patterns to ensure consistent behavior across tooling.

### How does pnpm handle dependency installation differently than npm in a workspace?

pnpm creates a single content-addressable store (`.pnpm-store`) at the repository root and hard-links packages into each workspace's `node_modules`, automatically deduplicating shared dependencies across the monorepo. This approach uses significantly less disk space than npm's nested `node_modules` structure while maintaining strict isolation between packages, and it enables instant linking of local workspace packages without publishing to a registry.

### Can I add a new workspace after the initial setup?

Yes, you can add new workspaces at any time by creating a new directory under the configured glob patterns (such as `apps/` or `packages/`), initializing it with a [`package.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/package.json), and running `pnpm install` from the repository root. pnpm automatically detects the new workspace, links it into the dependency graph, and makes it available for import by other workspaces using the package name declared in its [`package.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/package.json).

### Why should I pin the pnpm version in a monorepo?

Pinning the pnpm version (such as `8.15.8`) in the root [`package.json`](https://github.com/yangshun/tech-interview-handbook/blob/main/package.json) `engines` field ensures that all developers and CI environments use the exact same package manager version, preventing inconsistencies in lockfile formats, workspace resolution logic, and dependency tree structures that could arise from version mismatches. This practice, combined with Corepack, guarantees reproducible installs across the entire team.