Skip to main content
Version: Next

Custom Seed

The Reference Implementation ships with a set of default seed data — registrars, identifier schemes, data models, render templates, and service instances. These defaults cover common UNTP use cases out of the box.

Deployers who need additional data — custom registrars, identifier schemes, data models, render templates, or conformity schemes — can supply a YAML manifest via a Docker volume mount, without modifying the source code or rebuilding the image.

How It Works

After the core seed completes (see Startup), the seed script looks for a custom seed manifest at a well-known path inside the container:

/app/seed/custom/seed.yaml

If the file exists, it is parsed, validated, and its contents are upserted into the database. If the file does not exist, the custom seed step is silently skipped.

To supply your own seed data, mount a directory containing your seed.yaml (and any referenced files) into the container:

docker run -v ./my-seed:/app/seed/custom ...

Or in Docker Compose:

services:
reference-implementation:
volumes:
- ./my-seed:/app/seed/custom

The mounted directory should contain:

  • seed.yaml — the manifest (required)
  • Any template files referenced by render templates, e.g. templates/dpp.hbs (if applicable)

Environment Variables

VariableDescriptionDefault
SKIP_CUSTOM_SEEDSet to true to skip custom seed processing entirelyfalse

Setting SKIP_CUSTOM_SEED=true prevents the custom seed from running even if a manifest file is present.

ID Format

Every entity in the manifest requires an id field in CUID v1 format. CUIDs (Collision-resistant Unique Identifiers) are used throughout the Reference Implementation as primary keys.

A CUID v1 looks like: clxyz1234567890abcdef — a 25-character lowercase string starting with c.

To generate CUIDs for your manifest, use any CUID v1 library or online generator:

node -e "const s='cdefghijklmnopqrstuvwxyz',t=Date.now().toString(36),r=()=>Math.random().toString(36).slice(2);console.log('c'+t+r()+r())"

Each ID must be unique across the entire manifest. If an ID matches a record already in the database owned by the system tenant, it will be updated (upserted). If it matches a record owned by a different tenant, validation will fail.

Manifest Structure

The manifest is a YAML file with four top-level arrays. All are optional — omit any section you do not need.

registrars: []         # Identifier registrars with nested schemes and qualifiers
dataModels: [] # Data model extension configurations
renderTemplates: [] # HTML render templates (files referenced by relative path)
conformitySchemes: [] # Conformity schemes to ingest under the system tenant

Registrars

A registrar represents an identifier-issuing authority (e.g. GS1, a national business register). Each registrar can contain nested identifier schemes, and each scheme can contain nested qualifiers.

registrars:
- id: "clxyz1234567890abcdef"
name: "My Registrar"
namespace: "my-registrar.example.com"
url: "https://my-registrar.example.com" # optional
idrServiceInstanceId: "clxyz0000000000000001" # optional — links to an IDR service instance
identifierSchemes:
- id: "clxyz1234567890scheme1"
name: "Product ID"
primaryKey: "01"
validationPattern: "^\\d{14}$"
linkTemplate: "https://id.example.com/01/{value}"
qualifiers: # optional
- id: "clxyz1234567890qual01"
key: "10"
description: "Batch or lot number"
validationPattern: "^[\\x21-\\x22\\x25-\\x2F\\x30-\\x39\\x41-\\x5A\\x61-\\x7A]{1,20}$"
order: 1 # optional, defaults to 0
FieldTypeRequiredDescription
idCUID v1YesUnique identifier
namestringYesDisplay name
namespacestringYesNamespace identifier
urlURLNoRegistrar website
idrServiceInstanceIdCUID v1NoReference to an IDR service instance
identifierSchemesarrayNoNested identifier schemes

Identifier Scheme fields:

FieldTypeRequiredDescription
idCUID v1YesUnique identifier
namestringYesDisplay name
primaryKeystringYesPrimary key code (e.g. "01" for GTIN)
validationPatternstringYesRegular expression for validating identifiers
linkTemplatestringYesURL template for resolving identifiers
qualifiersarrayNoNested qualifiers

Qualifier fields:

FieldTypeRequiredDescription
idCUID v1YesUnique identifier
keystringYesQualifier key code
descriptionstringYesHuman-readable description
validationPatternstringYesRegular expression for validating qualifier values
orderintegerNoSort order (defaults to 0)

Data Models

Data models define credential types. Custom data models must reference a core (non-extension) data model as their parent — this links your extension to one of the built-in UNTP credential types (DPP, DCC, DFR, DIA, DTE).

