Working with Designs
The IDK manages three types of design entities: credential designs for claim presentation, issuer designs for provider branding, and verifier designs for relying party display. This guide covers creating, querying, and updating these entities, along with their displays, claim definitions, and render variants.
Credential Designs
A credential design describes how a credential type should be presented to users. It combines localized display names, claim presentation rules, bindings that connect it to specific credential types, and render variants for different visual contexts.
Creating a Credential Design
- Android/Kotlin
- iOS/Swift
val design = designService.createCredentialDesign(
tenantId = tenantId,
input = CreateCredentialDesignInput(
alias = "identity-credential",
bindings = listOf(
DesignBinding(
key = DesignBindingKey.VCT,
value = "https://issuer.example.com/identity"
),
DesignBinding(
key = DesignBindingKey.CREDENTIAL_CONFIGURATION_ID,
value = "IdentityCredential"
)
),
displays = listOf(
LocalizedCredentialDisplay(
locale = "en",
name = "Identity Credential",
description = "Government-issued digital identity"
),
LocalizedCredentialDisplay(
locale = "nl",
name = "Identiteitsbewijs",
description = "Door de overheid uitgegeven digitale identiteit"
)
),
claims = listOf(
ClaimPresentation(
path = DesignClaimPath(listOf(ClaimPathSegment.Property("given_name"))),
labels = listOf(
ClaimLabel(locale = "en", label = "First Name"),
ClaimLabel(locale = "nl", label = "Voornaam")
),
mandatory = true,
order = 1,
valueKind = ClaimValueKind.STRING,
widgetHint = ClaimWidgetHint.TEXT
),
ClaimPresentation(
path = DesignClaimPath(listOf(ClaimPathSegment.Property("family_name"))),
labels = listOf(
ClaimLabel(locale = "en", label = "Last Name"),
ClaimLabel(locale = "nl", label = "Achternaam")
),
mandatory = true,
order = 2,
valueKind = ClaimValueKind.STRING,
widgetHint = ClaimWidgetHint.TEXT
),
ClaimPresentation(
path = DesignClaimPath(listOf(ClaimPathSegment.Property("birth_date"))),
labels = listOf(
ClaimLabel(locale = "en", label = "Date of Birth"),
ClaimLabel(locale = "nl", label = "Geboortedatum")
),
mandatory = true,
order = 3,
valueKind = ClaimValueKind.DATE,
widgetHint = ClaimWidgetHint.DATE
),
ClaimPresentation(
path = DesignClaimPath(listOf(ClaimPathSegment.Property("portrait"))),
labels = listOf(ClaimLabel(locale = "en", label = "Photo")),
mandatory = false,
order = 4,
valueKind = ClaimValueKind.IMAGE,
widgetHint = ClaimWidgetHint.IMAGE,
sdPolicy = SdPolicy.ALLOWED
)
)
)
)
let design = try await designService.createCredentialDesign(
tenantId: tenantId,
input: CreateCredentialDesignInput(
alias: "identity-credential",
bindings: [
DesignBinding(key: .vct, value: "https://issuer.example.com/identity"),
DesignBinding(key: .credentialConfigurationId, value: "IdentityCredential")
],
displays: [
LocalizedCredentialDisplay(
locale: "en",
name: "Identity Credential",
description: "Government-issued digital identity"
),
LocalizedCredentialDisplay(
locale: "nl",
name: "Identiteitsbewijs",
description: "Door de overheid uitgegeven digitale identiteit"
)
],
claims: [
ClaimPresentation(
path: DesignClaimPath([.property("given_name")]),
labels: [
ClaimLabel(locale: "en", label: "First Name"),
ClaimLabel(locale: "nl", label: "Voornaam")
],
mandatory: true,
order: 1,
valueKind: .string,
widgetHint: .text
),
ClaimPresentation(
path: DesignClaimPath([.property("family_name")]),
labels: [
ClaimLabel(locale: "en", label: "Last Name"),
ClaimLabel(locale: "nl", label: "Achternaam")
],
mandatory: true,
order: 2,
valueKind: .string,
widgetHint: .text
),
ClaimPresentation(
path: DesignClaimPath([.property("birth_date")]),
labels: [
ClaimLabel(locale: "en", label: "Date of Birth"),
ClaimLabel(locale: "nl", label: "Geboortedatum")
],
mandatory: true,
order: 3,
valueKind: .date,
widgetHint: .date
),
ClaimPresentation(
path: DesignClaimPath([.property("portrait")]),
labels: [ClaimLabel(locale: "en", label: "Photo")],
mandatory: false,
order: 4,
valueKind: .image,
widgetHint: .image,
sdPolicy: .allowed
)
]
)
)
The bindings list is key: it determines which credentials this design applies to. A design with both a VCT and CREDENTIAL_CONFIGURATION_ID binding will match credentials identified by either property.
Querying Designs
Designs can be retrieved by ID, binding, or listed with filters.
- Android/Kotlin
- iOS/Swift
// By ID
val design = designService.getCredentialDesign(tenantId, designId)
// By exact binding
val result = designService.findCredentialDesignByBinding(
tenantId = tenantId,
binding = DesignBinding(
key = DesignBindingKey.VCT,
value = "https://issuer.example.com/identity"
)
)
val designs = result.value // IdkResult<List<CredentialDesignRecord>, IdkError>
// By binding key and value (returns first match)
val result = designService.findCredentialDesignByBindingKey(
tenantId = tenantId,
bindingKey = DesignBindingKey.CREDENTIAL_CONFIGURATION_ID,
bindingValue = "IdentityCredential"
)
val designs = result.value // IdkResult<List<CredentialDesignRecord>, IdkError>
// List all designs
val designs = designService.listCredentialDesigns(tenantId)
// By ID
let design = try await designService.getCredentialDesign(tenantId: tenantId, designId: designId)
// By exact binding
let result = try await designService.findCredentialDesignByBinding(
tenantId: tenantId,
binding: DesignBinding(key: .vct, value: "https://issuer.example.com/identity")
)
let designs = result.value // IdkResult<List<CredentialDesignRecord>, IdkError>
// By binding key and value (returns first match)
let result = try await designService.findCredentialDesignByBindingKey(
tenantId: tenantId,
bindingKey: .credentialConfigurationId,
bindingValue: "IdentityCredential"
)
let designs = result.value // IdkResult<List<CredentialDesignRecord>, IdkError>
// List all designs
let designs = try await designService.listCredentialDesigns(tenantId: tenantId)
Claim Presentation
Claims are the most detailed part of a credential design. Each ClaimPresentation controls how a single claim is displayed to the user.
Claim Paths
Claims are identified by their path within the credential structure. Paths use segments that can be property names, array indices, or wildcard array elements:
// Simple property: "family_name"
DesignClaimPath(listOf(ClaimPathSegment.Property("family_name")))
// Nested property: "address.city"
DesignClaimPath(listOf(
ClaimPathSegment.Property("address"),
ClaimPathSegment.Property("city")
))
// mDoc namespace: "org.iso.18013.5.1.family_name"
DesignClaimPath(listOf(
ClaimPathSegment.Property("org.iso.18013.5.1"),
ClaimPathSegment.Property("family_name")
))
// Array element: "addresses[0].city"
DesignClaimPath(listOf(
ClaimPathSegment.Property("addresses"),
ClaimPathSegment.Index(0),
ClaimPathSegment.Property("city")
))
Value Kinds and Widget Hints
The valueKind tells the wallet what type of data the claim contains, while widgetHint suggests how to render it. These work together to produce appropriate UI:
| Value Kind | Widget Hints | Typical Rendering |
|---|---|---|
STRING | TEXT, MULTILINE_TEXT, BADGE | Text field, text area, or colored badge |
DATE | DATE | Formatted date with locale-aware formatting |
DATE_TIME | DATE_TIME | Formatted timestamp |
BOOLEAN | CHECKBOX | Toggle or checkmark |
IMAGE | IMAGE | Rendered image (base64 or URI) |
URI | URI | Clickable link |
NUMBER | TEXT | Formatted number with optional unit |
ARRAY | LIST, TABLE | List items or table rows |
OBJECT | GROUP | Nested group of sub-claims |
MARKDOWN | MARKDOWN | Rendered markdown content |
Selective Disclosure Policy
For SD-JWT credentials, the sdPolicy controls whether a claim can be selectively disclosed:
| Policy | Meaning |
|---|---|
ALWAYS | This claim is always selectively disclosable and must be individually consented |
ALLOWED | The claim may be disclosed selectively if the wallet and verifier support it |
NEVER | This claim is always included in presentations (e.g., credential type identifiers) |
Claim Groups and Ordering
Claims can be organized into groups and ordered within those groups. The order field controls display sequence, and the group field allows visual grouping in wallet UIs:
ClaimPresentation(
path = DesignClaimPath(listOf(ClaimPathSegment.Property("given_name"))),
labels = listOf(ClaimLabel(locale = "en", label = "First Name")),
order = 1,
group = "personal-info"
)
Issuer Designs
Issuer designs store branding information for credential issuers: display names, descriptions, and logos. They are linked to issuers through bindings or a partyId reference.
- Android/Kotlin
- iOS/Swift
val issuerDesign = designService.createIssuerDesign(
tenantId = tenantId,
input = CreateIssuerDesignInput(
alias = "example-issuer",
bindings = listOf(
DesignBinding(
key = DesignBindingKey.ISSUER_ID,
value = "https://issuer.example.com"
)
),
displays = listOf(
EntityLocaleDesign(
locale = "en",
displayName = "Example Government Agency",
description = "Official credential issuer"
)
)
)
)
let issuerDesign = try await designService.createIssuerDesign(
tenantId: tenantId,
input: CreateIssuerDesignInput(
alias: "example-issuer",
bindings: [
DesignBinding(key: .issuerId, value: "https://issuer.example.com")
],
displays: [
EntityLocaleDesign(
locale: "en",
displayName: "Example Government Agency",
description: "Official credential issuer"
)
]
)
)
A credential design can reference an issuer design through its issuerDesignId field, linking the credential's display metadata to the issuer's branding.
Verifier Designs
Verifier designs follow the same structure as issuer designs but are used when displaying information about relying parties in consent screens:
- Android/Kotlin
- iOS/Swift
val verifierDesign = designService.createVerifierDesign(
tenantId = tenantId,
input = CreateVerifierDesignInput(
alias = "airport-verifier",
bindings = listOf(
DesignBinding(key = DesignBindingKey.CUSTOM, value = "airport-border-control")
),
displays = listOf(
EntityLocaleDesign(
locale = "en",
displayName = "Airport Border Control",
description = "Identity verification at border crossing"
)
)
)
)
let verifierDesign = try await designService.createVerifierDesign(
tenantId: tenantId,
input: CreateVerifierDesignInput(
alias: "airport-verifier",
bindings: [
DesignBinding(key: .custom, value: "airport-border-control")
],
displays: [
EntityLocaleDesign(
locale: "en",
displayName: "Airport Border Control",
description: "Identity verification at border crossing"
)
]
)
)
Render Variants
Render variants define how a credential is visually represented. A single design can have multiple variants for different contexts: a simple card for list views, an SVG template for detail views, and a PDF template for printing.
Simple Card
The simplest render variant defines colors and an optional logo:
- Android/Kotlin
- iOS/Swift
val variant = designService.createRenderVariant(
tenantId = tenantId,
input = CreateRenderVariantInput(
kind = RenderVariantKind.SIMPLE_CARD,
alias = "identity-card-light",
localeApplicability = listOf("en", "nl"),
colors = CardColors(
background = "#1a365d",
text = "#ffffff",
accent = "#63b3ed"
),
logo = AssetReference(
uri = "https://issuer.example.com/logo-white.png",
altText = "Issuer Logo"
)
)
)
let variant = try await designService.createRenderVariant(
tenantId: tenantId,
input: CreateRenderVariantInput(
kind: .simpleCard,
alias: "identity-card-light",
localeApplicability: ["en", "nl"],
colors: CardColors(
background: "#1a365d",
text: "#ffffff",
accent: "#63b3ed"
),
logo: AssetReference(
uri: "https://issuer.example.com/logo-white.png",
altText: "Issuer Logo"
)
)
)
SVG Template
SVG templates provide rich credential visuals with placeholder substitution. The template can reference claim values using placeholder syntax, and the IDK handles substitution at render time:
- Android/Kotlin
- iOS/Swift
val svgVariant = designService.createRenderVariant(
tenantId = tenantId,
input = CreateRenderVariantInput(
kind = RenderVariantKind.SVG_TEMPLATE,
alias = "identity-svg-portrait",
svgTemplate = SvgTemplate(
uri = "https://issuer.example.com/templates/identity.svg",
orientation = SvgOrientation.PORTRAIT,
colorScheme = SvgColorScheme.LIGHT,
contrast = SvgContrast.NORMAL
)
)
)
let svgVariant = try await designService.createRenderVariant(
tenantId: tenantId,
input: CreateRenderVariantInput(
kind: .svgTemplate,
alias: "identity-svg-portrait",
svgTemplate: SvgTemplate(
uri: "https://issuer.example.com/templates/identity.svg",
orientation: .portrait,
colorScheme: .light,
contrast: .normal
)
)
)
W3C Render Method
For credentials following the W3C VC Render Method specification, you can reference external render method definitions:
val w3cVariant = designService.createRenderVariant(
tenantId = tenantId,
input = CreateRenderVariantInput(
kind = RenderVariantKind.W3C_RENDER_METHOD,
alias = "w3c-html-render",
w3cRenderMethod = W3cRenderMethodReference(
type = "SvgRenderingTemplate2024",
uri = "https://issuer.example.com/renders/identity.svg",
mediaType = "image/svg+xml",
name = "Identity Credential Card",
digestMultibase = "zQmWvQ..."
)
)
)
Asset Management
Design assets (logos, backgrounds, SVG templates) can be uploaded and managed through the design service. Assets are stored in the IDK's blob store and referenced from designs via AssetReference.
- Android/Kotlin
- iOS/Swift
// Upload a logo
val assetRef = designService.uploadDesignAsset(
tenantId = tenantId,
input = UploadDesignAssetInput(
designId = design.id,
assetType = DesignAssetType.LOGO,
content = logoPngBytes,
contentType = "image/png",
altText = "Issuer Logo"
)
)
// Retrieve the asset
val asset = designService.getDesignAsset(
tenantId = tenantId,
input = GetDesignAssetInput(
designId = design.id,
assetType = DesignAssetType.LOGO
)
)
// Upload a logo
let assetRef = try await designService.uploadDesignAsset(
tenantId: tenantId,
input: UploadDesignAssetInput(
designId: design.id,
assetType: .logo,
content: logoPngBytes,
contentType: "image/png",
altText: "Issuer Logo"
)
)
// Retrieve the asset
let asset = try await designService.getDesignAsset(
tenantId: tenantId,
input: GetDesignAssetInput(
designId: design.id,
assetType: .logo
)
)
Supported asset types are LOGO, BACKGROUND_IMAGE, SVG_TEMPLATE, and PDF_TEMPLATE.
Data Types
ClaimPresentation
data class ClaimPresentation(
val path: DesignClaimPath, // Claim path segments
val labels: List<ClaimLabel>, // Localized labels
val mandatory: Boolean = false, // Required claim
val sdPolicy: SdPolicy? = null, // Selective disclosure policy
val order: Int? = null, // Display ordering
val group: String? = null, // Visual grouping
val svgId: String? = null, // SVG template placeholder ID
val valueKind: ClaimValueKind? = null, // Data type hint
val widgetHint: ClaimWidgetHint? = null, // UI rendering hint
val markdownAllowed: Boolean = false, // Allow markdown rendering
val entryCodes: List<String>? = null, // Enumerated valid values
val unit: String? = null // Unit of measurement
)
RenderVariantRecord
data class RenderVariantRecord(
val id: String,
val tenantId: String,
val kind: RenderVariantKind, // SIMPLE_CARD, SVG_TEMPLATE, etc.
val alias: String? = null,
val localeApplicability: List<String>? = null,
val logo: AssetReference? = null,
val backgroundImage: AssetReference? = null,
val colors: CardColors? = null,
val svgTemplate: SvgTemplate? = null,
val w3cRenderMethod: W3cRenderMethodReference? = null,
val pdfTemplate: AssetReference? = null
)