Skip to content

Object Flavors

An object flavor is a Secondary Object Type (SOT) that a user can apply to a DMS object in order to enrich it with additional metadata properties. Applying a flavor adds the SOT’s metadata schema to the object without changing its base type. Flavors appear in the object sidebar as applicable actions and as chips on already-enriched objects.

Apps register flavors programmatically during initialization. Flavors can also be provided through a server-side configuration file, making SOTs available in the Shell UI without any client code changes. Both sources feed into a single global registry managed by ShellService.

SOT Enrichment Without Coupling An object gains a new metadata aspect without its base type changing. A plain document can become an invoice, an email, or a quality-controlled asset simply by having the corresponding SOT applied — no structural change to the object itself is required.

Declarative Applicability Each flavor declares when it is relevant: which base object type it targets (documents, folders, or both) and, for documents, which MIME types it makes sense for. The Shell evaluates these rules automatically and only surfaces flavors that match the current object. An email flavor only appears on .eml files; an image annotation flavor only appears on images.

Multi-Source Extensibility Apps register their own flavors at startup. Tenant-specific SOTs that are not registered by any app can be made available through configuration, with no code changes or redeployment required. Both sources coexist in the same registry.

Controlled User Interaction Flavors can be configured to open a metadata form before being applied, to apply instantly, or to open a fully custom dialog. Individual flavors can also be locked: preventApply makes a flavor visible but not selectable; preventRemove prevents removal once applied.

A flavor is defined using the AppFlavor interface, which apps and configuration provide. When the Shell registers a flavor, it creates an ObjectFlavor — a runtime extension of AppFlavor that adds tracking metadata (app, origin, name, description).

// From @yuuvis/client-core — base properties shared by all flavor sources
interface ObjectTypeFlavor {
id: string;
sot: string;
icon?: string;
svgIcon?: boolean;
applicableTo?: {
mimeTypes?: string[];
folders?: boolean;
documents?: boolean;
};
preventApply?: boolean;
preventRemove?: boolean;
}
// From @yuuvis/client-shell-core — what apps and config define
interface AppFlavor extends ObjectTypeFlavor {
objectTypeID: string;
descriptionKey?: string;
useDefaultApplyComponent?: boolean;
applyComponent?: Type<any>;
}
// Runtime type — created by ShellService after registration
type ObjectFlavor = AppFlavor & {
app: string;
origin: 'app' | 'config';
name?: string;
description?: string;
};
PropertyTypeInterfaceDescription
idstringObjectTypeFlavorUnique identifier for the flavor. Used as the uniqueness key together with app.
sotstringObjectTypeFlavorThe Secondary Object Type ID that is added to the object when the flavor is applied.
iconstringObjectTypeFlavorIcon name or SVG content. Interpreted as a Material icon name when svgIcon is false, or as SVG when true.
svgIconbooleanObjectTypeFlavorIf true, the icon value is treated as an SVG identifier rather than a Material icon name.
applicableTo.mimeTypesstring[]ObjectTypeFlavorMIME type patterns. Supports wildcards (image/*, */*). Absent or empty means all MIME types.
applicableTo.foldersbooleanObjectTypeFlavorWhether this flavor can be applied to folders.
applicableTo.documentsbooleanObjectTypeFlavorWhether this flavor can be applied to documents.
preventApplybooleanObjectTypeFlavorIf true, the flavor is shown as applied but users cannot apply it. Useful for read-only SOT display.
preventRemovebooleanObjectTypeFlavorIf true, the flavor cannot be removed once applied.
objectTypeIDstringAppFlavorThe base object type this flavor targets — typically SystemType.DOCUMENT or SystemType.FOLDER.
useDefaultApplyComponentbooleanAppFlavorOpens the default metadata form overlay on apply. If false and no applyComponent is set, applies instantly.
applyComponentType<any>AppFlavorCustom Angular component opened as a dialog on apply. Must extend AbstractApplyObjectFlavorComponent.
descriptionKeystringAppFlavorTranslation key for the flavor description shown in the picker dialog.
appstringObjectFlavorID of the app that registered the flavor. Set to 'global' for config-registered flavors. Added by ShellService.
origin'app' | 'config'ObjectFlavorSource of the flavor registration. Added by ShellService.
namestringObjectFlavorLocalized display name, populated at runtime from the translation system.
descriptionstringObjectFlavorLocalized description, populated at runtime.

The Shell processes flavors through three distinct phases: registration, applicability filtering, and user interaction. Each phase is driven by specific AppFlavor properties and controlled by ShellService.

ShellService maintains a single global list of all registered flavors. Every flavor — regardless of whether it was registered by an app or loaded from configuration — is stored in this list as an ObjectFlavor object.

Two properties are added automatically by the Shell on registration:

  • app — the ID of the app that registered it (or 'global' for config-registered flavors)
  • origin — either 'app' (registered in code) or 'config' (loaded from the yuv:shell:types DMS object)

