Supply chain guide

npm SBOM: how to generate a software bill of materials with CycloneDX or SPDX

An SBOM is a machine-readable list of everything in your software’s dependency tree. Here is how to generate one for your npm project, which format to pick, how to wire it into CI, and what it is actually useful for.

What is an SBOM

A software bill of materials (SBOM) is a formal, machine-readable inventory of every component in your software: direct dependencies, transitive dependencies, their exact versions, their licenses, and their unique identifiers (such as Package URLs, or PURLs). For an npm project, an SBOM is essentially a structured export of what your package-lock.json already knows, serialized into a standardized format that other tools, auditors, and security platforms can query.

Think of it as the ingredient list on a food product, applied to software. When a critical vulnerability is disclosed in a widely-used library (the npm ecosystem has had several), organizations with SBOMs can query their inventory and know within minutes whether they are affected. Organizations without one spend days of manual grep-and-search work to answer the same question.

Why generate one now

SBOM adoption has accelerated due to three converging pressures:

US Executive Order 14028 (May 2021)

Requires federal agencies to obtain SBOMs for software they acquire. Software vendors selling to the US government — or seeking to — must produce SBOMs for their products. NIST’s guidance on SBOM minimum elements (published 2021) specifies CycloneDX and SPDX as the accepted formats.

EU Cyber Resilience Act (CRA)

Passed by the European Parliament in 2024, with enforcement requirements phased in from 2027. Requires manufacturers of “products with digital elements” sold in the EU market to maintain SBOMs for their products throughout the support lifecycle. Applies primarily to commercial products, not to open-source software distributed without a commercial relationship.

Enterprise procurement requirements

Even without a legal mandate, SBOMs are increasingly part of enterprise security questionnaires. Larger customers — especially in finance, healthcare, and public sector — ask vendors to provide an SBOM as part of third-party risk management. Generating one ahead of the ask is low-effort and signals supply chain maturity.

For most open-source or indie projects, an SBOM is not legally required today. If you distribute software commercially to regulated industries or sell to US government agencies or into the EU market, check with a lawyer about your specific obligations. The tooling below is accurate regardless of whether you are required to use it.

SPDX vs CycloneDX

Two open formats dominate the SBOM landscape. Both are accepted by NIST’s SBOM minimum elements guidance and by the US government’s SBOM requirements under EO 14028.

Dimension SPDX CycloneDX
Governing body Linux Foundation OWASP
Primary focus License compliance and provenance Supply chain security and vulnerability analysis
File formats .spdx, .spdx.json, .spdx.tv, .spdx.rdf .json, .xml, .proto
npm CLI tool spdx-sbom-generator, npm sbom --sbom-format spdx @cyclonedx/cyclonedx-npm, npm sbom --sbom-format cyclonedx
Vulnerability metadata Limited Rich (CVSS, VEX)
License data detail Deep (SPDX license expressions) Good (SPDX expressions supported)
Security platform support Good Broad (Dependency-Track, Grype, etc.)
Best for Legal / license compliance workflows Security tooling, CI pipelines, vulnerability analysis

Which to pick: for a new npm project with a security focus, choose CycloneDX. It is the format most security tools consume natively (Dependency-Track, Grype, Trivy, and others all support CycloneDX JSON as input). If your primary concern is license compliance for a legal review, SPDX has slightly richer provenance data. When in doubt, generate both — both CLI tools are fast and free.

Generate an SBOM with CycloneDX

The official CycloneDX npm tool produces a fully-conformant CycloneDX SBOM from your package.json and package-lock.json:

# Install as a dev dependency (recommended, keeps the version pinned)
npm install -D @cyclonedx/cyclonedx-npm

# Or install globally
npm install -g @cyclonedx/cyclonedx-npm
# Generate a CycloneDX JSON SBOM (most compatible format)
npx @cyclonedx/cyclonedx-npm --output-format json --output-file sbom.cyclonedx.json

# Generate CycloneDX XML
npx @cyclonedx/cyclonedx-npm --output-format xml --output-file sbom.cyclonedx.xml

# Include devDependencies (omit flag to generate production-only SBOM)
npx @cyclonedx/cyclonedx-npm --include-dev --output-format json --output-file sbom.cyclonedx.json