dataModels:
- id: "clxyz1234567890model1"
name: "Australian DPP v1.0"
credentialType: "DigitalProductPassport"
version: "1.0.0"
parentConfigId: "c1pxfzzkeb86jgeel7hrvmcle" # must reference a core data model
schemaUrl: "https://example.com/schemas/au-dpp.json"
contextUrl: "https://example.com/contexts/au-dpp.jsonld"
websiteUrl: "https://example.com/docs/au-dpp" # optional
FieldTypeRequiredDescription
idCUID v1YesUnique identifier
namestringYesDisplay name
credentialTypestringYesCredential type name
versionstringYesVersion string
parentConfigIdCUID v1YesID of an existing core data model in the database
schemaUrlURLYesJSON Schema URL for the credential
contextUrlURLYesJSON-LD context URL
websiteUrlURLNoDocumentation URL

The parentConfigId must reference a core data model created by the default seed, not another extension. The core data models and their IDs are:

Credential TypeVersionID
DigitalProductPassport0.6.0cxuj555flzqtp4ldvklv6ya39
DigitalProductPassport0.6.1c1pxfzzkeb86jgeel7hrvmcle
DigitalProductPassport0.7.0ca3frzta22f7lblxntvw6ukuh
DigitalConformityCredential0.6.0c3imzyum0txv1y9xkww88aktp
DigitalConformityCredential0.6.1cttpz40pfgcfeue2wmbc3jti8
DigitalConformityCredential0.7.0ca9ndkrc8lxmtsfzwynui40zy
DigitalFacilityRecord0.6.0ctfgtrsuiwv1fedo9t5swxhnk
DigitalFacilityRecord0.6.1csrtste8ai2llop7ui8u6n11l
DigitalFacilityRecord0.7.0cj3s37lt6pvh56ggspr9upt5m
DigitalIdentityAnchor0.6.0cz9raijqcay5nzmq59geoggrk
DigitalIdentityAnchor0.6.1cn5u63huxvqgdwppebaxmqt9l
DigitalIdentityAnchor0.7.0cw0tzf723j1oql3u4s1r0c2g2
DigitalTraceabilityEvent0.6.0crqvpwffc0k2p4bvr8za1ii6j
DigitalTraceabilityEvent0.6.1cwb7m3k0hpz9xqft6rjn2oe4s
DigitalTraceabilityEvent0.7.0cfhlj3bumipb74z8irp6uiuxn

These IDs are defined in prisma/seed.ts. New core data models may be added in future releases.

Render Templates

Render templates define how credentials are displayed. Each template references an HTML file (typically a Handlebars .hbs file) by relative path from the mount directory.

renderTemplates:
- id: "clxyz1234567890templ1"
name: "AU DPP Default Template"
file: "templates/au-dpp.hbs"
dataModelId: "clxyz1234567890model1"
renderMethodType: "RenderTemplate2024"
isDefault: true # optional, defaults to false
inline: false # optional
mediaType: "text/html" # optional
mediaQuery: "" # optional
FieldTypeRequiredDescription
idCUID v1YesUnique identifier
namestringYesDisplay name
filestringYesRelative path to the template file within the mount directory
dataModelIdCUID v1YesReference to a data model (in the manifest or already in the database)
renderMethodTypestringYesEither "RenderTemplate2024" or "WebRenderingTemplate2022"
isDefaultbooleanNoWhether this is the default template for its data model (defaults to false)
inlinebooleanNoWhether the template should be inlined
mediaTypestringNoMIME type of the rendered output
mediaQuerystringNoCSS media query for the template

The template file at the specified path is uploaded to the storage service during seed processing. The storage service must be configured and reachable.

caution

Only one render template per data model may have isDefault: true. If multiple templates for the same data model are marked as default, validation will fail.

Conformity Schemes

Conformity schemes are ingested under the system tenant with source = SYSTEM_SEED. Each entry points at a single scheme document — either a remote URL the loader fetches at seed time, or a local JSON-LD file in the mount directory.

conformitySchemes:
- url: "https://vocab.example.com/scheme-a"
version: "0.7.0"
- file: "schemes/scheme-b.json"
version: "0.7.0"
FieldTypeRequiredDescription
urlURLYes (or file)HTTP(S) URL of the scheme document to fetch at seed time
filestringYes (or url)Path relative to the mount directory of a local JSON-LD scheme document
versionstringYesCVC specification version this document conforms to, e.g. "0.7.0"

Exactly one of url or file must be set per entry. The scheme's display name and structure (profiles, criteria) are derived from the document itself; no operator-supplied name is required.

