| |

Yarn 07 ๐Ÿงถ Yarn Workspaces and Monorepos

โฑ๏ธ Estimated read: 10 min | |

A monorepo stores multiple packages in a single repository. Yarn Workspaces is the mechanism that makes this practical: it installs all packages’ dependencies together, links packages to each other without publishing to a registry, and provides commands to run scripts across the entire project. In Yarn Berry (2+), the feature set expands significantly with the workspace: protocol, constraints, catalogs, and focused installs. The goal is the same across versionsโ€”treat a collection of packages as a single project for installation and linkingโ€”but the tools for controlling that behavior differ.

Key point: A Yarn workspace is any directory with a package.json that is matched by the workspaces glob in the root manifest. The root itself is always a workspace, and it must be private so that it cannot be published.


Why workspaces exist

The multi-repository problem. When a project consists of a frontend, a backend, and a shared library, splitting them into separate repositories means every change to the shared library requires publishing a new version, updating the consumers, and managing three sets of pull requests. A monorepo with workspaces collapses this: the shared library is linked directly, and a change to it is immediately visible to the consuming packages without any registry round-trip .

The dependency duplication problem. Without workspaces, each package in a monorepo has its own node_modules and its own copy of React, TypeScript, and every shared dependency. With Yarn’s default node-modules linker, shared dependencies are hoisted to the root node_modules, installed once, and resolved by walking up the directory tree . One lockfile at the root governs the entire project .

The linking problem. Package A depends on Package B, both in the same repository. Without a workspace protocol, Package A must reference Package B by a version number that exists on the registry. The workspace: protocol (Yarn 2+) tells Yarn to resolve the dependency to the local workspace regardless of the version, and replaces the protocol with the actual version only when the package is published .

The orchestration problem. Running a build across ten packages requires running ten commands in dependency order. yarn workspaces foreach provides the traversal: -A selects all workspaces, -p runs in parallel, and -t orders execution topologically so that a package’s dependencies build before the package itself .

The CI problem. A full yarn install in a large monorepo installs every dependency for every package. When deploying a single application, this is wasteful. yarn workspaces focus installs only the dependencies required by a specific workspace and its transitive dependencies, skipping the rest of the monorepo entirely .

The trade-off. Workspaces reduce duplication and simplify cross-package development, but they introduce a single point of failure. A broken script in one workspace can affect the entire repository’s CI. Good hygieneโ€”ensuring every workspace has its own declared dependencies and that status checks passโ€”is essential to keep the monorepo manageable .


a. Declaring workspaces and linking packages

Workspaces are declared in the root package.json with a workspaces array containing glob patterns. Every directory matching a pattern and containing a package.json becomes a workspace. The root manifest must have "private": true; Yarn refuses to treat a publishable package as a workspace root .

{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ]
}

A common layout separates applications (apps/) from shared libraries (packages/). After adding the workspaces field, running yarn install at the root resolves and installs every workspace’s dependencies and links the internal packages together .

To depend on another workspace, Yarn 2+ uses the workspace: protocol. The range can be workspace:*, workspace:^, or an explicit version. Yarn always resolves the dependency to the local package; the range only determines what the published manifest will contain if the package is ever published .

{
  "name": "@myorg/app",
  "dependencies": {
    "@myorg/utils": "workspace:^"
  }
}

On Yarn 1 (Classic), there is no workspace: protocol. The dependency is referenced by a version range that matches the local package’s version, and Yarn links it during install .


b. Running commands across workspaces

yarn workspaces foreach is the traversal command. It runs a script in every workspace, or a subset defined by flags. The flags control scope, ordering, and concurrency.

# Run "build" in every workspace, in dependency order
yarn workspaces foreach -Apt run build

# Run "test" only in workspaces changed since origin/main
yarn workspaces foreach -Apt --since=origin/main run test

# Run "lint" in one workspace and its dependencies
yarn workspaces foreach -Rpt --from '@myorg/app' run lint

The -A (or --all) flag includes every workspace. The -t (or --topological) flag orders execution so that dependencies run before dependents. The -p (or --parallel) flag runs scripts concurrently. The --since flag limits execution to workspaces with changes since a git reference .

In Yarn 4, foreach is built in. In Yarn 3, it requires the workspace-tools plugin: yarn plugin import workspace-tools. In Yarn 1, the equivalent is yarn workspaces run build, which runs the script sequentially across all workspaces .


c. Focused installs and monorepo hygiene

yarn workspaces focus installs only what a specific workspace needs. Given a workspace name, it resolves the workspace’s dependencies, extends the list to include transitive workspace dependencies, and excludes everything else. This is useful for CI pipelines that deploy a single application or for local development when only one package is being modified .

# Install only the dependencies for @myorg/app
yarn workspaces focus @myorg/app

