Plugins built for this runtime use Module Federation as build and package tool.
Support Plugins developed for VCFA 9.1 +
UI Plugin in the context of VCFA is a small Angular application.
Every UI Plugin is built using Webpack Module Federation.
Every UI Plugin can use SDK to communicate with VCFA https://www.npmjs.com/package/@vcfa/sdk.
The SDK will be described by a Quick Starter Project which works out of the box and you can tweak it to your use case.
VCFA defines a list of extension point types (regular strings) that plugin authors can use to choose where to render their UI Plugin. The list of extension points and how to use them is described in Extension Points.
Quick Start project is a small Angular based application that can run as a standalone application and also be built as a VCFA plugin. Follow the steps below to get started.
This document assumes you have a Node Version Manager (nvm) installed.
The Quick Start project can be cloned from GitHub.
git clone https://github.com/vmware/vcf-services.git --branch quick-start-simple ./quick-start-simple
Quick Start project has build configuration for VCFA Plugin.
cd packages/vcfa-plugin
nvm use 22 # NodeJS v22.x.x
npm i
npm run build:plugin
The Quick Start can be started locally with the following script.
cd packages/vcfa-plugin
nvm use 22 # NodeJS v22.x.x
npm run start
Quick Start project uses default Angular unit test setup.
cd packages/vcfa-plugin
nvm use 22
npm run test
Quick Start packages as *.tar format to bundle all its elements and prepare them for installation in VCFA.
# In project root folder
nvm use 22
sh ./scripts/package.sh # See output in ./packages/vcf-service/dist
Open VCFA Tenant Manager -> Service Management -> Click Upload

Select the tar file ./packages/vcf-service/dist/quick-start.tar.gz -> Click -> Upload -> Install
VCF Service will be available in VCFA Tenant Manager -> Overview

Refresh the page to see Quick Start Plugin page

