Skip to main content

Build an app for end-users to create and manage dynamic schemas

This guide provides a scenario-based walk-through for implementing a backend that allows end users to create, manage, and publish schemas from a custom frontend app. The focus is on the backend logic and the SDK methods that enable this capability.

This guide assumes your frontend app provides a UI for defining a schema (for example, a form builder) and that this UI can send a ClaimsSchema object to your backend.

Part 1: Schema creation workflow

Step 1: Create a new empty schema

The first step is to create a new credential schema resource on the Truvity platform. At this point, the schema is empty and the app needs to populate it as the user adds fields in the UI. Use the SchemaCreate method for this purpose.

ResourceCredentialSchema createdSchema = client.schemas()
.schemaCreate(SchemaCreateRequest.builder()
.data(CredentialSchemaCreate.builder()
.schema(ClaimsSchemaOptional.builder().build())
.build())
.build());

Step 2: Handle schema updates

As the user interacts with the UI by adding or removing fields, you can handle schema updates in the backend. Use the SchemaUpdate method to apply incremental changes to the credential schema resource by providing the updated ClaimsSchema.

ResourceCredentialSchema updatedSchema = client.schemas()
.schemaUpdate(
createdSchema.getId(),
SchemaUpdateRequest.builder()
.ifMatch(createdSchema.getEtag())
.data(CredentialSchemaUpdate.builder()
.schema(ClaimsSchema.builder()
.addFields(ClaimsSchemaFieldsItem.string(
ClaimsSchemaFieldStringValue.builder()
.name("name")
.notEmpty(true)
.build()))
.addFields(ClaimsSchemaFieldsItem.number(
ClaimsSchemaFieldNumberValue.builder()
.name("year_of_birth")
.notEmpty(true)
.build()))
.build())
.build())
.build());

Step 3: Publish the schema

Once the user finalizes the schema in the UI, you can publish it using the SchemaPublish method. This action makes the schema immutable and publicly accessible. The user can provide the slug and version in the UI or the app can automatically generate them.

ResourcePublishedCredentialSchema publishedSchema = client.schemas()
.schemaPublish(
updatedSchema.getId(),
SchemaPublishRequest.builder()
.slug(slug)
.version(version)
.build());

Part 2: Working with the created schema

Step 4: Create a new draft

After the app publishes the schema, the user can use it to create a new credential draft. This example shows how you can create a draft from the previously created schema.

ResourceDraft createdDraft = client.drafts()
.draftCreate(DraftCreateRequest.builder()
.data(DraftCreate.builder()
.metaSchema(DraftCreateMetaSchema.of(
publishedSchema.getData().getUrl()))
.build())
.build());

In some cases you need to use a schema from a different tenant when creating a draft, for example when one organization defines structure for credentials it wants to receive. In this case, specify the URL of the schema from another tenant:

ResourceDraft createdDraft = client.drafts()
.draftCreate(DraftCreateRequest.builder()
.data(DraftCreate.builder()
.metaSchema(DraftCreateMetaSchema.of(
"https://ssi.truvity.com/tenants/{tenant_id}/meta-schema/{schema_slug}/v{schema_version}")
.build())
.build());

Step 5: Populate the draft with claims

The example below shows how the app can populate credential claims in the draft as the user fills in the data in the UI.

client.drafts()
.draftUpdate(
createdDraft.getId(),
DraftUpdateRequest.builder()
.ifMatch(createdDraft.getEtag())
.data(DraftUpdate.builder()
.values(Map.of(
"name",
CredentialClaimValue.string(StringClaimValue.builder()
.value("John Doe")
.build()),
"year_of_birth",
CredentialClaimValue.number(NumberClaimValue.builder()
.value(1985)
.build())))
.build())
.build());

Step 6: Issue the credential and retrieve credential claims

Once the user completes the draft, you can issue the credential. The draftLatestIssue method makes the credential immutable and verifiable.

You can then use the CredentialClaimValues method to retrieve the credential claims in a format-independent way for display in the user interface.

var issuedVc = client.drafts()
.draftLatestIssue(
createdDraft.getId(),
DraftLatestIssueRequest.builder()
.keyId(privateKeyId)
.build());

var issuedVcClaims = client.credentials().getCredentialClaimValues(issuedVc.getId());

Part 3: Schema evolution workflow

This workflow describes how your backend can handle the evolution of an existing, published schema. It starts with displaying an existing schema in the UI, allowing the user to make changes, and then publishing a new version of the schema.

Step 7: Search for and display an existing schema

When a user selects an existing schema from the UI (for example, Payment/v1), your backend first searches and retrieves it. Use the PublishedSchemaSearch method to find a schema by its slug and version, or the PublishedSchemaLatest method to retrieve it by its ID.

ListPublishedCredentialSchema foundPublishedSchemas = client.publishedSchemas()
.publishedSchemaSearch(PublishedSchemaSearchRequest.builder()
.filter(List.of(
PublishedSchemaBaseFilter.builder()
.data(PublishedSchemaFilterData.builder()
.slug(PublishedSchemaFilterDataSlug.equal(slug))
.build())
.build(),
PublishedSchemaBaseFilter.builder()
.data(PublishedSchemaFilterData.builder()
.version(PublishedSchemaFilterDataVersion.equal(version))
.build())
.build()))
.build());

ResourcePublishedCredentialSchema publishedSchema = foundPublishedSchemas.getItems().get(0);

// alternatively

ResourcePublishedCredentialSchema publishedSchema =
client.publishedSchemas().publishedSchemaLatest(schemaId);

Step 8: Copy and update the new schema

When the user wants to modify the schema in the UI (for example, to add a new field), your backend should create a new credential schema resource based on the original. This adheres to the principle of immutability. Use the SchemaCreate method with the fromPublishedMetaSchemaUrl parameter for this purpose. Use the SchemaUpdate method to apply the changes from the UI.

ResourceCredentialSchema copiedSchema = client.schemas()
.schemaCreate(SchemaCreateRequest.builder()
.data(CredentialSchemaCreate.builder()
.fromPublishedSchemaUrl(publishedSchema.getData().getUrl())
.build())
.build());

client.schemas()
.schemaUpdate(
copiedSchema.getId(),
SchemaUpdateRequest.builder()
.ifMatch(copiedSchema.getEtag())
.data(CredentialSchemaUpdate.builder()
.schema(updatedClaimsSchema)
.build())
.build());

Step 9: Publish the new schema version

Once the user finalizes their changes, they can publish the new schema as a new version. Use the schemaPublish method for this purpose. The user can either provide a new version number in the UI or the app can append it automatically.

ResourcePublishedCredentialSchema publishedCopiedSchema = client.schemas()
.schemaPublish(
copiedSchema.getId(),
SchemaPublishRequest.builder()
.slug(slug)
.version(version)
.build());

What's next

After you create and publish a schema, your app's backend is responsible for implementing the subsequent business logic. Truvity provides the tools to create and manage schemas, but the processing and validation of credential data against the schema are specific to your use case.

Your next steps typically involve:

  • Retrieve Credential Claims: Once a Verifiable Credential (VC) is created, retrieve its claim values.
  • Implement Business Logic: Process and validate the data according to your app's requirements. This might include comparing the claims of the VC against the schema definitions, running custom validation rules, or performing other logic specific to your business needs.

This business-specific logic must be coded within your app.

Further reading