# Omit flattening — preserve the full nested dependency graph
npx @cyclonedx/cyclonedx-npm --output-format json --output-file sbom.cyclonedx.json

What the output contains

The generated JSON file lists every resolved component in your dependency tree with:

  • Component name, version, and PURL — e.g. pkg:npm/express@4.18.2
  • License expression — e.g. MIT, Apache-2.0, (MIT OR ISC)
  • Component hashes — SHA-1 and SHA-256 of the package tarball for integrity verification
  • Dependency graph — which component depends on which, as a structured graph (not just a flat list)
  • Metadata — the generating tool, timestamp, and the root component being described
Commit the SBOM file at release time, not in the main branch. Transitive dependencies change with every npm install (lock file updates, new transitive versions). An SBOM committed to main will be stale within days. Generate a fresh SBOM as part of your release workflow (see CI below) and attach it to the release artifact.

Generate an SBOM with npm’s built-in command (npm 9+)

npm 9.1.0 added a built-in npm sbom command. If you have npm 9+ installed, you do not need an extra package:

# Check your npm version
npm --version  # needs 9.1.0 or later

# Generate a CycloneDX SBOM (prints to stdout)
npm sbom --sbom-format cyclonedx --sbom-type package > sbom.cyclonedx.json

# Generate an SPDX SBOM
npm sbom --sbom-format spdx --sbom-type package > sbom.spdx.json

# Include devDependencies
npm sbom --sbom-format cyclonedx --include-workspace-root > sbom.cyclonedx.json
npm sbom vs @cyclonedx/cyclonedx-npm: npm’s built-in command produces a valid SBOM but omits per-component SHA-256 hashes by default (the --omit and hash options are limited compared to the dedicated tool). For compliance submissions or security platform ingestion, @cyclonedx/cyclonedx-npm produces a more complete output. For quick checks and internal tooling, npm sbom is faster and requires no extra install.

Generate an SBOM with SPDX tools

If you need SPDX format (common for license compliance workflows, open-source fund audits, or FOSSA integration), spdx-sbom-generator supports npm alongside Go, Rust, Python, and others from the same CLI:

# Install globally
npm install -g spdx-sbom-generator

# Generate SPDX JSON (run from your project root)
spdx-sbom-generator -p . -f json
# Outputs: bom-npm.json

# Generate SPDX tag-value format
spdx-sbom-generator -p . -f tv
# Outputs: bom-npm.spdx

Alternatively, npm’s built-in command supports SPDX format directly (see above). For teams already using the npm CLI and not needing cross-ecosystem SBOMs, npm sbom --sbom-format spdx is simpler.

CI integration: auto-generate on release (GitHub Actions)

The most common pattern is to generate the SBOM as part of your release workflow and attach it as a release artifact. This ensures the SBOM matches the exact code that shipped:

# .github/workflows/sbom.yml
name: Generate SBOM
on:
  push:
    tags: ['v*']
  workflow_dispatch:

jobs:
  sbom:
    runs-on: ubuntu-latest
    permissions:
      contents: write  # required to upload release assets
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Generate CycloneDX SBOM
        run: npx @cyclonedx/cyclonedx-npm --output-format json --output-file sbom.cyclonedx.json

      - name: Upload SBOM as artifact
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.cyclonedx.json

      # Optional: attach to GitHub release if triggered by a tag push
      - name: Upload SBOM to release
        if: startsWith(github.ref, 'refs/tags/')
        uses: softprops/action-gh-release@v2
        with:
          files: sbom.cyclonedx.json

Add SBOM generation to an existing build workflow

If you already have a build-and-test workflow, add the SBOM step after npm ci and before your test or lint steps:

- name: Install dependencies
  run: npm ci

- name: Generate SBOM
  run: npx @cyclonedx/cyclonedx-npm --output-format json --output-file sbom.cyclonedx.json

# Your existing test/build steps follow
- name: Run tests
  run: npm test

Ingest the SBOM into Dependency-Track

Dependency-Track is an open-source component analysis platform (OWASP project) that accepts CycloneDX SBOMs and continuously monitors your components against the NVD, OSV, and other vulnerability feeds. Upload your SBOM via its API:

