npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tracepass/dpp-identifiers

v0.14.2

Published

Validate and parse EU Digital Product Passport identifiers (EN 18219) — GS1 Digital Link, ISO/IEC 15459, IEC 61406, DID, DOI. Pure functions, no network, no third-party runtime dependencies.

Readme

@tracepass/dpp-identifiers

Validate and parse EU Digital Product Passport identifiers per EN 18219 — GS1, ISO/IEC 15459, IEC 61406, DID, DOI. Pure functions, no network, no third-party runtime dependencies.

License Dependencies

Part of tracepass-open · Maintained by TracePass


npm install @tracepass/dpp-identifiers

Validate any identifier scheme EN 18219 permits for a Digital Product Passport, and build/parse the resolver paths they map to.

Validate a product identifier

import { validateProductIdentifier } from "@tracepass/dpp-identifiers";
import type { ProductIdentifier } from "@tracepass/dpp-types";

// GS1 Digital Link — normalises GTIN-8/12/13 to 14 digits
const gs1: ProductIdentifier = {
  scheme: "gs1",
  gtin: "5449000000996",   // GTIN-13 — normalised to "05449000000996"
  serialNumber: "SN-001",
};
const r = validateProductIdentifier(gs1);
// r.ok === true; r.value.gtin === "05449000000996"

// ISO/IEC 15459 — required for battery passports (Art. 77(3))
const iso15459: ProductIdentifier = {
  scheme: "iso15459",
  issuingAgencyCode: "MH",   // starts with a letter (GS1 holds the digit range)
  primaryId: "BATT-2026-001",
  raw: "MHBATT-2026-001",
};
validateProductIdentifier(iso15459).ok;  // true

// Battery passports: only gs1 and iso15459 are allowed
validateProductIdentifier(
  { scheme: "did", did: "did:web:example.com", method: "web" },
  { category: "battery" },
).ok;  // false — Battery Regulation Art. 77(3) requires ISO/IEC 15459

Supported schemes (EN 18219)

| scheme | EN 18219 | Notes | |---|---|---| | gs1 | scheme 1 | GTIN-8/12/13/14 accepted; always normalised to 14 digits | | iso15459 | scheme 1 | Non-GS1 issuing agency. Agency code first char must be a letter | | iec61406 | scheme 2 | https URL, ASCII-only, RFC 3986-valid | | did | scheme 3 | W3C DID Core syntax only; no method allow-list | | doi | scheme 5 | 10.<registrant>/<suffix>; accepts doi: / https://doi.org/ prefixes. granularity (model | batch | item) is required: clause 5.6.2(b) |

Battery passports accept only gs1 and iso15459: Battery Regulation Art. 77(3) requires ISO/IEC 15459 until a delegated act replaces it with EN 18219.

LEI is not a product identifier. EN 18219 clause 5.1 names schemes 1–5 for products; LEI does not appear. Pass LEI as an operator identifier instead: { scheme: "iso6523", icd: "0199", value: "<20-char LEI>" }.

Validate operator and facility identifiers

import { validateOperatorIdentifier, validateFacilityIdentifier } from "@tracepass/dpp-identifiers";

// ISO 7064 MOD 97-10 check for LEI
validateOperatorIdentifier({ scheme: "iso6523", icd: "0199", value: "5493001KJTIIGC8Y1R12" }).ok;  // true

// GLN (icd 0088) validated with GS1 mod-10
validateOperatorIdentifier({ scheme: "iso6523", icd: "0088", value: "5412345000013" }).ok;  // true

// Facility — the same four schemes as operators (EN 18219 clauses 6.2–6.5)
validateFacilityIdentifier({ scheme: "gln", gln: "5412345000013", extension: "DOCK-1" }).ok;  // true
validateFacilityIdentifier({ scheme: "did", did: "did:web:plant.example.com" }).ok;  // true

Build and parse resolver paths

import { identifierToUri, identifierOwnUrl, parseResolverPath, diQueryCandidateKeys, identifierKey } from "@tracepass/dpp-identifiers";

