Migrating from 4.x to 5.0¶
This guide covers all breaking changes introduced in EUDIPLO v5.0 and the steps required to migrate from any 4.x version.
Back up before upgrading
Always back up your database and your assets/config/ directory before performing a major version upgrade.
Summary of Breaking Changes¶
| Area | Change | Impact |
|---|---|---|
| Credential Configuration Format | Configs migrate from v1 (claims model) to v2 (field-based model). DB columns claims, disclosureFrame, schema are dropped. |
High |
| Removed Top-Level Config Fields | claims, disclosureFrame, schema removed from credential config JSON |
High |
Removed config Sub-Fields |
config.namespace, config.claimsByNamespace, config.claimsMetadata removed |
High |
| Schema Metadata API Endpoints | POST /api/issuer/credentials/schema-metadata/sign and /sign-version moved to registrar controller |
Medium |
| Removed DTOs / Types | ClaimMetadata, ClaimDisplayInfo, SchemaResponse removed from public API and SDK |
Medium |
| Filesystem Config Import | v1 JSON files in assets/config/.../issuance/credentials/ are rejected on import — must be migrated |
Medium |
KeyChainEntity JWK columns |
DB columns activeKey/rootKey/previousKey renamed to activeJwk/rootJwk/previousJwk |
Low |
key_chain.externalKeyId |
New integrity constraint: external KMS providers (vault, aws-kms) must have a non-null externalKeyId |
Low |
1. Credential Configuration Format (v1 to v2)¶
What Changed¶
Credential configurations now use a field-based model (v2) instead of the legacy claims/disclosure model (v1). This simplifies credential structure, improves clarity, and better aligns with the OID4VCI claim metadata specification.
Key Differences¶
| Aspect | v1 (Legacy) | v2 (Field-based) |
|---|---|---|
| Structure | Separate claims, disclosureFrame, schema, claimsMetadata |
Single fields[] array |
| Field Definition | Implicit in JSON schema | Explicit ClaimFieldDefinition |
| Selective Disclosure | Via disclosureFrame structure |
Per-field disclosable flag |
| Display Info | Embedded in config.claimsMetadata |
Per-field display[] array |
| mDOC Namespace | config.namespace or claimsByNamespace |
Per-field namespace property |
| Validation | Top-level JSON schema |
Derived at runtime from fields |
Manual File Migration¶
No Automated Migration Available
EUDIPLO will not write to your config files or modify them during upgrade. You must manually convert each credential config file from the v1 shape to the v2 shape before upgrading to 5.0. In the future, we will evaluate if writing to the config section is a good idea and how to do this safely without risking data loss or corruption.
For configs stored on disk in assets/config/.../issuance/credentials/, the file format must be migrated manually before you upgrade to 5.0. v5.0 only accepts the field-based model and rejects the legacy v1 shape on import.
The mapping is straightforward:
claimsbecomesfields[].defaultValuedisclosureFramebecomesfields[].disclosableschemais no longer stored in the config file; it is derived fromfields[]config.claimsMetadatabecomesfields[].display[]andfields[].mandatoryconfig.namespacebecomesfields[].namespacefor mDOC entriesconfig.claimsByNamespacebecomes one field entry per claim, each with the correctnamespaceanddefaultValue
Before (v1): Legacy Credential Config¶
{
"id": "pid",
"config": {
"format": "mso_mdoc",
"docType": "eu.europa.ec.eudi.pid.1",
"namespace": "eu.europa.ec.eudi.pid.1",
"display": [{ "name": "PID", "locale": "en-US" }],
"claimsMetadata": [
{
"path": ["given_name"],
"mandatory": true,
"display": [{ "name": "Given Name", "locale": "en-US" }]
},
{
"path": ["family_name"],
"display": [{ "name": "Family Name", "locale": "en-US" }]
}
]
},
"claims": {
"given_name": "John",
"family_name": "Doe"
},
"disclosureFrame": {
"_sd": ["given_name", "family_name"]
},
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"given_name": { "type": "string" },
"family_name": { "type": "string" }
},
"required": ["given_name"]
}
}
After (v2): Field-Based Credential Config¶
{
"id": "pid",
"configVersion": 2,
"config": {
"format": "mso_mdoc",
"docType": "eu.europa.ec.eudi.pid.1",
"display": [{ "name": "PID", "locale": "en-US" }]
},
"fields": [
{
"path": ["given_name"],
"type": "string",
"defaultValue": "John",
"mandatory": true,
"disclosable": true,
"namespace": "eu.europa.ec.eudi.pid.1",
"display": [{ "name": "Given Name", "locale": "en-US" }]
},
{
"path": ["family_name"],
"type": "string",
"defaultValue": "Doe",
"disclosable": true,
"namespace": "eu.europa.ec.eudi.pid.1",
"display": [{ "name": "Family Name", "locale": "en-US" }]
}
]
}
Step 1: Convert each config file¶
Open every credential config JSON file and rewrite it from the v1 structure to the v2 structure shown above.
If you have a credential with multiple namespaces, create one fields[] entry per claim and set the correct namespace on each field.
If a field was optional in the old schema, leave mandatory unset or set it to false.
Step 2: Verify the converted file¶
Check that:
configVersionis set to2fields[]contains one entry for each leaf claim you want to issuedefaultValuematches the oldclaimsvaluedisclosablematches the olddisclosureFramebehavior for SD-JWTnamespaceis set correctly for mDOC fieldsdisplay[]still contains the labels you want shown in the UI
Step 3: Remove legacy top-level keys¶
Delete these keys from the file once the v2 version is in place:
claimsdisclosureFrameschema
Also remove legacy nested config keys:
config.namespaceconfig.claimsByNamespaceconfig.claimsMetadata
Step 4: Update any external generators¶
If you generate credential configs from code or templates, update them to emit fields[] directly instead of the removed v1 keys.
Step 5: Validate before upgrade¶
- Re-import the config into your environment or test instance
- Issue a test credential in each affected format
- Confirm the credential payload, selective disclosure, and mDOC namespace behavior match the old config
- Only then upgrade the deployment to 5.0
Removed Top-Level Fields¶
The following top-level fields are no longer accepted on the credential config DTO and have been dropped from the database:
claims— replaced byfields[].defaultValuedisclosureFrame— replaced byfields[].disclosableschema— derived at runtime fromfields[]
Removed config Sub-Fields¶
The following fields are no longer accepted inside IssuerMetadataCredentialConfig (config):
config.namespace— moved to per-fieldfields[].namespace(mDOC only)config.claimsByNamespace— replaced by per-fieldfields[].namespaceplusfields[].defaultValueconfig.claimsMetadata— replaced byfields[].display[]andfields[].mandatory
2. Schema Metadata Signing Endpoints Moved¶
The signing endpoints for TS11 schema metadata were moved from the issuer controller to the registrar controller, so they live alongside the other registrar operations.
| Old (4.x) | New (5.0) |
|---|---|
POST /api/issuer/credentials/schema-metadata/sign |
POST /api/schema-metadata/sign |
POST /api/issuer/credentials/schema-metadata/sign-version |
POST /api/schema-metadata/sign-version |
Request and response bodies are unchanged. Update any clients that call these endpoints to use the new paths.
Info
The TS11 specification is still flagged as @experimental. Expect further changes in subsequent versions.
3. Removed DTOs and Types¶
The following types are removed from @eudiplo/sdk-core and the backend API:
ClaimMetadataClaimDisplayInfoSchemaResponse
Replace them in your code with:
ClaimFieldDefinitionDto(replacesClaimMetadata)FieldDisplayDto(replacesClaimDisplayInfo)
The runtime JSON schema previously exposed via SchemaResponse is derived on demand from fields[] via the buildJsonSchema() helper.
If you regenerate the API client (pnpm run gen:api), TypeScript will surface the removed types as compile errors at every call site.
4. KeyChainEntity JWK Column Rename¶
The DB columns that hold the active, root, and previous JWKs on the key_chain table were renamed for clarity. The columns continue to hold a single encrypted JWK each (private JWK for the db provider, public JWK for external providers):
| Old column | New column |
|---|---|
activeKey |
activeJwk |
rootKey |
rootJwk |
previousKey |
previousJwk |
Migration 1765000000000-RenameKeyChainActiveKeyToActiveJwk performs the rename in place using ALTER TABLE ... RENAME COLUMN, which is supported by both PostgreSQL and SQLite (≥ 3.25). No data transformation is required and no application code change is needed after upgrade — the TypeORM entity has been renamed in lockstep.
Action required only if you read these columns directly (ad-hoc SQL, dashboards, external scripts, reporting). Update those references to the new column names.
5. key_chain.externalKeyId Integrity Constraint¶
Migration 1764000000000-AddKmsExternalKeyIdCheck enforces that any key chain backed by a non-db KMS provider (vault, aws-kms) has a non-null externalKeyId:
- PostgreSQL: real
CHECKconstraint (kmsProvider = 'db' OR externalKeyId IS NOT NULL). - SQLite: equivalent
BEFORE INSERT/BEFORE UPDATEtriggers that abort the write.
This prevents orphaned external KMS references where the backend would not know which Vault/AWS key to sign with.
Action required only if you have legacy rows where kmsProvider != 'db' and externalKeyId IS NULL. The migration will fail on such rows. Inspect with:
SELECT id, "tenantId", "kmsProvider"
FROM "key_chain"
WHERE "kmsProvider" != 'db' AND "externalKeyId" IS NULL;
Either backfill the externalKeyId (recover the corresponding Vault/AWS key ID and update the row) or delete the orphaned row before upgrading.
6. Client UI Improvements (Non-Breaking)¶
The Angular client received additional improvements alongside the v2 migration:
- Submit button is disabled until all required fields are valid
- Tab error badges show the count of invalid controls per tab
- Inline validation hints appear once the form is dirty and invalid
- Locale dropdowns use consistent BCP47 labels across Display Configuration and Fields
No action is required — these are pure UX improvements.
Example Migration¶
Before (v1): PID Credential Config¶
{
"id": "pid",
"config": {
"format": "mso_mdoc",
"docType": "eu.europa.ec.eudi.pid.1",
"namespace": "eu.europa.ec.eudi.pid.1",
"display": [{ "name": "PID", "locale": "en-US" }],
"claimsMetadata": [
{
"path": ["given_name"],
"mandatory": true,
"display": [{ "name": "Given Name", "locale": "en-US" }]
}
]
},
"claims": {
"given_name": "John",
"family_name": "Doe"
},
"disclosureFrame": {
"_sd": ["given_name", "family_name"]
},
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"given_name": { "type": "string" },
"family_name": { "type": "string" }
},
"required": ["given_name"]
}
}
After (v2): PID Credential Config¶
{
"id": "pid",
"configVersion": 2,
"config": {
"format": "mso_mdoc",
"docType": "eu.europa.ec.eudi.pid.1",
"display": [{ "name": "PID", "locale": "en-US" }]
},
"fields": [
{
"path": ["given_name"],
"type": "string",
"defaultValue": "John",
"mandatory": true,
"disclosable": true,
"namespace": "eu.europa.ec.eudi.pid.1",
"display": [{ "lang": "en-US", "label": "Given Name" }]
},
{
"path": ["family_name"],
"type": "string",
"defaultValue": "Doe",
"disclosable": true,
"namespace": "eu.europa.ec.eudi.pid.1"
}
]
}
Recommended Upgrade Order¶
- Back up your database and
assets/config/directory - Stop the running 4.x deployment
- Convert all credential config files manually using the example above as the template
- Re-test issuance with the converted configs before touching production
- Deploy 5.0 once all configs are v2 and validated
- Update API clients that called the moved
/schema-metadata/sign[-version]endpoints - Regenerate SDK types (
pnpm run gen:api) and fix any TypeScript errors from removed DTOs - Verify by issuing test credentials in each configured format