The seed loader is insert-only-if-absent: if a row already exists at (sourceUrl, systemTenantId) (from a prior seed run, UNTP discovery, or operator action), the entry is skipped. This preserves any post-seed updates rather than overwriting them.

For file entries, the loader reads the top-level id (or @id) from the JSON-LD document to determine the row's sourceUrl. The file must be a valid JSON-LD scheme document.

Complete Example

Below is a full example manifest that provisions a custom registrar with an identifier scheme, a data model extension, a render template, and two conformity schemes (one fetched from a URL, one read from a local file).

Mount directory structure:

my-seed/
seed.yaml
templates/
au-dpp.hbs

seed.yaml:

registrars:
- id: "clxyz1234567890abreg1"
name: "Australian Business Register"
namespace: "abr.gov.au"
url: "https://abr.gov.au"
identifierSchemes:
- id: "clxyz1234567890abnsc"
name: "Australian Business Number"
primaryKey: "abn"
validationPattern: "^\\d{11}$"
linkTemplate: "https://abr.business.gov.au/ABN/View?abn={value}"

dataModels:
- id: "clxyz1234567890aumod"
name: "AU Digital Product Passport"
credentialType: "DigitalProductPassport"
version: "1.0.0"
parentConfigId: "c1pxfzzkeb86jgeel7hrvmcle"
schemaUrl: "https://example.com/schemas/au-dpp-v1.json"
contextUrl: "https://example.com/contexts/au-dpp-v1.jsonld"
websiteUrl: "https://example.com/docs/au-dpp"

renderTemplates:
- id: "clxyz1234567890autpl"
name: "AU DPP Template"
file: "templates/au-dpp.hbs"
dataModelId: "clxyz1234567890aumod"
renderMethodType: "RenderTemplate2024"
isDefault: true

conformitySchemes:
- url: "https://vocab.example.com/au-scheme"
version: "0.7.0"
- file: "schemes/au-fallback-scheme.json"
version: "0.7.0"

Validation

The manifest goes through two validation phases before any data is written to the database.

Phase 1: Schema Validation

The YAML is validated against a strict schema. Errors at this phase include:

  • Missing required fields
  • Invalid field types (e.g. a number where a string is expected)
  • IDs that are not valid CUID v1 format
  • URLs that are not valid
  • Invalid renderMethodType values

Phase 2: Referential Integrity

After schema validation passes, the system checks cross-references and constraints:

  • No duplicate IDs — every id across the entire manifest must be unique
  • Parent data model references — each data model's parentConfigId must reference a core (non-extension) data model that exists in the database
  • Render template data model references — each render template's dataModelId must reference either a data model in the manifest or one already in the database
  • Template file existence — each render template's file must point to a file that exists within the mount directory
  • Path traversal protection — template file paths cannot reference files outside the mount directory (e.g. ../../etc/passwd is rejected)
  • Default template uniqueness — only one render template per data model may be marked as isDefault: true
  • Tenant ID collision protection — IDs that already exist in a non-system tenant cannot be upserted

If any validation error is detected, the seed process exits with code 1 and logs the specific errors. No data is written.

Conformity scheme entries are validated structurally (URL XOR file, required version) at Phase 1. Other concerns (URL reachability, file existence, presence of the ConformityScheme data-model row for the entry's version) are evaluated at processing time and recorded as per-entry skips or failures; they do not abort the seed pass.

Processing Order

Once validation passes, the custom seed processes entities in the following order:

  1. Upload render template files to the storage service
  2. Upsert all entities (registrars, schemes, qualifiers, data models, render templates) in a single atomic database transaction
  3. Ingest conformity schemes (outside the main transaction). For each entry, the loader either fetches the URL or reads the file, then runs the scheme through the CVC pipeline (fetch → JSON parse → schema validate → JSON-LD expand → parse → persist). Per-entry failures are logged and counted; the loop continues to the next entry.

Important Notes

  • Runs after core seed — custom seed depends on core data models and service instances already existing. It runs as the final step of the seed process described in Startup.
  • Idempotent — all operations use upsert. Re-running the seed with the same manifest is safe and will update existing records.
  • Atomic — registrars, schemes, qualifiers, data models, and render templates are written in a single database transaction. If any write fails, the entire batch is rolled back.
  • System tenant ownership — all custom seed entities are owned by the system tenant.
  • Storage service required for templates — if your manifest includes render templates, the storage service must be configured and reachable at seed time.
  • Network access required for URL-sourced conformity schemes — any conformitySchemes entry with a url field must be reachable at seed time. File-sourced entries are read directly from the mount directory and do not need network access.
  • Exit on error — validation failures cause the seed process to exit with code 1, preventing the application from starting with invalid data.