Package URL
A package URL (purl) is a standard string that identifies a software package by its ecosystem, namespace, name and version, such as pkg:npm/[email protected].
Last updated: November 11, 2026
A package URL (purl) is a standard identifier for a software package that names it the way its own ecosystem does: pkg:type/namespace/name@version?qualifiers#subpath. It became an Ecma standard, ECMA-427, in December 2025 (1st edition, Ecma TC54), and the specification is maintained at package-url/purl-spec.
How a purl is built
Every purl starts with the scheme pkg. The type is the ecosystem or package manager (npm, pypi, cargo, deb, maven and so on). The namespace is an optional grouping such as an npm scope, a Maven group or a Linux distribution. Then come the name and the version. Qualifiers add facts the ecosystem needs to pin the exact package, such as the architecture or the distribution release, and the subpath points inside a package.
pkg:npm/%40angular/[email protected]
pkg:pypi/[email protected]
pkg:cargo/[email protected]
pkg:deb/ubuntu/[email protected]+dfsg-1ubuntu2.1?arch=arm64&distro=noble
The last one says more than "cpio 2.15": it is Ubuntu's own build for the Noble release on arm64. That matters because distributions backport security fixes into their own versions, so the same upstream version can be vulnerable in one distribution and fixed in another.
Why vulnerability matching depends on it
Advisory databases organized by package know a component by its ecosystem and name. The OSV schema requires ecosystem and name for every affected package and calls its purl field "optional but recommended"; GitHub's advisories are also listed per ecosystem package. When an SBOM component carries a purl, a tool can match it against those advisories exactly. Without one it has to guess from a name or a CPE, or it matches nothing, and the vulnerability goes unreported.
In a Yocto build we compared, the build system's own SBOM listed hundreds of Rust crates only as .crate files with a download location and no pkg:cargo purl, so those crates could not be matched against Rust advisories (see Yocto builds). Newer Yocto releases add ecosystem purls for Cargo and Go sources.
Package URL vs CPE
| Package URL | CPE | |
|---|---|---|
| Who defines the name | The package's ecosystem | NVD's CPE dictionary |
| Identifies | A package in a registry or distribution | A product, including hardware and operating systems |
| Format | pkg:type/namespace/name@version | cpe:2.3:part:vendor:product:version:... |
| Standard | ECMA-427 | NISTIR 7695 (CPE 2.3) |
| Used by | OSV, GitHub advisories, SBOM tools | NVD, many scanners |
| Typical failure | Missing for vendored or hand-listed code | Ambiguous vendor and product names; no CPE data for many new CVEs |
The rule of thumb: give every package that has an ecosystem a purl, and use a CPE for what has none, such as commercial software, firmware and hardware. Mismatched identifiers are one of the main causes of wrong findings, covered in how to reduce vulnerability noise.
Package URLs in SBOMs
CycloneDX and SPDX both carry a purl per component. CISA's 2026 Minimum Elements say component identifiers should use common identifiers "such as Common Platform Enumeration (CPE) and Package-URL (PURL)", and Germany's BSI TR-03183-2 requires such identifiers for each component where they exist. A missing purl is a field quality problem rather than a gap in SBOM completeness: the component is listed, but no advisory database can look it up.
CRACI records a package URL for every package a build pulls in, including each Rust crate in a Yocto build, so its SBOMs can be matched against advisories. See build-time SBOMs.