Generate one SBOM per workspace when each package ships separately, and one for the whole repository when it ships as a single product. Both npm sbom and the CycloneDX npm tool let you select workspaces, so the boundary is a setting you choose, not something the monorepo forces on you.
The core idea
An npm workspace is a package inside a larger repository, managed from one root. The question for an SBOM is the same as for any SBOM: what is the product, and which components belong to it? A repository can contain one product, several, or a mix of apps and shared libraries that nobody ships alone.
If the SBOM boundary does not match the shipped boundary, someone downstream gets either missing components or components that are not theirs.
What the tools offer
The npm documentation lists workspace and workspaces settings that run a command in the context of selected workspaces. A workspace value can be a name, a path to a workspace directory, or a path to a parent directory, which selects all workspaces inside it.
The CycloneDX npm tool has its own options. By default it includes dependencies for all configured workspaces. You can restrict it to named workspaces, turn workspaces off, and control whether the root package's dependencies are included. The README marks the workspace features as experimental, so test results on your repository before depending on them.
| Option behaviour | Result |
|---|---|
| Default | Dependencies for all configured workspaces |
| Select workspaces explicitly | Only those workspaces, and the root is not included unless you ask |
| Disable workspaces | Workspace dependencies left out |
| Include the workspace root explicitly | Root dependencies added to the chosen workspaces |
A decision guide
- Does one deployable contain everything? Produce a single repository wide SBOM with the defaults.
- Do several packages ship on their own, such as separate services? Produce one file per workspace by selecting each in turn.
- Is there a shared library that several shipped apps use? Each app file should list that library as a dependency. Do not rely on a separate file for the library to cover the apps.
- Does the root package hold build tooling only? Leave the root out of per package files, so tooling does not show up as part of a product.
Steps for per package files
- List your workspaces with the names npm knows them by.
- Loop over them in CI, running the generator once per workspace with that workspace selected.
- Name each output after the workspace and the commit.
- Check the root decision. If the root holds real runtime dependencies, include it deliberately.
- Spot check one file: pick a dependency you know belongs to another workspace and confirm it is absent.
Step 5 catches the common mistake of a boundary that is wider than you intended.
Hoisting can confuse readers
npm hoists shared packages to the root node_modules, so the layout on disk does not mirror which workspace asked for which package. The generators work from npm's dependency tree for each selected workspace, not from folder structure, but if you inspect files by hand you may be misled. Trust the tool's workspace selection, and verify with a known example.
Duplicates
If a package appears at several levels of the tree, the CycloneDX tool does not deduplicate artificially, so you may see it more than once. That reflects how it was installed, and is covered in a separate post.
After generation
Run vulnerability matching per file so findings route to the owning team. Safeguard's SBOM and dependency scanning handles npm lockfiles and workspaces, and the free scan is an easy way to see how your repository is read.
Quick check
Are the workspace options stable?
The CycloneDX npm README marks its workspace features as experimental. Test them on your own repository and pin the tool version.
Should the root package be in each workspace file?
Only if the root has real runtime dependencies that belong to the product. Build tooling at the root usually should stay out.
Sources
Last reviewed: 19 October 2026.