Plugins

Starting with CIB seven 2.3, the CIB seven webclient can be extended with frontend plugins: self-contained pieces of user interface that an operator installs alongside the webclient and that render inside it, next to the built-in views. A plugin can show data from your own systems, add a report, or bring a view of your own into the process instance a user is looking at, without forking or patching the webclient.

A plugin is a jar on the webclient’s classpath. Installing one is a deployment step, not a user action, and plugins are switched off by default.

The mechanism is part of the Community Edition, and the Enterprise Edition renders the same slots: one plugin build runs in both, and nothing in a plugin has to know which edition it was installed into.

Plugins replace the plugin concept of the legacy web applications (Cockpit, Tasklist, Admin and Welcome), which are deprecated and will be removed, see Web Apps and Support Considerations. The two mechanisms are unrelated: a plugin written for those applications cannot be installed into the CIB seven webclient, and a webclient plugin needs no server-side code.

Enabling plugins

Plugins are disabled by default. Set the following property in the application.yaml configuration file — in CIB seven Run, configuration/default.yml — and restart the webclient:

cibseven:
  webclient:
    plugins:
      enabled: true

With plugins disabled there is no discovery, no plugin endpoint, and nothing to reach — see cibseven.webclient.plugins.enabled.

Installing a plugin

A plugin ships as a jar containing one folder per plugin below META-INF/cibseven-plugins/:

my-plugin.jar
└── META-INF/
    └── cibseven-plugins/
        └── demo-report/          (1)
            ├── plugin.json       (2)
            ├── index.js          (3)
            ├── styles.css
            └── translations_en.json
  1. The folder name is the plugin id. It appears in URLs, so it may contain letters, digits, ., - and _ only, and must start with a letter or a digit.
  2. The manifest, see below.
  3. The files the manifest refers to, all relative to this folder.

Where the jar goes depends on how the webclient is deployed:

Distribution Location
CIB seven Run configuration/userlib/
War in an application server WEB-INF/lib/ of the webclient war
Spring Boot application anywhere on the classpath, e.g. BOOT-INF/lib/ or a Maven/Gradle dependency

The classpath is scanned once, while the webclient starts, and the result is logged — this is where an operator checks that a jar was picked up:

INFO o.cibseven.webapp.plugin.PluginRegistry : Found 1 frontend plugin(s) on the classpath: [demo-report]

Adding or removing a plugin, or changing its plugin.json, therefore requires a restart of the webclient followed by a page reload. Editing the files of an already deployed plugin only requires the reload: plugin files are served without caching.

The manifest

Every plugin folder contains a plugin.json:

{
  "entry": "index.js",
  "apiVersion": ["2.3"],
  "slots": ["process-instance-tab"],
  "styles": ["styles.css"],
  "translations": { "en": "translations_en.json", "de": "translations_de.json" }
}
Field Meaning
entry Required. The module the webclient imports, relative to the plugin folder
apiVersion Required. The webclient lines this build was tested against, each as major.minor. One of them has to be the webclient’s own line, otherwise the plugin is refused. A single string is accepted as well
slots Documentation only. What is rendered is decided by the plugin’s registerPlugin calls
styles Stylesheets the plugin ships. They are added to the page before the plugin registers anything
translations One file per language, merged under plugins.<plugin-id>.*

The plugin id is not part of the manifest: it is the folder name, and the only source of it. A manifest without an entry, or one that names none of the webclient’s lines, is rejected with a log entry and the plugin is skipped — the rest of the webclient is unaffected.

Writing a plugin

A plugin is a separately built ES module that exports a register function. It is called once while the webclient starts, with the plugin’s id, its base URL, and a registerPlugin bound to it:

import DemoReport from './DemoReport.vue'

export function register({ id, registerPlugin }) {
  registerPlugin('process-instance-tab', DemoReport, {
    id: 'demo-report',                // becomes ?tab=demo-report
    text: `plugins.${id}.title`       // translation key
  })
}

Slots

A slot is a place in the webclient a plugin can contribute to. The registered component receives the props of that slot:

Slot Contributes Props
process-instance-tab One tab of a process instance instance, process, tenantId
decision-definition-tab One tab of a decision definition version decision, tenantId

Any number of plugins can contribute to one slot, and one plugin may register several times. Contributed tabs are appended after the built-in ones, in an order that is not defined. Registered ids have to be unique across all plugins and may not shadow a built-in tab id; a contribution taking an id that is already in use is dropped with a console warning. The Enterprise Edition has more built-in tabs than the Community Edition, so prefixing the id with the plugin name is the simplest way to keep one build working in both.