- name: Upload SBOM to Dependency-Track
  if: startsWith(github.ref, 'refs/tags/')
  run: |
    curl -X PUT \
      -H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \
      -H "Content-Type: multipart/form-data" \
      -F "project=${{ secrets.DTRACK_PROJECT_UUID }}" \
      -F "bom=@sbom.cyclonedx.json" \
      ${{ secrets.DTRACK_URL }}/api/v1/bom

Dependency-Track can be self-hosted at no cost (Docker image available) and provides a dashboard of all components, their vulnerability status, and policy violations across all your projects.

Using DepCheck alongside your SBOM workflow

An SBOM documents the dependency tree you already have. It does not tell you whether a package in that tree has an active CVE, has been abandoned by its maintainer, or shows typosquatting signals. It also does not help you evaluate a package you are considering adding.

DepCheck fills the upstream gap: paste your package.json (or a specific package name) before running npm install to catch risky packages before they enter your dependency tree — and therefore before they appear in your SBOM. Think of it as a pre-install gate: DepCheck flags what CVE databases don’t yet know (typosquatting signals, abandonment, unusual permission requests), while your SBOM tooling documents what you ship.

Check your package.json with DepCheck →

Paste your package.json and see CVE findings alongside typosquatting signals, abandoned package warnings, and license risk — no install, no account, all in the browser.

Frequently asked questions

What is an SBOM?

A software bill of materials (SBOM) is a machine-readable inventory of every component in your software — direct dependencies, transitive dependencies, their versions, licenses, and package identifiers. For an npm project, an SBOM formally documents everything in your package-lock.json dependency tree in a standardized format (SPDX or CycloneDX) that other tools, auditors, and security platforms can query.

Is generating an SBOM required for npm packages?

Not currently for most npm packages. SBOM requirements are context-dependent. US Executive Order 14028 requires SBOMs for software sold to US federal agencies. The EU Cyber Resilience Act (CRA), with enforcement phased in from 2027, requires SBOMs for commercial products with digital elements sold in the EU. Open-source libraries distributed without a commercial relationship generally fall outside these mandates. Enterprise customers increasingly ask for SBOMs as part of procurement due diligence regardless of legal requirements.

What is the difference between SPDX and CycloneDX?

SPDX (maintained by the Linux Foundation) was designed primarily for license compliance and provenance documentation. CycloneDX (maintained by OWASP) was designed for supply chain security: its schema includes vulnerability references, CVSS scores, and VEX (Vulnerability Exploitability eXchange) metadata alongside the component inventory. CycloneDX is generally the better choice for security tooling integration; SPDX for legal/license workflows. Both are accepted by NIST’s SBOM minimum elements guidance.

Does an SBOM replace npm audit?

No. An SBOM documents what is in your dependency tree; npm audit checks whether any of those components have known CVEs. They are complementary. Some security platforms (like Dependency-Track) accept a CycloneDX SBOM as input and run continuous vulnerability monitoring against it — in that case the SBOM drives the audit. But generating an SBOM without scanning it against a vulnerability feed is only an inventory, not a security check.

How do I generate an SBOM without installing extra tools?

npm 9.1.0 and later include a built-in npm sbom command. Run:

npm sbom --sbom-format cyclonedx --sbom-type package > sbom.cyclonedx.json

Use --sbom-format spdx for SPDX output. This requires no additional dependencies. The output is valid but less detailed than @cyclonedx/cyclonedx-npm (no per-component SHA-256 hashes by default).

What is a PURL in a CycloneDX SBOM?

A Package URL (PURL) is a standardized string identifier for a software component. For npm packages it looks like pkg:npm/express@4.18.2 — encoding the package type, name, and version. Vulnerability databases, license scanners, and SCA tools use PURLs to match components without ambiguity across databases. PURLs are how a CycloneDX SBOM connects to external vulnerability feeds (GitHub Advisory DB, OSV, NVD) at query time.

Does DepCheck read my SBOM file?

DepCheck analyzes package.json by paste, not by reading an SBOM file. The SBOM is a downstream document describing your finalized dependency tree. DepCheck is most useful upstream — before npm install or before adding a new dependency — to flag typosquatting signals, abandoned packages, and risky license terms in packages you are considering. Run DepCheck at dependency selection time; generate your SBOM at build or release time.

Also in the Copper Bay Labs ship-safety suite

SBOM generation is one layer of your pre-ship checklist. These free tools cover the rest: