Skip to main content
Version: v1

Loading a Dynamic Plugin

Dynamic plugins can be loaded by VeeCode DevPortal at start time. They are usually published to a private npm registry or OCI registry, and the DevPortal instance will load them from there according to the configuration.

Configuration​

There are two configuration surfaces depending on your deployment model:

dynamic-plugins.yaml (distro container / SaaS)​

For the standard DevPortal container (docker-compose, SaaS, or VKDR), edit dynamic-plugins.yaml directly:

plugins:
# npm plugin
- package: '@yourorg/yourplugin@x.y.z'
disabled: false
integrity: sha512-xxxxxxxxx
# preloaded plugin (uses path relative to the dynamic-plugins directory)
- package: ./dynamic-plugins/dist/another-plugin-dynamic
disabled: false

Helm values.yaml (Kubernetes deployment)​

For Kubernetes deployments using the VeeCode Helm chart, configure plugins under global.dynamic.plugins:

global:
dynamic:
plugins:
# npm plugin
- package: '@yourorg/yourplugin@x.y.z'
disabled: false
integrity: sha512-xxxxxxxxx
# preloaded plugin
- package: ./dynamic-plugins/dist/another-plugin-dynamic
disabled: false

Both surfaces accept the same plugin entry format. The dynamic-plugins.yaml approach is recommended for non-Helm deployments.

Generating the integrity hash​

The integrity: field is required for remote npm packages (unless the env var SKIP_INTEGRITY_CHECK=true is set). The installer downloads the package, recomputes its SHA-512, and refuses to load it on mismatch.

It is not used for:

  • OCI packages (oci://...) — these are validated by digest comparison via skopeo inspect.
  • Local paths (./dynamic-plugins/dist/...) — pre-bundled in the image; no download involved.

Expected format: sha512-<base64>. Two ways to generate it:

Method A — query the npm registry directly (preferred for public packages):

npm view <package>@<version> dist.integrity
# example
npm view @backstage/plugin-catalog@2.0.5 dist.integrity

Returns the hash in exactly the format the installer compares against. Always pin the version — npm view <package> dist.integrity (no version) returns the latest, which will mismatch if you have a specific version pinned in your package: field.

Method B — compute it locally (fallback for private registries or when npm view fails):

npm pack <package>@<version> && \
HASH=$(cat <package>-<version>.tgz | openssl dgst -sha512 -binary | openssl base64 -A) && \
echo "sha512-$HASH"

This replicates the exact pipeline the installer uses internally (cat archive | openssl dgst -sha512 -binary | openssl base64 -A), so the produced hash is guaranteed to match a successful install.

Private npm registry​

Due to security and compliance reasons you may not want VeeCode DevPortal to load plugins from public npm registries. You may prefer to use a private npm registry, like Nexus, Artifactory or even Verdaccio.

VeeCode DevPortal can be configured to rely on a private npm registry - you just have to configure a special secret named "veecode-devportal-dynamic-plugins-npmrc" (where "veecode-devportal" is the Helm release name) in the same namespace as the DevPortal deployment:

apiVersion: v1
kind: Secret
metadata:
name: veecode-devportal-dynamic-plugins-npmrc
namespace: <your-namespace>
type: Opaque
stringData:
.npmrc: |
registry=<registry-url>
//<registry-url>:_authToken=<auth-token>

Alternatively you can create the secret using the kubectl command:

kubectl create secret generic veecode-devportal-dynamic-plugins-npmrc \
"--from-literal=.npmrc=registry=your-registry-url"

Wiring plugins​

Dynamic plugins wire themselves to the DevPortal instance through configuration. Unlike static plugins, they cannot modify the host Backstage project's code — wiring happens at runtime, declared in YAML.

The wiring rule depends on the plugin's role, declared in its package.json under backstage.role:

RolepluginConfig needed?Where does its config go?
frontend-pluginYes — describes mount points, tabs, routesInside pluginConfig.dynamicPlugins.frontend.<plugin-id>
backend-pluginNo — auto-discovered by the loaderPlain top-level keys in app-config.yaml (e.g., sonarqube:, gitlab:)
backend-plugin-moduleNo — auto-attaches to its parent pluginPlain top-level keys in app-config.yaml (parent plugin's config)

There is no dynamicPlugins.backend.* key. Backend plugins never wire themselves through pluginConfig — the loader detects them by their package role and registers them automatically.

Backend plugin example (no pluginConfig)​

plugins:
- package: oci://quay.io/veecode/sonarqube:bs_1.49.4!backstage-community-plugin-sonarqube-backend
disabled: false
# No pluginConfig — backend role is auto-discovered

The plugin's runtime configuration goes in your regular app-config.yaml (or app-config.local.yaml) as a normal top-level section:

sonarqube:
baseUrl: ${SONARQUBE_URL}
apiKey: ${SONARQUBE_TOKEN}

Same pattern for backend-plugin-module (e.g., a scaffolder action module attaches itself to the scaffolder plugin):

plugins:
- package: oci://quay.io/veecode/roadie-backstage-plugins:bs_1.49.4!roadiehq-scaffolder-backend-argocd
disabled: false
# No pluginConfig — module auto-attaches to the scaffolder backend

Frontend plugin example (pluginConfig required)​

Frontend plugins must declare where their UI components mount, because Backstage's frontend has no auto-discovery for routes, sidebars, tabs, or cards:

plugins:
- package: oci://quay.io/veecode/sonarqube:bs_1.49.4!backstage-community-plugin-sonarqube
disabled: false
pluginConfig:
dynamicPlugins:
frontend:
backstage-community.plugin-sonarqube:
mountPoints:
- mountPoint: entity.page.overview/cards
importName: EntitySonarQubeCard
config:
if:
allOf:
- isSonarQubeAvailable

The plugin-id under dynamicPlugins.frontend.<plugin-id> is the npm package name with @ removed and / replaced by . (note: ., not -, for this key specifically — different from the OCI reference convention).

See Wiring a Frontend Plugin for the full list of frontend mount points, tab paths, and the scalprum mechanism that processes these declarations at runtime.

Tips​

You can check the loaded plugins using this URL:

curl <your-devportal-url>/api/dynamic-plugins-info/loaded-plugins