npm workspaces: manage multiple packages in one repository
Stop copying shared code between repositories. npm workspaces let you manage multiple packages under one root with a single install.
14 Mar 2026, 12:48 UTC

The problem: two packages, two repositories, one bug
You maintain a React application and a shared UI component library. Today they live in separate Git repositories. When you fix a button component in the library, you publish a new version, update the application's dependency, and redeploy. If a teammate forgets to bump the version, the application runs stale code. If you need to refactor a shared type, you coordinate changes across two pull requests and two CI pipelines.
npm workspaces remove that split. They let you keep multiple packages in a single repository, install their dependencies once, and treat each package as a first-class npm package with its own package.json. This post shows how to set up a workspace, verify dependency deduplication, and run scripts across every package.
How npm workspaces link packages
A workspace is an npm feature, available since npm 7 and stable in npm 8+, that declares a set of nested packages under a single root package.json. The root file lists glob patterns in a workspaces array. A glob is a wildcard pattern such as packages/* that matches folder names. When you run npm install at the root, npm installs all dependencies into one top-level node_modules folder and symlinks each workspace package so other workspaces can import it by name.
Hoisting means npm moves shared dependencies to the top level instead of installing a copy inside every workspace. This reduces duplication, but it also means all workspaces normally resolve to the same version of a hoisted package.
Worked example: a monorepo with an app and a UI library
Run these commands in a terminal at the root of your repository. You need write permission to the directory; avoid sudo because it changes file ownership and can break later installs.
- Create the folder structure and initialize each nested package:
mkdir -p packages/app packages/ui
cd packages/app && npm init -y && cd ../ui && npm init -y && cd ..
Each npm init -y creates a minimal package.json inside its folder. The root also needs a package.json:
{
"name": "monorepo-root",
"private": true,
"workspaces": [
"packages/*"
]
}
Add a shared dependency to the UI library so we can verify deduplication later:
- Run from the root:
npm install lodash --workspace=packages/ui
This command edits packages/ui/package.json and the root package-lock.json. It does not publish anything.
Now install everything:
npm install
Expected result: a single top-level node_modules contains lodash, and workspace symlinks link packages/app and packages/ui into the root tree.
Check that the dependency is deduplicated:
npm ls lodash
If the output shows one version under node_modules and references from both workspaces, hoisting worked. If you see two different versions, npm could not hoist the dependency because of conflicting version ranges.
Run a script in every workspace. Suppose each package.json has:
"scripts": {
"check": "echo checking $npm_package_name"
}
Execute it across all workspaces from the root:
npm run check --workspaces
To target only the UI library:
npm workspace packages/ui run check
Trade-off: hoisting can hide version conflicts
The main limitation is aggressive hoisting. If packages/app requires react@18 and packages/ui requires react@17, npm cannot safely hoist a single react to the top level without breaking one of them. You may see peer dependency warnings or runtime errors where the wrong version loads.
Practical check: after npm install, run
npm ls react
Inspect the tree. If workspaces resolve to different versions and your build tool expects one copy, you may need to align version ranges or avoid workspaces for that dependency.
Some older build plugins also assume a traditional flat layout where every package has its own nested node_modules. Those tools may not follow workspace symlinks without configuration changes.
Rollback
Because npm install creates node_modules and updates package-lock.json, you can undo the operation by deleting those files and restoring any earlier package.json from version control:
rm -rf node_modules package-lock.json
git checkout -- package.json packages/*/package.json
Then reinstall if needed.
What to try next
Start with two small packages and a glob pattern that matches your folders. Verify with npm ls before adding more packages or CI steps. If your dependency tree stays consistent and your scripts run across workspaces, you have replaced multi-repo coordination with a single install command.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.