Safeguard
Developer Tools•

How does pnpm sbom work? Formats, flags, and what it needs

pnpm sbom writes CycloneDX or SPDX JSON from your lockfile. The flags that matter, what the root component looks like, and where output needs review.

Safeguard Security Team
6 min read

The pnpm sbom command generates a software bill of materials for a project from its lockfile. You must pick a format with --sbom-format: cyclonedx or spdx. Flags then control the root component type, dev dependency scope, and output path. It needs a lockfile, and a package manifest is optional.

What the command needs and produces

According to the pnpm documentation, a lockfile is mandatory. A package manifest is optional, but without one the root component is named unknown, gets version 0.0.0 and has no license. The command never creates a manifest for you.

Output is JSON. The documented formats are CycloneDX (current default spec version 1.7) and SPDX 2.3. You can ask for CycloneDX 1.5 or 1.6 with --sbom-spec-version if a downstream tool cannot read 1.7. That flag is just valid with CycloneDX.

The flags worth knowing

FlagWhat it does
--sbom-formatRequired. cyclonedx or spdx
--sbom-typelibrary (default) or application for the root component
--prod, -PJust dependencies and optionalDependencies
--dev, -DJust devDependencies
--no-optionalLeave out optionalDependencies
--lockfile-justUse lockfile data and skip reading the store
--exclude-peersDrop peer dependencies and anything reachable just through them
--outWrite to a file instead of stdout

Two flags carry metadata for your reader: --sbom-authors and --sbom-supplier, written to the CycloneDX metadata block.

Steps to produce a first file

  1. Confirm the lockfile is committed and current.
  2. Decide whether you ship an application or a library, and set --sbom-type to match.
  3. Run the command with a format and an output path.
  4. Open the file and check the root component name, version and type.
  5. Compare the component count with what you expect from your lockfile.

For a deployed service, a production-just file is usually what a customer wants. For internal license review you may keep dev dependencies, because the CycloneDX output marks components reachable just through devDependencies with scope excluded and a development property. That means a reader can still filter them out.

Where to double check

The default type is library, so an application should set --sbom-type application. Otherwise the root component says something different from what you ship. Also remember that --lockfile-just skips the store, which is fine for a quick inventory but means the file reflects what the lockfile says, not what happens to be installed on a machine.

A lockfile based file is an inventory of declared packages. It does not tell you which packages your code actually calls. Reachability analysis is the step that does that, and it is separate from generation.

A worked example

Picture a small service called orders-api. It has a package.json with 12 direct dependencies and 9 dev dependencies, plus a committed lockfile. These numbers are an illustration, not a measurement of any real project.

You run the command twice. The first run uses --sbom-format cyclonedx with --prod and --sbom-type application. The second run drops --prod. Here is what a careful reviewer would expect to see.

RunIncludesWhat the reader learns
Production scopedependencies and optionalDependencies, plus everything they pull inWhat ships to users
Full scopeThe above plus devDependenciesWhat the build and test tooling touch

Open both files and compare three things. The root component should say orders-api with the version from package.json and type application. The component count in the second file should be larger. And components reachable solely through devDependencies should carry scope excluded and the development property, as the pnpm docs describe for CycloneDX output.

If the counts are identical, either you have no dev dependencies or the scope flag did not apply. Investigate before you send anything to a customer.

Choosing between CycloneDX and SPDX in this command

The command accepts either, so the real question is who reads the file. Ask your recipient. If they say either is fine, many teams pick CycloneDX for application SBOMs because it has a direct way to mark component scope and to describe dependency relationships, and SPDX when a customer's process is built around licenses. Whichever you choose, write the format and version in your cover note, for example CycloneDX 1.6 or SPDX 2.3.

If a downstream tool cannot read the newest CycloneDX version, set --sbom-spec-version to 1.6 or 1.5. Keep that flag in your script so the choice is visible to the next person.

Common failure modes and how to spot them

  1. No lockfile. The command needs one. A missing lockfile means the command cannot run, so fix the repo, not the flags.
  2. Wrong root type. The default is library. A deployed service described as a library confuses readers about what you ship.
  3. Missing manifest. With no package.json, the root is unknown with version 0.0.0 and no license. A customer who sees that will ask what the file describes.
  4. Stale lockfile. If package.json changed and the lockfile did not, the SBOM describes the old tree. Run your install with a frozen lockfile in CI to catch this.
  5. Store versus lockfile differences. By default the command can read the store, while --lockfile-solely does not. Pick one on purpose.

What to record next to the file

An SBOM without context ages badly. Keep a short note beside each file: the date, the commit hash, the pnpm version, the flags you used, and the person or job that ran it. When someone asks in a year why the count changed, that note answers the question in a minute.

From file to decision

Generating the file is step one. Step two is turning it into decisions: which components have known advisories, which of those your code actually reaches, and which fixes are cheap. A scan of the file gives you the first answer. Reachability analysis helps with the second. Keep the SBOM as the input to that process, not the end of it.

Try it yourself

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

Click into a component and look at how direct and transitive packages are separated.

Where Safeguard fits

Once you have a file, Safeguard can scan the dependencies and SBOM for known issues, and the free scan at gold.safeguard.sh/scan is a quick way to see results for one project.

Quick check

Does pnpm sbom work without a package.json?

Yes, if a lockfile exists. The root component is then named unknown with version 0.0.0 and no license, so add a manifest if the root should say something useful.

Can I get CycloneDX 1.6 instead of 1.7?

Yes. Use --sbom-spec-version 1.6 with the CycloneDX format. The flag was added in pnpm 11.1.0 per the docs.

Does pnpm sbom scan for vulnerabilities?

No. The command produces an inventory. Vulnerability matching is a separate step you run on the file.

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.