Skip to content
New Livestream: How Banks Protect Their Apps with FAPI 2.0. Register Now!

Data Extension Schemas

Data extension schemas let you add typed properties to configuration entities without changing the Duende storage database schema. Values remain part of the entity, while schema metadata defines their type, validation, queryability and display information.

IdentityServer supports extensions on:

  • Clients
  • API resources
  • API scopes
  • Identity resources
  • Dynamic identity providers
  • SAML service providers
Schema StoreChoose It When
In-memorySchema definitions live with application code, change through deployments and must be identical on every node.
Storage-backedAn administration system must create or change schemas at runtime without redeploying IdentityServer.

In-memory schemas are often the safer starting point. They keep schema changes in source control and release review, while configuration values can still be managed dynamically through the admin APIs.

Storage-backed schemas register ISchemaAdmin as well as ISchemaStore. They are more dynamic, but your administration system must coordinate schema compatibility with all running application versions. Use AddDynamicSchemas() when you intentionally need that model.

Define typed attributes once and reuse those definitions when assigning values:

ClientDataExtensions.cs
using Duende.IdentityServer.Stores.Storage;
using Duende.Storage.EntityAttributeValue;
public static class ClientDataExtensions
{
public static readonly TypedAttributeDefinition<string> Department =
new(
AttributeCode.Create("department"),
new ScalarAttributeType(ScalarDataType.String));
public static readonly TypedAttributeDefinition<int> CostCenter =
new(
AttributeCode.Create("cost_center"),
new ScalarAttributeType(ScalarDataType.Integer));
public static readonly SchemaConfiguration Schema = new()
{
SchemaId = SchemaId.Client,
DisplayName = "Client extensions",
Description = "Organization data attached to clients.",
AttributeDefinitions = [Department, CostCenter]
};
}

SchemaId.Client, SchemaId.ApiResource, SchemaId.ApiScope, SchemaId.IdentityResource and SchemaId.SamlServiceProvider select the entity type to extend. Dynamic identity providers use a type-specific ID, such as SchemaId.IdentityProvider("oidc"). Only register one schema for each ID.

Register the schema when configuring IdentityServer:

Program.cs
builder.Services
.AddIdentityServer()
.AddStorage(storage => storage.AddSqliteStore(/* ... */))
.AddInMemoryDataExtensionSchemas(
[ClientDataExtensions.Schema]);

First configure a database provider and run IDatabaseSchema.MigrateAsync as described in Getting Started. Then register the storage-backed schema services:

Program.cs
builder.Services
.AddIdentityServer()
.AddStorage(storage => storage.AddSqliteStore(/* ... */))
.AddDynamicSchemas();
// ...
await app.Services
.GetRequiredService<IDatabaseSchema>()
.MigrateAsync(CancellationToken.None);

After the database migration has completed, provision the schema through ISchemaAdmin:

Program.cs
using Duende.Storage.EntityAttributeValue;
var schemaAdmin = app.Services.GetRequiredService<ISchemaAdmin>();
var schemaId = ClientDataExtensions.Schema.SchemaId;
var existing = await schemaAdmin.GetAsync(
schemaId,
CancellationToken.None);
if (!existing.Found)
{
var created = await schemaAdmin.CreateAsync(
ClientDataExtensions.Schema,
CancellationToken.None);
if (!created.IsSuccess)
{
throw new InvalidOperationException(
string.Join("; ", created.Errors));
}
}

This example bootstraps an initial definition from code. After that, an administration system can create or update schemas at runtime through ISchemaAdmin without redeploying IdentityServer.

Data extension schemas are records in Duende Storage, not new relational tables. Creating or updating one does not require a new SQL migration after the common database schema is initialized. Run provisioning from one deployment process to avoid concurrent instances racing between the get and create operations.

To change a schema, get its current definition and pass the returned version to ISchemaAdmin.UpdateAsync. The version enforces optimistic concurrency. Expose schema administration only through an authenticated, authorized and audited management path.

Use the same typed definitions to add values to an administration model:

ConfigurationAdmin.cs
using Duende.IdentityServer.Admin.Clients;
using Duende.Storage.EntityAttributeValue;
var extensions = new AttributeValueCollection();
extensions.Set(ClientDataExtensions.Department, "Sales");
extensions.Set(ClientDataExtensions.CostCenter, 4100);
var client = new CreateClient
{
ClientId = "sales-dashboard",
AllowedGrantTypes = ["client_credentials"],
ExtendedProperties = extensions
};
var result = await clientAdmin.CreateAsync(client, ct);

The admin API rejects unknown properties, values of the wrong type, missing required properties and duplicate values for attributes marked as unique. Set IsQueryable only for values that your administration experience must filter or sort; queryable fields require additional database indexing and storage.

Treat schema changes like contract changes. Adding an optional property is usually compatible. Renaming or removing a property, changing its type or making it required can invalidate existing entities and older application versions.