Skip to main content
Version: Next

Adding a Bridge

This guide walks through adding a data model bridge for a new version of a UNTP core data model. The process follows the same delta pattern used by the existing bridges.

When you need a new bridge

You need to add a bridge when a new version of the UNTP specification is released.

When you don't need one

If a new UNTP version has no structural changes for a given credential type — the fields are in the same positions with the same names — you don't need new builder or extractor logic. Instead, create a version directory with a single file that re-exports the previous version. This is a single line of code with no duplication and no new tests needed for the bridge logic itself — only a registry entry.

Where things live

Each credential type has a directory under data-models/, and each version has its own subdirectory containing its builder, extractor, and version spec:

packages/services/src/data-model-bridges/
├── primitives/ # Shared building blocks
└── data-models/
└── dpp/
└── versions/
├── v060/
│ ├── builder.ts # Builds credential subject
│ ├── builder.test.ts
│ ├── extractor.ts # Extracts entity + conformity refs
│ ├── extractor.test.ts
│ └── index.ts # Pairs builder + extractor
├── v061/
│ └── index.ts # Re-exports v060 (identical)
└── v070/ # ← new version goes here
├── builder.ts
├── extractor.ts
└── index.ts

Steps

1. Create the version directory

Under the credential type's versions/ directory, create a new directory for the version (e.g., v070/).

2. Write the builder

The builder is a function that receives entity data (organisation, facility, product, conformity selections) and returns a credential subject in the correct structure for that version. Follow the existing builders as reference:

  • Use the shared primitives from primitives/ for common structures (party objects, identifier schemes, locations, addresses)
  • Only use the entity fields relevant to the credential type — silently ignore the rest
  • For conformity data, route each input to the correct location within the credential subject (see Conformity Handling)

If the builder logic hasn't changed from the previous version, import and reuse it directly — no need to duplicate.

3. Write the extractor

The extractor is a function that receives a credential subject and returns the entity identifiers and conformity references it contains. Follow the existing extractors as reference:

  • Return only the fields that could be successfully extracted
  • Handle missing or undefined fields gracefully — return an empty result if nothing can be extracted, never throw
  • For conformity data, extract from the same locations the builder writes to

If the extractor logic hasn't changed from the previous version, import and reuse it directly.

4. Create the version spec

The version spec pairs the builder and extractor together. Create an index.ts in the version directory that imports both functions and exports a named spec constant.

If the credential type carries a conformity claim (currently Digital Conformity Credential), also write a conformity claim extractor and wire it into the spec as conformityClaimExtractor, or conformityClaimProvenanceExtractor if it also records where each projected value came from. A version spec that omits both fields gets no CVC validation for that version, silently: the bridge returns null from extractConformityClaim and extractConformityClaimWithProvenance rather than raising an error.

Also check where this version's schema puts the subject's own id and display name. Read the schema rather than the builder for this one: issuance captures these fields from the payload a caller submitted, which the builder may have had no part in producing. Most versions need no action here, because a version spec that omits subjectSummaryExtractor falls back to reading the credential subject's top-level id and name, which is where most schemas put them. Set subjectSummaryExtractor only when this version's schema nests those fields inside a sub-object (as DPP 0.6.x and DFR 0.6.x do) or names the display field something other than name (as DIA 0.7.0's registeredName does). It receives the subject as the credential carries it, so a version whose subject can be an array also decides there which element a library row describes; the default is the first.

If reusing functions from a previous version, the delta pattern makes this straightforward — import the previous version's functions and override only what changed.

5. Register in the bridge registry

Add the new version spec to bridge-registry.ts. The registry maps credential type and version strings to bridge instances. Add an entry for the new version alongside the existing ones.

6. Add tests

Builder and extractor tests are parameterised across all supported versions. Add the new version to the existing test parameterisation so it runs against the same test cases.

If the new version introduces structural changes, add test cases that cover the differences. Both builders and extractors are pure functions — no mocking required.

7. Update form configuration (if needed)

If the new version changes which entities a credential type requires (e.g., a credential type now requires a facility where it previously didn't), update the entity requirements in the form-config route handler.

Adding a new UNTP core data model

If the UNTP specification introduces an entirely new core data model type:

  1. Create a new directory under data-models/ (e.g., data-models/new-type/)
  2. Create the first version directory with builder, extractor, and version spec
  3. Add the new type to the registry in bridge-registry.ts
  4. Add entity requirements to the form-config route handler
  5. Write comprehensive builder and extractor tests

Reference

The existing bridges in the services package serve as the best reference for implementation patterns.