Uniqueness is enforced by the combination of id and app. If two different apps each register a flavor with the same id, they coexist independently. If the same app tries to register the same id twice, the second call is silently ignored.

// Two flavors with the same id but from different apps — both coexist:
{ id: 'flavor.email', app: 'io.yuuvis.app.mailarchive', origin: 'app' }
{ id: 'flavor.email', app: 'global', origin: 'config' }
// Same id, same app — second registration is ignored:
{ id: 'flavor.email', app: 'io.yuuvis.app.mailarchive', origin: 'app' }
{ id: 'flavor.email', app: 'io.yuuvis.app.mailarchive', origin: 'app' } ✗ ignored

App-registered flavors (origin: 'app') are added in code by an app’s extension service during its init() call, before the Shell UI renders:

// In an extension service:
this.#shell.exposeObjectFlavor(MY_FLAVOR, 'io.yuuvis.app.myapp');

Config-registered flavors (origin: 'config') are loaded at bootstrap from the yuv:shell:types DMS configuration object. No client code is required — the Shell fetches the configuration, converts each entry to an AppFlavor, and registers it under the 'global' app ID. See Configuring Flavors and Features for setup details.

The Shell does not show all registered flavors for every object. When an object is displayed, the Shell uses the applicableTo property to determine which flavors are relevant:

Base typeapplicableTo.folders and applicableTo.documents control which base object types the flavor targets.

// Only appears on folders:
applicableTo: { folders: true, documents: false }
// Only appears on documents:
applicableTo: { folders: false, documents: true }
// Appears on both:
applicableTo: { folders: true, documents: true }

MIME type — For document flavors, applicableTo.mimeTypes is a list of patterns. The object’s actual MIME type must match at least one. Wildcards are supported. If the list is absent or empty, the flavor applies to all documents regardless of file type.

// Only for RFC 822 email files (.eml):
applicableTo: { documents: true, mimeTypes: ['message/rfc822'] }
// For any image file:
applicableTo: { documents: true, mimeTypes: ['image/*'] }
// For all documents regardless of file type (omit mimeTypes or use */*):
applicableTo: { documents: true }

MIME type is only checked for documents. A flavor with folders: true and mimeTypes: ['image/*'] appears on all folders and on image documents only.

When a user selects a flavor from the picker, the Shell uses the useDefaultApplyComponent and applyComponent properties to decide what happens next:

ConditionBehavior
applyComponent is setOpens a dialog with that custom Angular component
useDefaultApplyComponent: trueOpens the built-in overlay: object preview on the left, SOT metadata form on the right
NeitherApplies the SOT to the object immediately — no dialog shown
// Opens metadata form before applying — user fills in SOT fields first:
{ sot: 'appExample:invoice', useDefaultApplyComponent: true }
// Applies instantly — no user interaction required:
{ sot: 'appExample:reviewed', useDefaultApplyComponent: false }
// Opens a fully custom dialog component:
{ sot: 'appExample:email', applyComponent: EmailApplyComponent }

The instantApply flag in the yuv:shell:types configuration file maps to this directly: instantApply: true sets useDefaultApplyComponent: false, producing an instant apply with no dialog.

Apps register flavors through ShellService during extension initialization. Flavors must be registered before the Shell UI renders — the init() method of an extension service is the right place.

extensions.service.ts
import { inject, Injectable } from '@angular/core';
import { AppFlavor, ClientShellExtension, ShellService } from '@yuuvis/client-shell-core';
import { SystemType } from '@yuuvis/client-core';
const APP_ID = 'io.yuuvis.app.mailarchive';
const APP_FLAVORS: AppFlavor[] = [
{
id: 'io.yuuvis.app.mailarchive.flavor.email',
objectTypeID: SystemType.DOCUMENT,
sot: 'appSystemmail:email',
icon: 'email_svg_content',
svgIcon: true,
applicableTo: {
documents: true,
mimeTypes: ['message/rfc822', 'application/vnd.ms-outlook']
},
useDefaultApplyComponent: true
}
];
@Injectable()
export class AppMailArchiveExtension implements ClientShellExtension {
#shell = inject(ShellService);
init(): Promise<any> {
const appTitle = this.#shell.getApp(APP_ID)?.title ?? APP_ID;
this.#shell.exposeObjectFlavors(APP_FLAVORS, appTitle);
return Promise.resolve();
}
}

Registration methods on ShellService:

MethodDescription
exposeObjectFlavor(flavor, app?, origin?)Registers a single flavor. app defaults to 'global'; origin defaults to 'app'.
exposeObjectFlavors(flavors[], app?, origin?)Registers multiple flavors in one call.
concealObjectFlavor(flavor, app?)Removes a single flavor from the registry.
concealObjectFlavors(flavors[], app?)Removes multiple flavors from the registry.