# Install all workspaces but only production dependencies
yarn workspaces focus -A --production

In Yarn 4, focus works out of the box. In Yarn 3, it requires the workspace-tools plugin. The --production flag omits devDependencies, which is the correct behavior for building a deployable artifact .

Hoisting is the default with the node-modules linker. Shared dependencies are installed once at the root, and any workspace can import them without declaring them. This is convenient but dangerous: a workspace that imports a hoisted dependency it never declared will break when the package is published or moved. The fix is to declare every import in the workspace’s own package.json. To restrict hoisting, set nmHoistingLimits in .yarnrc.yml. Yarn PnP avoids the problem entirely by resolving imports from a lockfile-driven map rather than node_modules .


Complete Example Session

# ============================================
# PART 1: INITIALIZE THE MONOREPO ROOT
# ============================================

mkdir my-monorepo && cd my-monorepo
yarn init -p
# Creates package.json with "private": true
# ============================================
# PART 2: DECLARE WORKSPACES
# ============================================

# Edit package.json:
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["apps/*", "packages/*"]
}
# ============================================
# PART 3: CREATE A WORKSPACE
# ============================================

mkdir -p packages/utils
cd packages/utils
yarn init -p
# Edit package.json: "name": "@myorg/utils", "version": "1.0.0"
# ============================================
# PART 4: LINK A WORKSPACE
# ============================================

# In apps/web/package.json:
{
  "dependencies": {
    "@myorg/utils": "workspace:^"
  }
}
# ============================================
# PART 5: INSTALL EVERYTHING
# ============================================

cd ../..
yarn install
# Hoists shared deps to root node_modules,
# links @myorg/utils into apps/web.
# ============================================
# PART 6: RUN SCRIPTS ACROSS WORKSPACES
# ============================================

yarn workspaces foreach -Apt run build
# -A: all workspaces
# -p: parallel
# -t: topological (deps first)
# ============================================
# PART 7: FOCUSED INSTALL FOR DEPLOYMENT
# ============================================

yarn workspaces focus @myorg/app --production
# Installs only app's deps, omitting devDependencies.
# ============================================
# PART 8: SHARE DEPENDENCY VERSIONS (YARN 4)
# ============================================

# .yarnrc.yml:
catalog:
  react: ^19.0.0

# In workspace package.json:
"dependencies": {
  "react": "catalog:"
}
# ============================================
# PART 9: ENFORCE CONSISTENCY WITH CONSTRAINTS
# ============================================

# yarn.config.cjs:
module.exports = {
  async constraints({ Yarn }) {
    for (const dep of Yarn.dependencies({ ident: 'react' })) {
      dep.update('19.0.0');
    }
  }
};
# Run: yarn constraints --fix

The nine parts trace the workspace lifecycle: initializing the root, declaring globs, creating a package, linking via the workspace: protocol, installing, running scripts with foreach, using focused installs for deployment, sharing dependency versions via catalogs, and enforcing consistency with constraints.


Quick Reference

Workspace Declaration

ElementLocation
workspaces arrayRoot package.json
private: trueRoot package.json (required)
workspace: protocolWorkspace package.json dependencies
catalog: protocolYarn 4, .yarnrc.yml

Foreach Flags

FlagPurpose
-A, --allEvery workspace
-p, --parallelRun concurrently
-t, --topologicalDependencies first
--since=<ref>Changed since git ref
-R, --recursiveWorkspace and its dependencies

Focus Flags

FlagPurpose
(none)Current workspace and deps
-A, --allEntire project
--productionOmit devDependencies

Best Practices

โœ… Do This:

{
  "private": true,
  "workspaces": ["packages/*"]
}
<!-- Root must be private for workspaces -->       # โœ…
{
  "dependencies": {
    "@myorg/utils": "workspace:^"
  }
}
<!-- Use workspace: protocol for internal deps --> # โœ…
yarn workspaces foreach -Apt run build
<!-- Topological order for build scripts -->        # โœ…

โŒ Don’t Do This:

{
  "name": "root",
  "workspaces": ["packages/*"]
}
<!-- Missing "private": true -->                   # โŒ
# Rely on hoisted dependency without declaring it
<!-- Breaks when package is published -->          # โŒ
yarn workspaces foreach -A run build
<!-- No -t; dependencies may build after dependents --> # โŒ

Common Pitfalls

PitfallWhy It HappensFix
workspaces ignoredRoot not marked private: trueAdd "private": true
Internal dep not linkedNo workspace: protocolUse workspace:^ or workspace:*
Hoisting hides undeclared importsnode-modules linker hoists to rootDeclare every import in workspace manifest
foreach not found in Yarn 3Plugin not installedyarn plugin import workspace-tools
Focus installs too muchFocus includes transitive depsExpected behavior; scope workspace correctly

Real-World Examples

1. Root Workspace Declaration

{
  "private": true,
  "workspaces": ["apps/*", "packages/*"]
}

2. Link Internal Package

{
  "dependencies": {
    "@myorg/ui": "workspace:*"
  }
}

3. Build All, Topologically

yarn workspaces foreach -Apt run build

4. Run Test Only on Changed

yarn workspaces foreach -Apt --since=origin/main run test

5. Focus on One App

yarn workspaces focus @myorg/app

6. Focus Production Only

yarn workspaces focus -A --production

7. Catalog for Shared Version

catalog:
  react: ^19.0.0

8. Reference Catalog

{
  "dependencies": {
    "react": "catalog:"
  }
}

9. Constraints Fix

yarn constraints --fix

10. Yarn 1 Equivalent

yarn workspaces run build

Visual

Monorepo Structure with Workspaces

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  my-monorepo/                                โ”‚
โ”‚  โ”œโ”€โ”€ package.json (private: true)            โ”‚
โ”‚  โ”‚   โ””โ”€โ”€ workspaces: ["apps/*", "packages/*"]โ”‚
โ”‚  โ”œโ”€โ”€ yarn.lock                               โ”‚
โ”‚  โ”œโ”€โ”€ node_modules/ (hoisted)                 โ”‚
โ”‚  โ”œโ”€โ”€ apps/                                   โ”‚
โ”‚  โ”‚   โ”œโ”€โ”€ web/                                โ”‚
โ”‚  โ”‚   โ”‚   โ””โ”€โ”€ package.json                    โ”‚
โ”‚  โ”‚   โ””โ”€โ”€ api/                                โ”‚
โ”‚  โ”‚       โ””โ”€โ”€ package.json                    โ”‚
โ”‚  โ””โ”€โ”€ packages/                               โ”‚
โ”‚      โ””โ”€โ”€ utils/                              โ”‚
โ”‚          โ””โ”€โ”€ package.json                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ”‚ yarn install
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Root node_modules/                          โ”‚
โ”‚  โ”œโ”€โ”€ react/ (hoisted, shared)                โ”‚
โ”‚  โ”œโ”€โ”€ typescript/ (hoisted, shared)           โ”‚
โ”‚  โ””โ”€โ”€ @myorg/utils/ (symlink to packages)     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Every package shares the root node_modules and the single yarn.lock.

Foreach Execution Order

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  yarn workspaces foreach -Apt run build      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Topological sort (dependencies first)       โ”‚
โ”‚                                              โ”‚
โ”‚  1. @myorg/utils (no deps)                   โ”‚
โ”‚  2. @myorg/ui (depends on utils)             โ”‚
โ”‚  3. @myorg/app (depends on ui, utils)        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Build runs in order, in parallel where      โ”‚
โ”‚  dependencies permit.                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Without -t, the build order is arbitrary. With -t, a package’s dependencies are built before the package itself.

Focused Install

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Full install                                โ”‚
โ”‚  apps/web + apps/api + packages/utils        โ”‚
โ”‚  All dependencies installed.                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  yarn workspaces focus @myorg/app            โ”‚
โ”‚                                              โ”‚
โ”‚  apps/web only.                              โ”‚
โ”‚  packages/utils (dependency) included.       โ”‚
โ”‚  apps/api excluded.                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Focused installs reduce CI time and disk usage when deploying a single application.


Summary

ItemValue
Workspace declarationworkspaces array in root package.json
Root requirement"private": true
Internal dependencyworkspace:^ or workspace:*
HoistingDefault with node-modules linker
Traversal commandyarn workspaces foreach
Topological flag-t, --topological
Focused installyarn workspaces focus <name>
Production focus--production
Shared versions (Yarn 4)catalog: in .yarnrc.yml
Consistency rulesyarn constraints

Key takeaways:

  • The root must be private. Yarn workspaces only function when the root package.json has "private": true. The root itself is always a workspace .
  • The workspace: protocol links internal packages. It resolves to the local workspace regardless of registry versions, and the range determines the published manifest .
  • Hoisting is the default. Shared dependencies are installed once at the root. This is efficient but permits undeclared imports. Declare every dependency in the workspace that uses it .
  • foreach orchestrates across workspaces. The -A flag selects all, -p runs in parallel, and -t orders execution by dependency .
  • focus installs only what a workspace needs. It is the command for CI and deployment when a full install is unnecessary .
  • Yarn 4 adds catalogs and constraints. Catalogs share dependency versions across workspaces; constraints enforce consistency rules .

Remember: A Yarn workspace is a directory with a package.json that the root manifest points to. The root owns the lockfile and the hoisted node_modules. The workspace: protocol links packages without a registry. The foreach command runs scripts across the project. The focus command installs only what a specific workspace needs. Together, these features turn a collection of packages into a single, coherent project.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!