const gs1 = { scheme: "gs1", gtin: "05449000000996", serialNumber: "SN-001" } as const;

// Build a resolver URL
identifierToUri(gs1, "id.example.com");
// "https://id.example.com/01/05449000000996/21/SN-001"

// Parse back
parseResolverPath("/01/05449000000996/21/SN-001");
// { scheme: "gs1", gtin: "05449000000996", serialNumber: "SN-001" }

// ISO/IEC 15459 → the ISO/IEC 18975 query form (EN 18219 Table B.12)
identifierToUri(
  { scheme: "iso15459", issuingAgencyCode: "QC", primaryId: "ELMI12345", serial: "654321", raw: "QC/ELMI12345/654321" },
  "id.example.com",
);
// "https://id.example.com/?.25P=QCELMI12345&.S=654321"

// A resolver splits .25P back into candidate keys (the agency code is 1–3 characters)
diQueryCandidateKeys("QCELMI12345", "654321");
// ["iso15459:Q:CELMI12345:654321", "iso15459:QC:ELMI12345:654321", "iso15459:QCE:LMI12345:654321"]

// A DOI carries its own URL; no resolver needed
identifierOwnUrl({ scheme: "doi", doi: "10.1234/my-passport", granularity: "model" });
// "https://doi.org/10.1234/my-passport"

// Canonical key for deduplication
identifierKey(gs1);  // "gs1:05449000000996:SN-001"

identifierToUri returns null for iec61406, did and doi — those carry their own canonical URL (use identifierOwnUrl instead).

parseResolverPath and consumer pre-emption

parseResolverPath also reads the older /<AGENCY>/<primaryId>[/<serial>] path, so a URL already printed in that form keeps resolving. identifierToUri no longer emits it. It enforces the ISO/IEC 15459 agency-code shape: the first path segment must be 1–3 uppercase chars matching [A-Z][A-Z0-9]{0,2}. This rejects common app routes:

| Path | Result | |---|---| | /about/team | null — "about" is 5 chars | | /mh/ABC | null — "mh" is lowercase; identifierToUri always emits uppercase | | /MH/BAT-001 | {scheme: "iso15459", …} ✓ | | /01/0544…/21/SN | {scheme: "gs1", …} ✓ |

Short uppercase routes are still ambiguous. /P/x parses as {issuingAgencyCode: "P", primaryId: "x"} because "P" is a valid 1-char agency code. Consumers MUST match their own short app routes (/P, /Q, /DOCS, …) before delegating to parseResolverPath.

Read the canonical identifier

When code that was written before EN 18219 only sets passport.gs1, and new code sets passport.identifier, use resolveProductIdentifier to read whichever is present:

import { resolveProductIdentifier } from "@tracepass/dpp-identifiers";

const id = resolveProductIdentifier(passport);
// → passport.identifier, or lifted Gs1Identifier from passport.gs1, or undefined
if (!id) throw new Error("No product identifier");

identifier takes precedence when both fields are set.

Character constraint (EN 18219 clause 4.3.2)

All identifier fields must use ISO/IEC 646 characters — printable ASCII (0x20–0x7E). Non-ASCII input is rejected with an error naming the offending field.

Zero dependencies

This package is pure TypeScript over plain objects. It imports only two sibling packages (@tracepass/dpp-types for types and @tracepass/gs1-utils for GTIN/GLN arithmetic), neither of which adds a third-party runtime dependency.

Out of scope

  • EN 18219 scheme 4 (RAIN RFID and 2D codes) encodes the same ISO/IEC 15459 identifiers on a limited-capacity carrier. It is a data-carrier concern, not a separate identifier type.
  • DID method allow-list. Clause 5.4.2 requires DID Core only. did:web, did:ethr and did:ebsi are a recommendation (5.4.3). Any DID Core syntax is accepted.
  • A DID as an operator identifier also needs a verifiable credential from an authorised company register (clause 6.4.2(b)). The validator checks the syntax only.

License

Apache-2.0.