What the webclient provides

The plugin’s components import what they need from @cibseven/plugin-runtime, which hands over the webclient’s own instances:

Export What it is
vue, and the bare vue exports The webclient’s Vue runtime
services The webclient service objects (ProcessService, TaskService, …)
axios The configured instance, carrying the user’s authentication
getContext() config, a frozen copy of how the webclient is configured
navigation push, replace and currentRoute(), to send the user to another view
registerPlugin, getPlugin The slot registry — prefer the registerPlugin handed to register, which knows the plugin’s id
i18n, mergeTranslations Translations, namespaced per plugin
PLUGIN_API_VERSION The webclient line the plugin is running on

Heads-up!

vue, axios, bootstrap and @cibseven/plugin-runtime must never be bundled into the plugin — mark them as external in the plugin’s build. A bundled Vue gives the plugin a second Vue runtime, across which reactivity, provide/inject and the slot registry silently stop working. Anything else the plugin needs is its own dependency and is bundled normally.

Building and packaging

Any bundler does, as long as it externalizes the four names above and writes the plugin folder layout. With Vite, the build can write straight into the layout a jar needs:

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  publicDir: 'public',                 // plugin.json and the translation files
  build: {
    outDir: 'dist/META-INF/cibseven-plugins/demo-report',
    emptyOutDir: true,
    lib: {
      entry: 'src/index.js',
      formats: ['es'],
      fileName: () => 'index.js'       // the manifest's "entry"
    },
    rollupOptions: {
      external: ['vue', 'axios', 'bootstrap', '@cibseven/plugin-runtime'],
      output: {
        assetFileNames: 'styles.css'   // the manifest's "styles"
      }
    }
  }
})

Packaging is then one command over the build output:

jar --create --file demo-report-plugin.jar -C dist META-INF

A Maven or Gradle build that copies the same files to the same path inside a jar does just as well. Ship one jar per plugin; its name and version are yours to choose, since only the folder name identifies the plugin — and that name has to be unique across everything on the classpath.

A .vue file is never deployed: the browser cannot parse it, so what ships is always the built output.

Translations and styles

Messages from the files listed under translations are merged under plugins.<plugin-id>, so a component uses $t('plugins.demo-report.title') and follows a language switch like any other label. A language the manifest does not list falls back to the key.

Stylesheets listed under styles are added to the page before the plugin registers anything. Nothing scopes them, so prefix your selectors with something belonging to the plugin rather than styling shared elements.

Compatibility

PLUGIN_API_VERSION is the contract between a plugin and the webclient: the webclient’s own line, as major.minor. A patch release changes nothing a plugin binds to and therefore never invalidates a published plugin.

A plugin lists the lines it was built and tested against, so one published build can serve several webclient versions:

"apiVersion": ["2.2", "2.3"]

What this page describes — the runtime exports, the slots and their props, and the manifest fields — does not change incompatibly within a line. A plugin should be retested when the minor version rises, and can then be published for both lines at once.

What a plugin can do

Plugin code is not sandboxed. It is imported into the webclient page and has everything the webclient has: the session, the DOM, the configured axios instance and the services. A plugin can do anything the logged-in user can do. The slot registry and the API version are a compatibility contract, not a security boundary.

The control is the deployment. Plugins are only found on the webclient’s classpath, so installing one means adding a jar to the deployment — the same level of trust as any other jar there, and an administrator’s action rather than a user’s. On top of that, plugins are off by default.

What this does not change:

  • Data access. Plugins reach data through the same authenticated endpoints, with the user’s own permissions: a request the user is not allowed to make still returns 403. There is no direct database access.
  • Authentication. Everything below <services base path>/plugins/ is served without authentication on purpose — the code is not the secret, the data is. The list of installed plugins is readable before login and exposes plugin ids and file names only.
  • Availability. A plugin that fails to load, exports no register, or throws while rendering is isolated: the failure is logged, the plugin is dropped, and the rest of the webclient keeps working.
  • Startup. Plugins are loaded next to the webclient rather than before it, so a slow or unreachable plugin cannot delay it. Its contributions appear once it has registered them.

For whoever installs a plugin: it is their code and their responsibility. It has to be retested on every webclient update, and layout changes on our side can affect content rendered next to ours.

On this Page: