Safeguard
Developer Tools•

How do you create one SBOM per workspace package with pnpm?

Use pnpm sbom with `--split` and an `--out` path containing the package name to write one SBOM per workspace package. Steps, output shape, and pitfalls.

Safeguard Security Team
6 min read

Run pnpm sbom with --split to generate a separate SBOM for each selected workspace package. Without --out, the output is NDJSON on stdout. With --out, the path must contain the %s placeholder so each package gets its own file. The split option was added in pnpm 11.8.0.

What the documentation says

The pnpm sbom page describes these options for workspaces:

  • --split generates a separate SBOM per selected workspace package.
  • --out writes to a file instead of stdout, and the path can include %s for the package name and %v for the version. Added in 11.8.0.
  • --filter narrows which packages are selected.
  • When a single package is selected, the root component of the SBOM uses that package's metadata.

If you do not use --out with --split, you get newline delimited JSON, one SBOM per line, on stdout.

Steps

  1. Update to a pnpm version that supports the flags you need, and note it in your CI image.
  2. Commit the lockfile, since the command requires one.
  3. Choose a format with --sbom-format cyclonedx or spdx.
  4. Decide the type for the root. The default is library. Services you deploy should use application.
  5. Add --split and an --out path such as a directory with %s and %v in the filename.
  6. Use --filter if you just want the packages that ship.
  7. Run it, then count files against the packages you expected.

When one file per package is the right call

SituationOne per packageOne for the repo
Packages ship as separate artifactsYesNo
Customers receive one productMaybeYes
Teams own packages separatelyYesHarder
You need a single dependency countHarderYes

Ship the file that matches the thing a recipient installs. A library package consumed by others deserves its own file. An internal helper that never leaves the repo may not.

Pitfalls

Shared dependencies appear in many files. That is correct, not duplication, but it means totals across files overcount unique components. If you need a repo wide view, deduplicate by package URL.

Workspace packages that depend on each other show up as components in the file of the dependent. Decide whether internal packages should be listed as components or treated as part of the product.

Filenames with %v change on every version bump. That is useful for archives, but make sure the publishing step does not rely on a fixed name.

After you have the files

Store them with the release, name them predictably, and validate them. Then scan them. Safeguard can ingest SBOMs and run dependency scanning and reachability analysis on them, and the free scan at gold.safeguard.sh/scan is a quick first test.

A worked example

Consider a monorepo with four workspace packages: a web app, an API service, a shared utilities library and an internal build helper. The numbers here are invented for illustration.

PackageShips toNeeds its own SBOM?
web-appCustomers, as a deployed siteYes, type application
api-serviceCustomers, as a deployed serviceYes, type application
shared-utilsUsed by the two aboveMaybe, as a library
build-helperStays internalUsually no

Running with --split and a filter that selects web-app and api-service gives two files. With an output path that includes the package name placeholder and the version placeholder, you get names like web-app followed by its version, which suits archiving. The utilities library appears as a component inside each file, because both applications depend on it.

If the customer receives both applications as one product, they may prefer a single merged view. In that case, either run without --split for the whole repo or merge the two files after the fact, deduplicating components by their package URL.

Choosing application versus library

The default root type is library. For the two deployable packages, pass --sbom-type application. The library file, if you publish one, can keep the default. Mixing types across files in one release is normal, but write it down.

Filtering with intent

The --filter option selects workspace packages. Use it to match what you ship. A filter by directory or by package name works, and the pnpm filtering docs describe the selector forms. Keep the filter in a script, not in a developer's shell history, so the next release produces the same set.

Verifying the output

  1. Count files against the packages you selected.
  2. Open each file and check that the root component's name and version match the package.
  3. Confirm that internal workspace packages appear as components where you expect them.
  4. Check that production scope was applied where intended.
  5. Validate each file against the schema for its declared version.

Archiving and naming

The version placeholder in the output path is useful because filenames then track releases. Store the files in the same place as your release artifacts. Keep the commit hash and tool version in a small manifest alongside, so a file can always be traced to the build that produced it.

When a single file is better

If your consumers are scanners that ingest one file per product, or procurement teams who want one document, splitting adds work for them. Offer the merged view too, clearly labeled, and tell them how it was built. The goal is that each recipient gets the shape that matches what they installed.

A caution about totals

Do not add up component counts across split files and call it the repo total. Shared dependencies are counted once per file. Deduplicate by package URL if you need a repository wide number.

Try it yourself

Click through it at your own pace, or open it full screen.

Click into a few components and note how direct and transitive packages are separated.

Quick check

What does --split do without --out?

It prints NDJSON to stdout, one SBOM per selected workspace package.

Can I get one file for the whole workspace?

Yes. Run the command without --split. See the pnpm documentation for how the root component is chosen.

Should the internal build helper get an SBOM?

Usually not, if it never ships. Include it in internal reviews if its dependencies matter to your build security.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.