The goal of this guide is to expand the Quick Start project with more description how things work and how you can add new functionality.
The manifest.json file is located in the packages/vcfa-plugin/projects/ui-plugin/public/ directory within the quick-start project. This is where you declare your extension points.
Reference: ExtensionManifest.
Extension points are the primary way a plugin adds functionality to the VCFA UI. They define new navigation items and views. The extensionPoints array in manifest.json contains a list of these integration points.
Below is an explanation of the common properties found in each extension point object, followed by a breakdown of the specific extension points defined in this manifest.
Here are the specific extension points defined in manifest.json:
This extension adds a main navigation link to the Partner Services page.
This extension adds a navigation link under the “Manage & Govern” section.
Localization in the manifest.json file is handled through a key-value pairing system that allows the plugin menu items to display text in different languages based on the user’s locale. Localization done in the plugin itself is handled by the plugin developer by using any suitable library for localization.
The localization mechanism is defined within the “locales” object in the manifest.json. This object contains one or more sub-objects, each keyed by a language code (e.g., “en” for English).
"locales": {
"en": {
"example.localization.primary.navigation": "Partner Services Extension Point",
"example.localization.manage.govern": "Manage & Govern Extension Point",
...
}
}
Each language object holds a set of key-value pairs:
Elsewhere in the manifest.json, typically for user-facing text such as name and description in extensionPoints, these localization keys are used as placeholders. The keys are wrapped in percent signs (%).
For example:
"extensionPoints": [
{
"urn": "urn:vcfa:plugin:quick-start:primary-navigation",
"type": "navigation:primary",
"name": "%example.localization.primary.navigation%",
"description": "%example.localization.description%",
...
}
]
When the host application loads the plugin, it reads the user’s current locale. It then looks for a matching language code inside the “locales” object.
For any value in the manifest that is formatted like %key%, the application replaces it with the corresponding string from the locale object.
For instance, if the user’s language is English, “%example.localization.primary.navigation%” will be replaced with “Partner Services Extension Point”. If other languages were defined (e.g., “fr”, “es”), the corresponding translated string from that language’s object would be used.
This approach allows developers to support multiple languages without changing the core structure of the manifest, simply by adding new language objects to the “locales” section.
The following list of locales are supported:
Plugin assets are stored in the packages/vcfa-plugin/projects/ui-plugin/public/assets/ directory.
The manifest.json file, located at packages/vcfa-plugin/projects/ui-plugin/public/manifest.json, references these assets using paths relative to assets folder location. For example:
{
"iconShapeSrc": "custom-icons-folder/my-custom-icon.svg"
}
Files in this directory are packaged with the plugin during the build process.
To access plugin assets at runtime, use the EXTENSION_ASSET_URL injection token from the @vcfa/sdk. This token provides the base URL for your plugin’s assets.
You can inject it into your component’s constructor. It’s recommended to use the @Optional() decorator in case the token is not available in all environments (e.g., during development with ng serve).
First let’s install the Public SDK.
nvm use 22
npm i @vcfa/sdk@latest @vcfa/container-hooks@latest -S -E
Here is an example of how to use the EXTENSION_ASSET_URL token in a packages/vcfa-plugin/projects/ui-plugin/src/plugin/home/home.component.ts:
import { Component, Inject, Optional } from "@angular/core";
import { EXTENSION_ASSET_URL } from "@vcfa/sdk";
@Component({
selector: 'home',
template: `<img [src]="assetUrl + '/welcome.svg'">`
})
export class HomeComponent {
assetUrl = '/assets'; // Default fallback
constructor(
@Optional() @Inject(EXTENSION_ASSET_URL) public pluginAssetUrl: string
) {
if (this.pluginAssetUrl) {
this.assetUrl = this.pluginAssetUrl;
}
}
}
In this example:
From one Angular project you can build multiple plugins and in one plugin you can have many different extension points.
The current plugin setup uses a single entry point for module federation, defined in packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.plugin.ts.
export { SubnavComponent } from './plugin/subnav.component';
export { routes } from './plugin/plugin.routes';
This file exports the SubnavComponent and the plugin-specific routes. This file is then exposed as a federated module through the webpack configuration.
In packages/vcfa-plugin/projects/ui-plugin/webpack.config.js, you can see how bootstrap.plugin.ts file is exposed:
// ...
name: 'ui-plugin',
exposes: {
'./Navigation': './projects/ui-plugin/src/bootstrap.plugin.ts',
},
// ...
The key ./Navigation is the alias for the module. VCFA UI will use this alias to load the module. The name used in the manifest is Navigation.
The public/manifest.json file defines the extension points that the plugin provides. It links an extension point to the exposed module and a component within it.
// ...
"extensionPoints": [
{
"urn": "urn:vcfa:plugin:quick-start:primary-navigation",
"type": "navigation:primary",
// ...
"exposes": "Navigation",
"component": "SubnavComponent",
// ...
},
// ...
Here, “exposes”: “Navigation” tells the host to load the module exposed as Navigation. “component”: “SubnavComponent” tells the host which component to render from that module. SubnavComponent is available because it’s exported from bootstrap.plugin.ts.
You are not limited to a single bootstrap file. You can have as many as you need, which is useful for organizing your code and exposing different sets of functionalities as separate modules. Each bootstrap file can export a different combination of components, routes, and providers.
Let’s say you want to expose a new feature. You can create a new file, for example, packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.dashboard.ts:
// packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.dashboard.ts
import { EnvironmentProviders } from '@angular/core';
import { DashboardComponent } from './app/dashboard/dashboard.component';
// Expose a component
export { DashboardComponent } from './app/dashboard/dashboard.component';
// Expose providers
export const providers: EnvironmentProviders[] = [
// provide services here
];
This file exports a new component, routes, and a set of EnvironmentProviders.
You may need to add this file in your tsconfig.app.json so it’s part of the compilation process
// tsconfig.app.json
...
"files": [
...,
"src/bootstrap.dashboard.ts",
],
...
Next create example Angular component packages/vcfa-plugin/projects/ui-plugin/src/app/dashboard/dashboard.component.ts with the following content:
// packages/vcfa-plugin/projects/ui-plugin/src/app/dashboard/dashboard.component.ts
import { Component } from "@angular/core";
@Component({
selector: 'dashboard',
template: '<h1>Dashboard</h1>',
})
export class DashboardComponent {}
Next, you need to update webpack.config.js to expose this new file as another module.
File: packages/vcfa-plugin/projects/ui-plugin/webpack.config.js
// ...
exposes: {
'./Navigation': './projects/ui-plugin/src/bootstrap.plugin.ts',
'./Dashboard': './projects/ui-plugin/src/bootstrap.dashboard.ts', // Add this line
},
// ...
Now your plugin exposes two modules: Navigation and Dashboard.
Finally, you can create a new extension point in manifest.json that uses the Dashboard module.
File: packages/vcfa-plugin/projects/ui-plugin/public/manifest.json
// ...
"extensionPoints": [
// ... existing extension points
{
"urn": "urn:vcfa:plugin:quick-start:dashboard",
"type": "navigation:primary",
"name": "Dashboard",
"description": "A custom dashboard.",
"exposes": "Dashboard",
"component": "DashboardComponent",
"componentSelector": "dashboard",
"route": "dashboard",
"icon": "assets/plugin.png"
}
],
// ...
This new extension point will load the DashboardComponent from the Dashboard module when the user navigates to it.
By following this pattern, you can create a plugin that provides multiple, independent pieces of functionality, each loaded on demand.
In practice you can use any i18n angular package to localize your user interface and bundle it as VCFA plugin.
Below we demonstrate usage of @ngx-translate for internationalization (i18n) into the VCFA UI plugin but any other suitable NPM package will do the job.
Add the following dependencies to packages/vcfa-plugin/package.json:
"@ngx-translate/core": "17.0.0",
"@ngx-translate/http-loader": "17.0.0"
These packages provide the core translation functionality and a loader for fetching translation files over HTTP.
Apply new packages. This will change your package-lock file.
cd packages/vcfa-plugin
nvm use 22
npm i
Translation files are placed in the packages/vcfa-plugin/projects/ui-plugin/public/assets/i18n/ directory.
Create english translation file en.json with the following content:
{
"subnav.menu.home": "Home",
"home.title": "Welcome to your VCFA Plugin",
"home.description": "The Quick Start Plugin showcases all available extension points in VCFA where you can start building your own plugin."
}
Since plugins in VCFA are loaded with a dynamic asset path, a custom TranslateLoader is required.
DynamicTranslateLoader is in packages/vcfa-plugin/projects/ui-plugin/src/plugin/dynamic-translate-loader.ts. This loader uses the EXTENSION_ASSET_URL provided by the Public SDK to construct the correct path to the translation files.
import { HttpClient } from '@angular/common/http';
import { TranslateLoader } from '@ngx-translate/core';
export class DynamicTranslateLoader implements TranslateLoader {
constructor(
private http: HttpClient,
private prefix: string = './assets/i18n/',
private suffix: string = '.json'
) { }
/**
* Gets the translations for a given language.
* @param lang The language to get the translations for.
* @returns An observable containing the loaded translations.
*/
public getTranslation(lang: string): any {
const fullPath = `${this.prefix}${lang}${this.suffix}`;
return this.http.get(fullPath);
}
}
The TranslateService and its loader are configured in packages/vcfa-plugin/projects/ui-plugin/src/bootstrap.plugin.ts.
provideHttpClient() is added to make HttpClient available for the loader. The TranslateService is configured using provideTranslateService, where the custom DynamicTranslateLoader is provided and instantiated with the correct asset path.
import { provideHttpClient } from '@angular/common/http';
import { provideTranslateService } from '@ngx-translate/core';
import { TranslateLoader } from '@ngx-translate/core';
import { HttpClient } from '@angular/common/http';
import { DynamicTranslateLoader } from './plugin/dynamic-translate-loader';
import { EXTENSION_ASSET_URL } from '@vcfa/sdk';
export { SubnavComponent } from './plugin/subnav.component';
export { routes } from './plugin/plugin.routes';
export const providers = [
provideHttpClient(),
provideTranslateService({
loader: {
provide: TranslateLoader,
useFactory: (http: HttpClient, assetUrl: string) => new DynamicTranslateLoader(http, `${assetUrl}/i18n/`),
deps: [HttpClient, EXTENSION_ASSET_URL],
},
fallbackLang: 'en',
}),
];
The translate pipe is used in component templates to display translated text.
In packages/vcfa-plugin/projects/ui-plugin/src/plugin/home/home.component.ts, the TranslatePipe is imported for use in the standalone component.
import { Component, Inject, Optional } from "@angular/core";
import { TranslatePipe } from "@ngx-translate/core";
import { EXTENSION_ASSET_URL } from "@vcfa/sdk";
@Component({
standalone: true,
selector: 'home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
imports: [
TranslatePipe,
]
})
export class HomeComponent {
assetUrl = '/assets';
constructor(
@Optional() @Inject(EXTENSION_ASSET_URL) public pluginAssetUrl: string,
) {
this.assetUrl = this.pluginAssetUrl || '/assets';
}
}
In packages/vcfa-plugin/projects/ui-plugin/src/plugin/home/home.component.html, the translate pipe is used with the translation keys.
<div class="hand-icon">👋</div>
<h1>home.title</h1>
<p class="welcome-description">home.description</p>
<div class="clr-row clr-justify-content-center clr-align-items-center" cds-layout="m:none">
<div class="welcome-container clr-col-lg-7 clr-col-12">
<img
class="no-search-results-image"
src="/welcome.svg"
alt="Welcome"
/>
</div>
</div>
This section explains how to securely pass a JWT token from the main application to a child iframe. This is useful when you need to embed a separate application or page within your UI plugin and want to authenticate it using the current user’s session.
The process involves using the window.postMessage API to communicate between the parent window (your Angular component) and the iframe.
Any other data provided by the Public SDK can be shared the same way.
The parent component is responsible for hosting the <iframe> and sending the JWT token to it.
Here is an example of a component that handles this.
File: packages/vcfa-plugin/projects/ui-plugin/src/plugin/jwt-iframe/jwt-iframe.component.ts
/*
* ******************************************************************
* Copyright (c) 2025 Broadcom. All Rights Reserved.
* Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc.
* and/or its subsidiaries.
* ******************************************************************
*/
import { CommonModule } from '@angular/common';
import { Component, ElementRef, Inject, OnDestroy, OnInit, AfterViewInit, Optional, ViewChild } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { AuthTokenHolderService, EXTENSION_ASSET_URL } from '@vcfa/sdk';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
imports: [CommonModule],
selector: 'app-jwt-iframe',
templateUrl: './jwt-iframe.component.html',
styleUrls: ['./jwt-iframe.component.scss'],
})
export class JwtIframeComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('iframe', { static: true }) iframe!: ElementRef;
private destroy$ = new Subject<void>();
iframeSrc: SafeResourceUrl = '/iframe-content.html';
constructor(
@Optional() @Inject(EXTENSION_ASSET_URL) public pluginAssetUrl: string,
@Optional() @Inject(AuthTokenHolderService) private authTokenHolderService: AuthTokenHolderService,
private sanitizer: DomSanitizer,
) {}
ngOnInit(): void {
this.iframeSrc = this.sanitizer.bypassSecurityTrustResourceUrl(`${this.pluginAssetUrl ? this.pluginAssetUrl : '/assets'}/iframe-content.html`);
}
ngAfterViewInit(): void {
this.initTokenListener();
window.addEventListener('message', this.handleIframeMessage.bind(this));
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
window.removeEventListener('message', this.handleIframeMessage.bind(this));
}
private handleIframeMessage(event: MessageEvent): void {
if (event.data && event.data.status === 'ready') {
this.initTokenListener();
}
}
private initTokenListener(): void {
if (this.authTokenHolderService) {
// Send the initial token
this.sendToken(this.authTokenHolderService.jwt);
// Subscribe to token changes
if (this.authTokenHolderService.jwtAsync) {
this.authTokenHolderService.jwtAsync
.pipe(takeUntil(this.destroy$))
.subscribe((token: string | undefined) => {
this.sendToken(token);
});
}
} else {
this.sendToken(undefined);
}
}
private sendToken(token: string | undefined): void {
if (this.iframe && this.iframe.nativeElement.contentWindow) {
this.iframe.nativeElement.contentWindow.postMessage(token, '*');
}
}
}
Key points:
The template is simple and just contains the iframe element.
File: packages/vcfa-plugin/projects/ui-plugin/src/plugin/jwt-iframe/jwt-iframe.component.html
<div class="container">
<iframe #iframe [src]="iframeSrc" width="100%" height="500px"></iframe>
</div>
File: packages/vcfa-plugin/projects/ui-plugin/src/plugin/jwt-iframe/jwt-iframe.component.scss
.iframe-container {
padding: 1rem;
}
iframe {
border: 1px solid #ccc;
}
The page loaded inside the iframe needs to listen for the message event to receive the token.
File: packages/vcfa-plugin/projects/ui-plugin/public/assets/iframe-content.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Iframe Content</title>
<style>
body { font-family: sans-serif; padding: 1rem; }
#token-container {
word-wrap: break-word;
border: 1px solid #eee;
padding: 1rem;
background-color: #f9f9f9;
}
</style>
</head>
<body>
<h1>Iframe Content Page</h1>
<p>This page is designed to receive a JWT from a parent window via <code>postMessage</code>.</p>
<h2>Received JWT:</h2>
<pre id="token-container">Waiting for token...</pre>
<script>
// Listen for messages from the parent window
window.addEventListener('message', (event) => {
// A security best practice is to check event.origin to ensure messages are from a trusted source.
// For this example, we'll accept messages from any origin.
console.log('Message received in iframe:', event.data);
const tokenContainer = document.getElementById('token-container');
if (tokenContainer) {
tokenContainer.textContent = event.data ? JSON.stringify(event.data, null, 2) : 'Token is null or empty';
}
});
// Notify the parent window that the iframe is ready to receive messages.
window.addEventListener('load', () => {
console.log('Iframe is loaded and ready. Notifying parent.');
window.parent.postMessage({ status: 'ready' }, '*');
});
</script>
</body>
</html>
Key points:
Finally, you need to add the component to your application’s routing and provide a way to navigate to it.
File: packages/vcfa-plugin/projects/ui-plugin/src/plugin/plugin.routes.ts
import { Routes } from "@angular/router";
import { HomeComponent } from "./home/home.component";
import { JwtIframeComponent } from "./jwt-iframe/jwt-iframe.component";
export const routes: Routes = [
{ path: "", redirectTo: "home", pathMatch: "full" },
{ path: "home", component: HomeComponent },
{ path: "access-token", component: JwtIframeComponent },
];
You can add a link to your new component in a navigation menu.
File: packages/vcfa-plugin/projects/ui-plugin/src/plugin/subnav.component.ts
// ...
export class SubnavComponent {
navItems: any[] = [
{ routerLink: "./home", iconShape: "home", labelKey: "Home" },
{ routerLink: "./access-token", iconShape: "cog", labelKey: "Access Token" },
];
}
This completes the setup for passing a JWT token to an iframe in a secure and reliable way.
nvm use 22
cd packages/vcfa-plugin
npm run build:plugin # This will build React and Angular together
cd ../..
sh scripts/package.sh # Packages everything in VCF Service
Open VCFA Tenant Manager -> Overview -> Click Upload

Select the tar file ./packages/vcf-service/dist/quick-start.tar.gz -> Click -> Upload -> Install

Refresh the page to see Quick Start Plugin page

This document provides documentation for all exposed injectables in the container-hooks library, which serves as part of the public SDK for VCFA UI plugin development.
The container-hooks library provides a minimal but essential public SDK for developing plugins within the VCFA-UI ecosystem. It exposes injection tokens for accessing runtime context information and authentication services.
Type: InjectionToken<string>
Description: Provides the root URL for API access, such as the load balancer URL or single-cell URL.
Usage:
import { API_ROOT_URL } from '@vcfa/sdk';
constructor(@Inject(API_ROOT_URL) private apiRootUrl: string) {
// Use apiRootUrl for making API calls
}
Example Value: "https://vcfa.example.com/api"
Type: InjectionToken<string>
Description: Indicates the current scope of the VCFA-UI session.
Possible Values:
"tenant" - For the tenant portal"service-provider" - For the service-provider portalUsage:
import { SESSION_SCOPE } from '@vcfa/sdk';
constructor(@Inject(SESSION_SCOPE) private sessionScope: string) {
if (this.sessionScope === 'tenant') {
// Tenant-specific logic
} else if (this.sessionScope === 'service-provider') {
// Service provider-specific logic
}
}
Type: InjectionToken<string>
Description: Provides the unique name (not display name) of the current tenant organization.
Usage:
import { SESSION_ORGANIZATION } from '@vcfa/sdk';
constructor(@Inject(SESSION_ORGANIZATION) private orgName: string) {
// Use orgName for organization-specific operations
}
Example Value: "acme-corp"
Type: InjectionToken<string>
Description: Provides the UUID identifier of the current tenant organization.
Usage:
import { SESSION_ORG_ID } from '@vcfa/sdk';
constructor(@Inject(SESSION_ORG_ID) private orgId: string) {
// Use orgId for API calls requiring organization UUID
}
Example Value: "550e8400-e29b-41d4-a716-446655440000"
Type: InjectionToken<string>
Description: Provides the full root path for accessing module assets such as images, scripts, and text files.
Usage:
import { EXTENSION_ASSET_URL } from '@vcfa/sdk';
constructor(@Inject(EXTENSION_ASSET_URL) private assetUrl: string) {
const imageUrl = `${this.assetUrl}/images/logo.png`;
}
Example Value: "/plugins/my-plugin/assets"
Type: InjectionToken<string>
Description: Provides the Angular route that the module is registered under.
Usage:
import { EXTENSION_ROUTE } from '@vcfa/sdk';
constructor(@Inject(EXTENSION_ROUTE) private extensionRoute: string) {
// Use for navigation or route-based logic
}
Example Value: "/plugins/my-plugin"
Type: Abstract Service Class
Description: Injectable service class that manages authentication data including JWT tokens, user context, and authorization headers.
string (getter)Observable<string> (getter)string (getter)"System" for provider portal)string (getter)voidimport { AuthTokenHolderService } from '@vcfa/sdk';
@Injectable()
export class MyService {
constructor(private authService: AuthTokenHolderService) {}
makeAuthenticatedRequest() {
const headers = {
'Authorization': `Bearer ${this.authService.jwt}`
};
// Subscribe to JWT changes for token refresh scenarios
this.authService.jwtAsync.subscribe(jwt => {
// Handle JWT token updates
});
}
}
Interface containing complete authentication token information.
Properties:
jwt: string - JWT tokenauthContext: string - Authorization context (e.g., ‘System’ for provider portal)username: string - Currently logged-in userWhen using these injectables in your plugin, ensure proper dependency injection setup:
import {
API_ROOT_URL,
SESSION_SCOPE,
SESSION_ORGANIZATION,
SESSION_ORG_ID,
EXTENSION_ASSET_URL,
EXTENSION_ROUTE,
AuthTokenHolderService
} from '@vcfa/sdk';
@Component({
// component configuration
})
export class MyPluginComponent {
constructor(
@Inject(API_ROOT_URL) private apiRootUrl: string,
@Inject(SESSION_SCOPE) private sessionScope: string,
@Inject(SESSION_ORGANIZATION) private orgName: string,
@Inject(SESSION_ORG_ID) private orgId: string,
@Inject(EXTENSION_ASSET_URL) private assetUrl: string,
@Inject(EXTENSION_ROUTE) private extensionRoute: string,
private authService: AuthTokenHolderService
) {}
}
jwtAsync for scenarios where token refresh might occurSESSION_SCOPE to implement different behaviors for tenant vs. service-provider contextsFollowing Clarity documentation
import '@cds/core/icon/register.js';
import { ClarityIcons, userIcon } from '@cds/core/icon';
ClarityIcons.addIcons(userIcon);
…anywhere in your code.
You can use VcdApiClient, authentication will be handled automatically (you don’t need to authenticate), links from Tenant Manager responses will also be parsed for you. Links will be available in the response body.
export class ExampleComponent {
constructor(
private client: VcdApiClient,
) {
this.client.get("/endpoint").subscribe((response) => {
});
}
}
You can also use Angular’s HttpClient but you need to obtain the jwt token from @vcfa/sdk see AuthTokenHolderService
Anywhere in your UI Plugin code
import { ClarityIcons} from '@cds/core/icon';
ClarityIcons.addIcons(['my-custom-icon', '<svg ... >[your SVG code goes here]</svg>']);
…then in your html template
<cds-icon shape="my-custom-shape"></cds-icon>
Most of the Extension Points supported have a property for iconShape, you may want to have a custom icon, one crafted by you.
Create packages/uiPlugin/src/public/assets/custom-icons folder and place your custom icon SVG files in that folder. (You can have as many custom icon folders and files you want)
Let’s say your svg file is my-custom-icon.svg (bell shape).
<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M32.66 27.9478C31.69 27.0976 30.84 26.1273 30.13 25.067C29.36 23.5965 28.9 21.986 28.77 20.3255V15.174C28.78 9.80236 24.79 5.27099 19.34 4.59079V3.32041C19.34 2.59018 18.74 2.01001 18 2.01001C17.26 2.01001 16.66 2.60019 16.66 3.32041V4.61079C11.27 5.34101 7.18 9.85238 7.19 15.174V20.3255C7.06 21.976 6.6 23.5865 5.83 25.067C5.14 26.1373 4.3 27.1076 3.34 27.9478C3.12 28.1379 3 28.408 3 28.6881V30.0285C3 30.5686 3.45 31.0088 4.01 31.0088H32C32.27 31.0088 32.52 30.9087 32.71 30.7187C32.9 30.5386 33 30.2785 33 30.0185V28.6781C33 28.398 32.87 28.1279 32.66 27.9378V27.9478ZM5.1 29.0382C6.04 28.1579 6.86 27.1576 7.56 26.0873C8.53 24.3067 9.1 22.3461 9.22 20.3255V15.174C9.11 12.033 10.76 9.08214 13.53 7.48166C16.4 5.82116 19.84 5.9412 22.5 7.48166C25.16 9.02213 26.92 12.033 26.81 15.174V20.3255C26.93 22.3361 27.5 24.3067 28.47 26.0873C29.17 27.1676 29.99 28.1579 30.93 29.0382H5.09H5.1Z"></path><path d="M15.41 32.0091C15.71 33.1794 16.79 34.0297 18.05 34.0097C19.27 33.9797 20.3 33.1494 20.59 32.0091H15.41Z"></path></svg>
Now in manifest.json you want to add a Manage & Govern Extension Point that uses this “my-custom-icon”.
{
...,
"type": "navigation:manage:govern",
"name": "%LOCALIZABLE.STRING%",
...,
"iconShape": "my-custom-icon",
"iconShapeSrc": "custom-icons-folder/my-custom-icon.svg"
},
Build your UI Plugin, in VCFA UI you should see the menu item of your extension point and your custom icon.
This section provides description for the UI extensibility building blocks.
The contents of a third party extension’s manifest.json file.
| Property | Type | Description |
|---|---|---|
| manifestVersion | string | The version of the manifest. |
| urn | string | Unique URN used as ID for the extension. |
| name | string | Human readable name for the extension. |
| containerVersion | string | The minimum supported version of VCFA UI. |
| productVersions | string[] | Versions of VCFA that the plugin claims compatibility with. |
| version | string | The version of the extension. |
| scope | ExtensionScope[] | Scopes that the extension may be used under (e.g. tenant, service-provider). |
| permissions | string[] | Minimum permissions required for the extension to be loaded. |
| description | string | Human readable description for the extension. |
| vendor | string | Human readable vendor name for the extension. |
| license | string | Human readable license information for the extension. |
| link | string | Support URL for the extension. |
| extensionPoints? | ExtensionPointManifest[] | Formal extension points. |
| locales | unknown | Extension locales for the manifest. Supported by manifest v2.0.0 and higher. |
Supported extension scopes.
export type ExtensionScope = 'tenant' | 'service-provider';
This defines a formal extension point. An extension point is a declarative way for an extension manifest to describe how the VCFA application behaviour is extended or modified by the extension.
| Property | Type | Description |
|---|---|---|
| urn | string | Universally unique URN that identifies this Extension Point. It is suggested to prepend the extension’s URN. |
| type | string | The type of Extension Point being defined from a supported list. This list will increase over time. |
| name | string | The name of the Extension Point, intended for display in extension management interfaces. |
| description | string | An overview of the Extension Point, intended for display in extension management interfaces. |
| component? | string | The symbol to be imported as an Angular component from the extension’s Module Federation bundle. The name of the standalone component which will be registered in a route or just rendered in the UI. |
| exposes? | string | The symbol to be imported as an Module Federation Component from the extension’s Module Federation bundle. The name of the exposed MF Component which exports all Angular components, modules, services, etc that will be bootstrapped in specified Extension Points. |
| remoteFilename? | string | The filename of the JavaScript module that contains the exposes symbol. Usually this file name is remoteEntry.js, but it could be something else in some cases. (default: remoteEntry.js) |
| route? | string | The route on which only top level extension points will be registered. |
| componentSelector? | string | Angular component selector of the root plugin Component. |
The below table shows all supported Angular versions per VCFA release.
| Angular 19 | |
|---|---|
| VCFA 9.1 | ✅ 19.2.0 - 19.2.18 |
The supported range can be seen in the peerDependencies of @vcfa/sdk NPM package.