Link Internal Packages Reliably with pnpm Workspaces and workspace:*
Use workspace:* in pnpm workspaces to link internal packages without publishing. A minimal pnpm-workspace.yaml and package.json setup, how pnpm resolves via symlinks in node_modules/.pnpm, and limits around peer dependencies, build order, and pnpm-only compatibility.
21 Aug 2026, 17:51 UTC

Use workspace:* to link internal code without publishing
The practical decision is to declare internal dependencies with "workspace:*" instead of a semver range, and let pnpm workspaces manage a single install for the repo. That gives you immediate local changes in consumers, one pnpm-lock.yaml, and deduplication via pnpm's content-addressable store without publishing packages.
Workspaces are a pnpm feature. The workspace protocol is pnpm-specific and will be treated as an invalid range by npm or yarn, so CI and all contributors must use pnpm.
Minimal workspace layout
At the repository root create pnpm-workspace.yaml to define which folders are workspaces.
packages:
- apps/*
- packages/*
Each internal package keeps a normal package.json. For an internal dependency use workspace:*.
// packages/ui/package.json
{
"name": "@repo/ui",
"version": "0.0.0",
"main": "src/index.ts"
}
// apps/web/package.json
{
"name": "web",
"private": true,
"dependencies": {
"@repo/ui": "workspace:*"
}
}
Run install from the repo root with write permission to the project directory.
pnpm install
Expected check: a single pnpm-lock.yaml is written at the root and node_modules contains a .pnpm directory with entries for workspace packages. Changing the packages globs in pnpm-workspace.yaml does not retroactively update the lockfile; run a fresh install after structural changes.
How pnpm resolves workspace:* under the hood
When pnpm install runs, workspace:* is resolved to the local package directory. pnpm does not copy files into node_modules. It creates a symlink through its store layout under node_modules/.pnpm so the consumer sees the local code. Changes in the source package are visible in the consumer without reinstall because the symlink points to the workspace folder.
pnpm's isolation model keeps most dependencies non-hoisted. That prevents phantom dependencies, where a package accidentally imports a dependency of its dependency that is not listed in its own package.json. The tradeoff is that tools assuming a flat node_modules can break, and binaries are not guaranteed at the repository root node_modules/.bin.
Build order is not inferred. If a workspace package compiles to dist, you must ensure producers build before consumers, for example with a workspace script or a preinstall step. pnpm will link source, not built artifacts, unless you point main/exports to the built output.
Limits and operational constraints
- pnpm-specific protocol. Using workspace:* makes the repo un-installable with npm or yarn.
- Strict peer dependencies. pnpm surfaces missing peers that other managers silently allow. Explicit peerDependencies declarations are required.
- Symlinked source exposure. The symlink exposes the package directory, so consumers can accidentally import files outside the package exports field.
- Install-time path rewriting. Packages that rewrite paths during install may be incompatible with pnpm's symlink layout.
Common mistakes
- Mixing workspace:* with a published version range for the same package. Pick one resolution strategy.
- Forgetting to list a package in the workspace globs. If a folder is not matched, pnpm treats it as external and workspace:* resolution fails.
- Relying on hoisted binaries. Scripts that expect a binary at the root node_modules will fail; use pnpm exec or workspace scripts.
Verify the link is working
Inspect resolution from the repo root.
pnpm why @repo/ui
Check that the resolved path points to a file: path inside the local packages folder.
pnpm list --depth=0
Confirm workspace packages are listed with workspace protocol resolved to local paths.
Inspect the store layout.
ls node_modules/.pnpm
Look for an entry for the workspace package that symlinks back to packages/ui. A trivial source change in packages/ui should be visible in apps/web without reinstall, confirming the symlink is live.
Risk: removing the workspace folder or renaming it breaks the symlink and install. Keep the folder structure stable and commit pnpm-workspace.yaml and pnpm-lock.yaml.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.