Configuring Flavors and Features
Introduction
Section titled “Introduction”The yuuvis Shell supports two complementary server-side configuration mechanisms that extend the shell client without requiring code changes or redeployment: object flavor configuration and feature configuration.
Object flavor configuration (yuv:shell:types) makes additional Secondary Object Types (SOTs) available in the Shell UI. SOTs that are not registered by any client app — for example, types from a tenant-specific schema imported directly into the backend — can be registered here so users can apply them to objects.
Feature configuration (yuv:shell:features) controls which apps and extensions are visible and accessible to which users. Instead of building separate client versions for different roles or tenants, a single client can serve all users while the feature configuration determines what each user sees.
Both configurations are stored as JSON files attached to system:item DMS objects. The Shell loads them during bootstrap and applies them throughout the session.
Why Flavors and Features Configuration?
Section titled “Why Flavors and Features Configuration?”Tenant-Specific Object Types Without Code Enterprise deployments often include tenant-specific document schemas with custom object types. Without flavor configuration, these types would only be accessible if a client app explicitly registers them. Flavor configuration lets administrators make any SOT available in the Shell UI without touching the client code.
Role-Based App Visibility Without Separate Builds Different user groups — accounting, HR, administrators, external partners — typically need different sets of apps. Feature configuration lets a single deployed client show each user only the apps and extensions they are authorized to use, driven entirely by server-side configuration.
Runtime Adaptability Both configurations are loaded fresh at every bootstrap. Administrators can change what types are available or which roles can access which apps without any client release. The change takes effect the next time users open the Shell.
Separation of Administrative Concerns Client developers define the available apps and object types in code. Administrators control access and availability through DMS configuration. Neither side needs to coordinate a deployment to adjust the other’s domain.
How It Works
Section titled “How It Works”Both configurations follow the same storage and retrieval pattern:
- A
system:itemDMS object with the secondary typesystem:configurationholds a JSON file as its content stream - The object is identified by its
system:nameproperty (yuv:shell:typesoryuv:shell:features) - During bootstrap, the Shell searches for this object by name and fetches the JSON file
- The parsed configuration is applied before the app component renders
Each mechanism is independently opt-in: enable them individually via enableObjectTypeConfig and enableFeatureConfig in the bootstrap options.
Configuring Object Flavors
Section titled “Configuring Object Flavors”Object flavor configuration is stored as a JSON file attached to a dedicated DMS object. When enabled, the Shell fetches this file during bootstrap and registers the declared flavors globally — no code changes or redeployment required.
What Is a Flavor?
Section titled “What Is a Flavor?”A flavor is a Secondary Object Type (SOT) that users can apply to a DMS object to enrich it with additional metadata. Flavors appear in the object sidebar as applicable actions and as chips on already-enriched objects. They are normally registered by client apps at development time, but can also be provided through a server-side configuration file.
For a full description of the flavor concept, data model, and programmatic API, see Object Flavors.
Enabling Flavor Configuration
Section titled “Enabling Flavor Configuration”Pass enableObjectTypeConfig: true to the bootstrap options in main.ts:
import { bootstrapShellApplication } from '@yuuvis/client-shell-core';import { AppComponent } from './app/app.component';import { appConfig } from './app/app.config';
bootstrapShellApplication( AppComponent, appConfig, { enableObjectTypeConfig: true }).catch((err) => console.error(err));When this option is set, the Shell runs ShellService._initObjectTypeConfig() as an APP_INITIALIZER before rendering. If no yuv:shell:types object exists in the DMS, the initializer completes silently with no effect.
Creating the Configuration Object
Section titled “Creating the Configuration Object”If the configuration object does not yet exist, create it with a POST request to the DMS objects endpoint. The request uses multipart form-data with two parts: data (the object metadata) and a file part containing the JSON configuration.
Create the DMS object:
POST https://<domain>/api/dms/objectsContent-Type: multipart/form-dataThe data part:
{ "objects": [ { "properties": { "system:name": { "value": "yuv:shell:types" }, "system:objectTypeId": { "value": "system:item" }, "system:secondaryObjectTypeIds": { "value": ["system:configuration"] }, "system:baseTypeId": { "value": "system:item" } }, "contentStreams": [ { "cid": "cid_types_config" } ] } ]}The second part of the multipart body should use cid_types_config as the content ID and contain your JSON configuration file.
Once the object exists, update the configuration file using the DMS Patch API:
PATCH https://<domain>/api/dms/objects/{objectId}/contents/fileTo find the object ID at any time:
SELECT * FROM system:item WHERE system:name = 'yuv:shell:types'Configuration Reference
Section titled “Configuration Reference”| Property | Mandatory | Type | Default | Description |
|---|---|---|---|---|
id | yes | string | — | Technical name of the SOT to register (e.g. tenMyschema:myCase). Used for icon lookup and localization. |
baseType | yes | string | — | The base object type this SOT applies to: system:object (all), system:folder, or system:document. |
instantApply | no | boolean | false | If true, the SOT is applied immediately without showing a metadata input dialog. |
applicableToMimeTypes | no | string[] | [""] | MIME type patterns that restrict which documents show this flavor. An empty string "" matches all files. |
Examples
Section titled “Examples”Tenant Case Type for Folders
Section titled “Tenant Case Type for Folders”A custom case management SOT that applies to folders only. Users select the flavor from the sidebar; a metadata dialog opens for them to fill in case details before the SOT is applied.
{ "id": "tenMytenantschema:myCase", "baseType": "system:folder", "instantApply": false, "applicableToMimeTypes": [""]}Document Classification Type
Section titled “Document Classification Type”A general document SOT that applies to all documents regardless of file type. Useful for classification types that are independent of content format.
{ "id": "tenMytenantschema:classifiedDocument", "baseType": "system:document", "instantApply": false, "applicableToMimeTypes": ["*/*"]}Instant-Apply Image Annotation Type
Section titled “Instant-Apply Image Annotation Type”An image-specific SOT that is applied immediately when the user selects it — no dialog is shown. Only appears for documents that contain an image file.
{ "id": "appMyapp:idPicture", "baseType": "system:document", "instantApply": true, "applicableToMimeTypes": ["image/*"]}Complete Configuration File
Section titled “Complete Configuration File”A realistic configuration combining multiple types:
[ { "id": "tenMytenantschema:myCase", "baseType": "system:folder", "instantApply": false, "applicableToMimeTypes": [""] }, { "id": "tenMytenantschema:classifiedDocument", "baseType": "system:document", "instantApply": false, "applicableToMimeTypes": ["*/*"] }, { "id": "appMyapp:idPicture", "baseType": "system:document", "instantApply": true, "applicableToMimeTypes": ["image/*"] }]Configuring App and Extension Features
Section titled “Configuring App and Extension Features”Feature configuration controls which apps and extensions are visible to which users. Like flavor configuration, the rules are stored as a JSON file in the DMS and are applied at bootstrap — allowing administrators to adjust access without any client redeployment.
What Is a Feature Entry?
Section titled “What Is a Feature Entry?”A feature entry defines the visibility and access rules for a single app or extension in the Shell. Each entry references the app by its technical ID and declares which user roles are allowed or denied access.
Feature configuration is a server-side complement to the Conditional App Activation mechanism. While conditional activation defines requirements and permissions in client code, feature configuration allows administrators to adjust app visibility dynamically — without touching the client application.
Enabling Feature Configuration
Section titled “Enabling Feature Configuration”Pass enableFeatureConfig: true to the bootstrap options in main.ts:
import { bootstrapShellApplication } from '@yuuvis/client-shell-core';import { AppComponent } from './app/app.component';import { appConfig } from './app/app.config';
bootstrapShellApplication( AppComponent, appConfig, { enableFeatureConfig: true }).catch((err) => console.error(err));Feature configuration is loaded before the app component renders. The Shell queries DMS for the yuv:shell:features object, parses the JSON, and injects the result into FeatureService via provideClientShellFeatures(). If no configuration object exists, all apps are treated as accessible.
Creating the Configuration Object
Section titled “Creating the Configuration Object”Create the DMS object with a POST request if it does not yet exist:
POST https://<domain>/api/dms/objectsContent-Type: multipart/form-dataThe data part:
{ "objects": [ { "properties": { "system:name": { "value": "yuv:shell:features" }, "system:objectTypeId": { "value": "system:item" }, "system:secondaryObjectTypeIds": { "value": ["system:configuration"] }, "system:baseTypeId": { "value": "system:item" } }, "contentStreams": [ { "cid": "cid_features_config" } ] } ]}Once the object exists, update the configuration file using the DMS Patch API:
PATCH https://<domain>/api/dms/objects/{objectId}/contents/fileTo find the object ID:
SELECT * FROM system:item WHERE system:name = 'yuv:shell:features'Configuration Reference
Section titled “Configuration Reference”| Property | Mandatory | Type | Default | Description |
|---|---|---|---|---|
id | yes | string | — | Technical app or extension ID as registered in the Shell client project. |
extension | no | boolean | false | Set to true for extension entries. Regular apps omit this property or set it to false. |
allowed | no | string[] | [] | Role names or user IDs that may use this app. An empty array grants access to all users. |
denied | no | string[] | [] | Role names or user IDs that are explicitly blocked. denied always overrides allowed. |
Permission Evaluation
Section titled “Permission Evaluation”For each app, FeatureService evaluates the following logic:
isAllowed = allowed.length === 0 OR user has at least one role in allowedisDenied = user has at least one role in denied
app is accessible = isAllowed AND NOT isDeniedKey rules:
- An empty
allowedarray means everyone is allowed — the app is not role-restricted - A non-empty
allowedarray means only users with a matching role can access the app deniedis always evaluated last and overridesallowed— a user with both an allowed and a denied role is blocked
The check is applied at three levels:
- Navigation — the app does not appear in the sidebar
- Route guard —
canMatchFeatureblocks direct URL access - Extension initialization — extension modules are not initialized at all, saving resources
Examples
Section titled “Examples”App Accessible to All Users
Section titled “App Accessible to All Users”An empty allowed array combined with an empty denied array makes the app visible to everyone. This is useful to explicitly document that an app is intentionally open, or to add a denied exclusion later without restructuring the entry.
{ "id": "io.yuuvis.app.search", "allowed": [], "denied": []}App Restricted to a Role
Section titled “App Restricted to a Role”Only users with the ACCOUNTING role see this app. All other users do not see it in navigation and cannot access its routes.
{ "id": "io.yuuvis.app.invoice", "allowed": ["ACCOUNTING"], "denied": []}Multiple Allowed Roles
Section titled “Multiple Allowed Roles”Users with either YUUVIS_TENANT_ADMIN or SUPPORT can access the app.
{ "id": "io.yuuvis.app.admin", "allowed": ["YUUVIS_TENANT_ADMIN", "SUPPORT"], "denied": []}Denying a Role Despite Broad Access
Section titled “Denying a Role Despite Broad Access”The inbox app is open to all users except those with the YUUVIS_NO_BPM role. This pattern is useful when you want to block a specific group without listing every other role in allowed.
{ "id": "io.yuuvis.app.inbox", "allowed": [], "denied": ["YUUVIS_NO_BPM"]}Extension with Role Restriction
Section titled “Extension with Role Restriction”Extensions must be marked with "extension": true. Otherwise the entry is ignored for extensions.
{ "id": "io.yuuvis.app.invoice.extension", "extension": true, "allowed": ["ACCOUNTING"], "denied": []}Complete Configuration File
Section titled “Complete Configuration File”A realistic configuration covering a mix of apps and extensions:
[ { "id": "io.yuuvis.app.search", "allowed": [], "denied": [] }, { "id": "io.yuuvis.app.inbox", "allowed": [], "denied": ["YUUVIS_NO_BPM"] }, { "id": "io.yuuvis.app.admin", "allowed": ["YUUVIS_TENANT_ADMIN"], "denied": [] }, { "id": "io.yuuvis.app.invoice", "allowed": ["ACCOUNTING"], "denied": [] }, { "id": "io.yuuvis.app.invoice.extension", "extension": true, "allowed": ["ACCOUNTING"], "denied": [] }, { "id": "io.yuuvis.app.ai.extension", "extension": true, "allowed": ["YUUVIS_APP_AI"], "denied": [] }]Managing Configurations
Section titled “Managing Configurations”Both configuration objects can be managed programmatically using ShellConfigService from @yuuvis/shell-core. The service provides a consistent API for creating, reading, updating, and deleting DMS-backed configuration objects.
import { inject } from '@angular/core';import { ShellConfigService } from '@yuuvis/shell-core';
export class MyAdminComponent { #configService = inject(ShellConfigService);
// Fetch the current types configuration loadTypesConfig() { this.#configService.get('yuv:shell:types').subscribe(config => { console.log(config.data); // parsed JSON content }); }
// Update the features configuration saveFeatures(objectId: string, updatedFeatures: object[]) { this.#configService.update(objectId, updatedFeatures).subscribe(); }
// Download the configuration as a file exportConfig(objectId: string) { this.#configService.download(objectId, 'features-config.json'); }
// Upload a new configuration file importConfig(objectId: string, file: File) { this.#configService.upload(objectId, file).subscribe(); }
// Delete the configuration object entirely deleteConfig(objectId: string) { this.#configService.delete(objectId).subscribe(); }}| Method | Description |
|---|---|
get(name) | Fetches the configuration object and its file content by system:name |
create(name, data) | Creates a new configuration object with the given name and JSON data |
update(id, data) | Replaces the file content of an existing configuration object |
delete(id) | Deletes the configuration object from DMS |
download(id, filename?) | Triggers a file download of the configuration |
upload(id, file) | Uploads a new file to replace the current configuration |