ShellService provides methods to retrieve flavors from the registry, with optional filtering by app or object context.

MethodReturnsDescription
getObjectFlavors(app?)ObjectFlavor[]All registered flavors, optionally scoped to one app.
getApplicableDocumentFlavors(mimeType, app?, customFlavors?)ObjectFlavor[]Flavors applicable to a document with the given MIME type. Excludes folder-only flavors and evaluates MIME patterns.
getApplicableFolderFlavors(app?, customFlavors?)ObjectFlavor[]Flavors applicable to folders. Excludes flavors with preventApply: true.
getAppliedObjectFlavors(dmsObject){ applied: ObjectFlavor[], applicable: ObjectFlavor[] }For a specific object: which registered flavors are already applied (SOT present) and which can still be applied.

The customFlavors parameter on the document and folder methods allows passing additional flavors that are managed by an app internally without being exposed globally to the registry.

ShellService exposes dedicated methods for applying and removing flavors. Each method enforces the preventApply and preventRemove constraints defined on the flavor before making any changes to the DMS object.

triggerApplyObjectFlavor() is the main entry point for user-initiated flavor application. It handles the dialog/instant split automatically:

import { inject } from '@angular/core';
import { ShellService } from '@yuuvis/client-shell-core';
export class MyComponent {
#shell = inject(ShellService);
applyFlavor(dmsObject: DmsObject, flavor: ObjectFlavor) {
this.#shell
.triggerApplyObjectFlavor(dmsObject, flavor)
.subscribe(success => {
if (success) {
// flavor was applied or dialog was opened
}
});
}
}
  • If flavor.preventApply is true, returns false immediately
  • If an applyComponent is set (or useDefaultApplyComponent is true), opens the dialog component
  • Otherwise applies the flavor directly by updating the DMS object

applyObjectFlavor() applies the flavor to the object and updates the DMS record immediately, with no dialog. It is typically called from within an apply component after the user has filled in the metadata form:

this.#shell.applyObjectFlavor(dmsObject, flavor, formData).subscribe();

The method adds flavor.sot to the object’s SECONDARY_OBJECT_TYPE_IDS array, merging any additional formData into the update.

this.#shell.removeObjectFlavor(dmsObject, flavor).subscribe();

If flavor.preventRemove is true, the call returns false without making any changes. Otherwise, the SOT is removed from the object’s SECONDARY_OBJECT_TYPE_IDS and the DMS record is updated.

When the default metadata form overlay is not suitable, a flavor can define its own apply component. This is useful when applying the flavor requires more complex user interaction — for example, mapping file metadata to SOT fields automatically, or showing a multi-step wizard.

Custom apply components extend AbstractApplyObjectFlavorComponent from @yuuvis/client-framework:

my-apply-flavor.component.ts
import { Component, inject } from '@angular/core';
import {
AbstractApplyObjectFlavorComponent
} from '@yuuvis/client-framework';
import { ShellService } from '@yuuvis/client-shell-core';
@Component({
selector: 'app-my-apply-flavor',
standalone: true,
template: `
<h2>Apply {{ flavor().id }}</h2>
<!-- custom form here -->
<button (click)="apply()">Apply</button>
<button (click)="dialogRef.close()">Cancel</button>
`
})
export class MyApplyFlavorComponent extends AbstractApplyObjectFlavorComponent {
#shell = inject(ShellService);
apply() {
const data = { /* collect form values */ };
this.#shell
.applyObjectFlavor(this.dmsObject(), this.flavor(), data)
.subscribe(() => this.dialogRef.close());
}
}

The base class provides:

  • dmsObject() — the object to apply the flavor to
  • flavor() — the flavor being applied
  • data() — optional contextual data passed by the caller
  • dialogRef — the MatDialogRef for closing the dialog

Register the component on the flavor definition:

const MY_FLAVOR: AppFlavor = {
id: 'com.example.flavor.custom',
sot: 'appExample:customType',
objectTypeID: SystemType.DOCUMENT,
applyComponent: MyApplyFlavorComponent,
// ...
};

The following components from @yuuvis/client-framework render flavors in the Shell UI. They consume the ShellService flavor API and can be reused in custom views.

ComponentSelectorDescription
ObjectFlavorComponentyuv-object-flavorDisplays applied flavors as chips and an “Add” button for applicable ones. Used in the object sidebar.
ObjectFlavorPickerComponent(dialog)Modal dialog listing all applicable flavors for the current object. Opens when the user clicks “Add”.
DefaultApplyFlavorComponent(dialog)Default apply overlay: object preview on the left, SOT metadata form on the right.
FlavorChipComponentyuv-flavor-chipRenders a single flavor as a chip with icon, name, and optional remove button.

Use ObjectFlavorComponent in custom object detail views by passing the current DMS object:

<yuv-object-flavor [dmsObject]="myObject" />

The component determines applicable and applied flavors automatically and handles the full apply/remove interaction.