Skip to main content

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.

:::warning Back up before upgrading Always back up your database and your assets/config/ directory before performing a major version upgrade. :::

Summary of Breaking Changes

AreaChangeImpact
Credential Configuration FormatConfigs migrate from v1 (claims model) to v2 (field-based model). DB columns claims, disclosureFrame, schema are dropped.High
Removed Top-Level Config Fieldsclaims, disclosureFrame, schema removed from credential config JSONHigh
Removed config Sub-Fieldsconfig.namespace, config.claimsByNamespace, config.claimsMetadata removedHigh
Schema Metadata API EndpointsPOST /api/issuer/credentials/schema-metadata/sign and /sign-version moved to registrar controllerMedium
Removed DTOs / TypesClaimMetadata, ClaimDisplayInfo, SchemaResponse removed from public API and SDKMedium
Filesystem Config Importv1 JSON files in assets/config/.../issuance/credentials/ are rejected on import — must be migratedMedium
KeyChainEntity JWK columnsDB columns activeKey/rootKey/previousKey renamed to activeJwk/rootJwk/previousJwkLow
key_chain.externalKeyIdNew integrity constraint: external KMS providers (vault, aws-kms) must have a non-null externalKeyIdLow

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

Aspectv1 (Legacy)v2 (Field-based)
StructureSeparate claims, disclosureFrame, schema, claimsMetadataSingle fields[] array
Field DefinitionImplicit in JSON schemaExplicit ClaimFieldDefinition
Selective DisclosureVia disclosureFrame structurePer-field disclosable flag
Display InfoEmbedded in config.claimsMetadataPer-field display[] array
mDOC Namespaceconfig.namespace or claimsByNamespacePer-field namespace property
ValidationTop-level JSON schemaDerived at runtime from fields

Manual File Migration

:::warning 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:

  • claims becomes fields[].defaultValue
  • disclosureFrame becomes fields[].disclosable
  • schema is no longer stored in the config file; it is derived from fields[]
  • config.claimsMetadata becomes fields[].display[] and fields[].mandatory
  • config.namespace becomes fields[].namespace for mDOC entries
  • config.claimsByNamespace becomes one field entry per claim, each with the correct namespace and defaultValue

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",
"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:

  1. fields[] contains one entry for each leaf claim you want to issue
  2. defaultValue matches the old claims value
  3. disclosable matches the old disclosureFrame behavior for SD-JWT
  4. namespace is set correctly for mDOC fields
  5. display[] 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:

  • claims
  • disclosureFrame
  • schema

Also remove legacy nested config keys:

  • config.namespace
  • config.claimsByNamespace
  • config.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

  1. Re-import the config into your environment or test instance
  2. Issue a test credential in each affected format
  3. Confirm the credential payload, selective disclosure, and mDOC namespace behavior match the old config
  4. 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 by fields[].defaultValue
  • disclosureFrame — replaced by fields[].disclosable
  • schema — derived at runtime from fields[]

Removed config Sub-Fields

The following fields are no longer accepted inside IssuerMetadataCredentialConfig (config):

  • config.namespace — moved to per-field fields[].namespace (mDOC only)
  • config.claimsByNamespace — replaced by per-field fields[].namespace plus fields[].defaultValue
  • config.claimsMetadata — replaced by fields[].display[] and fields[].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/signPOST /api/schema-metadata/sign
POST /api/issuer/credentials/schema-metadata/sign-versionPOST /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:

  • ClaimMetadata
  • ClaimDisplayInfo
  • SchemaResponse

Replace them in your code with:

  • ClaimFieldDefinitionDto (replaces ClaimMetadata)
  • FieldDisplayDto (replaces ClaimDisplayInfo)

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 columnNew column
activeKeyactiveJwk
rootKeyrootJwk
previousKeypreviousJwk

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 CHECK constraint (kmsProvider = 'db' OR externalKeyId IS NOT NULL).
  • SQLite: equivalent BEFORE INSERT/BEFORE UPDATE triggers 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",
"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"
}
]
}

  1. Back up your database and assets/config/ directory
  2. Stop the running 4.x deployment
  3. Convert all credential config files manually using the example above as the template
  4. Re-test issuance with the converted configs before touching production
  5. Deploy 5.0 once all configs are v2 and validated
  6. Update API clients that called the moved /schema-metadata/sign[-version] endpoints
  7. Regenerate SDK types (pnpm run gen:api) and fix any TypeScript errors from removed DTOs
  8. Verify by issuing test credentials in each configured format