In SPDX 2.3, identifiers like a purl or CPE go in an ExternalRef line with three parts: a category, a type and a locator. A purl goes under PACKAGE-MANAGER with type purl, and a CPE goes under SECURITY with a cpe type. The field is optional, and a package can have many.
The three parts
The 2.3.1 spec defines the format as category, then type, then locator separated by spaces. Category is one of SECURITY, PACKAGE-MANAGER, PERSISTENT-ID or OTHER. The type comes from a list in an annex of the spec. The locator is a string with no spaces that identifies the package in the target system. Cardinality is zero or more, so you can attach several.
The spec gives these examples.
ExternalRef: SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*
ExternalRef: PERSISTENT-ID swh swh:1:cnt:94a9ed024d3859793618152ea559a168bbcbb5e2
ExternalRef: OTHER LocationRef-acmeforge acmecorp/acmenator/4.1.3-alpha
For a purl, the common form uses the package-manager category and the purl type.
ExternalRef: PACKAGE-MANAGER purl pkg:npm/lodash@4.17.21
Which category for which identifier
| Identifier | Category | What it is for |
|---|---|---|
| purl | PACKAGE-MANAGER | Naming a package in an ecosystem registry |
| CPE | SECURITY | Looking up known vulnerabilities by product name |
| Software Heritage ID | PERSISTENT-ID | A stable identifier for archived content |
| Anything else | OTHER | Custom locators with a namespaced type |
NTIA's minimum elements call these other unique identifiers and say they exist to map data across ecosystems. The report lists CPE, SWID and purl as common examples and says to use them if they exist.
Why more than one identifier helps
No single naming scheme covers every ecosystem, and NTIA says so. A purl matches package registry data well. A CPE matches the way some vulnerability databases name products. Carrying both increases the chance that a downstream tool finds the right records. It also means a mismatch between them is a useful signal that something was resolved wrongly.
Mistakes to avoid
Spaces in the locator. The spec defines the locator as a string with no spaces, so do not paste a display name.
A guessed CPE. CPEs are easy to fabricate and hard to verify. Only add one you can trace to a source, or leave it out.
Using OTHER for a standard type. If a listed type exists, use it so consumers can recognize it.
Forgetting the comment. There is a separate ExternalRefComment field for human context, such as how the CPE was derived.
Quick test
After generating an SPDX file, count packages with a purl reference. In a typical package manager project, nearly all should have one. The ones without are the places your inventory is weakest.
Safeguard can generate SBOMs and run dependency scanning across your repositories, so the fields discussed here are checked on real projects instead of by hand. You can try the free scan at gold.safeguard.sh/scan.
Try it yourself
Open the demo, click through the component list, and look at how each package is identified and what is missing.
Click through it at your own pace, or open it full screen.
Quick check
Can I add several ExternalRef lines to one package?
Yes. The cardinality is zero or more, so a package can carry a purl, a CPE and other references.
Is ExternalRef required?
No. It is optional in SPDX 2.3, though NTIA recommends using other unique identifiers where they exist.
Sources
- SPDX 2.3.1, Clause 7: Package information
- NTIA, The Minimum Elements for a Software Bill of Materials (2021)
Last reviewed: 19 October 2026.