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
| Variable | Description | Default |
|---|---|---|
SKIP_CUSTOM_SEED | Set to true to skip custom seed processing entirely | false |
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
| Field | Type | Required | Description |
|---|---|---|---|
id | CUID v1 | Yes | Unique identifier |
name | string | Yes | Display name |
namespace | string | Yes | Namespace identifier |
url | URL | No | Registrar website |
idrServiceInstanceId | CUID v1 | No | Reference to an IDR service instance |
identifierSchemes | array | No | Nested identifier schemes |
Identifier Scheme fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | CUID v1 | Yes | Unique identifier |
name | string | Yes | Display name |
primaryKey | string | Yes | Primary key code (e.g. "01" for GTIN) |
validationPattern | string | Yes | Regular expression for validating identifiers |
linkTemplate | string | Yes | URL template for resolving identifiers |
qualifiers | array | No | Nested qualifiers |
Qualifier fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | CUID v1 | Yes | Unique identifier |
key | string | Yes | Qualifier key code |
description | string | Yes | Human-readable description |
validationPattern | string | Yes | Regular expression for validating qualifier values |
order | integer | No | Sort 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
| Field | Type | Required | Description |
|---|---|---|---|
id | CUID v1 | Yes | Unique identifier |
name | string | Yes | Display name |
credentialType | string | Yes | Credential type name |
version | string | Yes | Version string |
parentConfigId | CUID v1 | Yes | ID of an existing core data model in the database |
schemaUrl | URL | Yes | JSON Schema URL for the credential |
contextUrl | URL | Yes | JSON-LD context URL |
websiteUrl | URL | No | Documentation 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 Type | Version | ID |
|---|---|---|
| DigitalProductPassport | 0.6.0 | cxuj555flzqtp4ldvklv6ya39 |
| DigitalProductPassport | 0.6.1 | c1pxfzzkeb86jgeel7hrvmcle |
| DigitalProductPassport | 0.7.0 | ca3frzta22f7lblxntvw6ukuh |
| DigitalConformityCredential | 0.6.0 | c3imzyum0txv1y9xkww88aktp |
| DigitalConformityCredential | 0.6.1 | cttpz40pfgcfeue2wmbc3jti8 |
| DigitalConformityCredential | 0.7.0 | ca9ndkrc8lxmtsfzwynui40zy |
| DigitalFacilityRecord | 0.6.0 | ctfgtrsuiwv1fedo9t5swxhnk |
| DigitalFacilityRecord | 0.6.1 | csrtste8ai2llop7ui8u6n11l |
| DigitalFacilityRecord | 0.7.0 | cj3s37lt6pvh56ggspr9upt5m |
| DigitalIdentityAnchor | 0.6.0 | cz9raijqcay5nzmq59geoggrk |
| DigitalIdentityAnchor | 0.6.1 | cn5u63huxvqgdwppebaxmqt9l |
| DigitalIdentityAnchor | 0.7.0 | cw0tzf723j1oql3u4s1r0c2g2 |
| DigitalTraceabilityEvent | 0.6.0 | crqvpwffc0k2p4bvr8za1ii6j |
| DigitalTraceabilityEvent | 0.6.1 | cwb7m3k0hpz9xqft6rjn2oe4s |
| DigitalTraceabilityEvent | 0.7.0 | cfhlj3bumipb74z8irp6uiuxn |
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
| Field | Type | Required | Description |
|---|---|---|---|
id | CUID v1 | Yes | Unique identifier |
name | string | Yes | Display name |
file | string | Yes | Relative path to the template file within the mount directory |
dataModelId | CUID v1 | Yes | Reference to a data model (in the manifest or already in the database) |
renderMethodType | string | Yes | Either "RenderTemplate2024" or "WebRenderingTemplate2022" |
isDefault | boolean | No | Whether this is the default template for its data model (defaults to false) |
inline | boolean | No | Whether the template should be inlined |
mediaType | string | No | MIME type of the rendered output |
mediaQuery | string | No | CSS 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.
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"
| Field | Type | Required | Description |
|---|---|---|---|
url | URL | Yes (or file) | HTTP(S) URL of the scheme document to fetch at seed time |
file | string | Yes (or url) | Path relative to the mount directory of a local JSON-LD scheme document |
version | string | Yes | CVC 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
renderMethodTypevalues
Phase 2: Referential Integrity
After schema validation passes, the system checks cross-references and constraints:
- No duplicate IDs — every
idacross the entire manifest must be unique - Parent data model references — each data model's
parentConfigIdmust reference a core (non-extension) data model that exists in the database - Render template data model references — each render template's
dataModelIdmust reference either a data model in the manifest or one already in the database - Template file existence — each render template's
filemust 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/passwdis 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:
- Upload render template files to the storage service
- Upsert all entities (registrars, schemes, qualifiers, data models, render templates) in a single atomic database transaction
- 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
conformitySchemesentry with aurlfield 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.