Skip to 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.

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:

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

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

  • 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 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 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",
    "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"
        }
    ]
}

  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