This package provides experimental support for dynamic backend features (plugins and modules) in Backstage, allowing you to load plugins at runtime from a separate directory without including them in your main application's package.json.
This enables:
Important: This service handles loading only - packaging and distribution are separate concerns with multiple approaches available (see Packaging Approaches section).
@backstage/frontend-dynamic-feature-loader: Companion package for loading dynamic frontend plugins. This is only supported in the New Frontend System. For more details, checkout the README.The service consists of several key components:
dynamicPlugins.rootDirectory) for plugin packagesmain, backstage.role)When a backend dynamic plugin requires a module, it is resolved from the plugin's own node_modules first, then from the node_modules of the Backstage root, and then from the global folders of Node.js, which start with the directories in NODE_PATH. Plugins outside of the Backstage root reach the root node_modules through NODE_PATH, so for them the order of NODE_PATH applies. When NODE_PATH doesn't include it, the root node_modules is searched right after the global folders.
Modules that are not found in these places are resolved from the host backend, through the node_modules directories of the backend package and of this package within the Backstage root. The backend package is found through the entry point of the process and the working directory. If the backend is started by another program, such as a process manager, use the backend package directory as its working directory, and adjust relative paths such as --config arguments.
This lets a plugin require the packages it shares with the host when the package manager does not link them into the root node_modules, as with the pnpm isolated node linker. With the default pnpm hoisting, this includes the packages that pnpm hoists into node_modules/.pnpm/node_modules. With hoist: false, only the direct dependencies of the backend package and of this package are found.
Add the service to your backend application:
yarn add @backstage/backend-dynamic-feature-service
import { createBackend } from '@backstage/backend-defaults';
import { dynamicPluginsFeatureLoader } from '@backstage/backend-dynamic-feature-service';
const backend = createBackend();
// Add this line to enable dynamic plugin support:
backend.add(dynamicPluginsFeatureLoader);
// ... your other plugins
backend.start();
Configure the dynamic plugins directory in your app-config.yaml:
dynamicPlugins:
rootDirectory: dynamic-plugins-root
Before exploring specific packaging approaches, it's important to understand what constitutes a valid dynamic plugin package. Dynamic plugins follow the basic rules of Node.js packages, including:
Package contents:
dist/.config-schema.json) for plugin configuration validationnode_modules folder containing private dependencies for backend pluginsRequired package.json fields:
name: Package identifierversion: Package versionmain: Entry point to the plugin's JavaScript codebackstage.role: Must be set to "backend-plugin" or "backend-plugin-module"Since this service only handles loading, you would choose a packaging approach based on your plugin's dependencies:
When to use: Plugin only uses dependencies that are already provided by the main Backstage application.
How to use:
cd my-backstage-plugin
yarn pack
# Results in: package.tgz
# Extract to dynamic plugins directory
mkdir -p /path/to/dynamic-plugins-root/my-backstage-plugin
tar -xzf package.tgz -C /path/to/dynamic-plugins-root/my-backstage-plugin --strip-components=1
Why this works: The plugin can resolve all its dependencies from the main application. See Module Resolution.
Reality: Most plugins have private dependencies not available in the main application, so this approach has limited applicability.
When to use: Plugin has private dependencies not available in the main Backstage application.
How to use:
# Package the plugin
cd my-backstage-plugin
yarn pack
# Extract and install dependencies
mkdir -p /path/to/dynamic-plugins-root/my-backstage-plugin
tar -xzf package.tgz -C /path/to/dynamic-plugin-root/my-backstage-plugin --strip-components=1
cp yarn.lock /path/to/dynamic-plugin-root/my-backstage-plugin
cd /path/to/dynamic-plugins-root/my-backstage-plugin
yarn install # Installs all the plugin's dependencies
Why this works: Each plugin gets its own node_modules directory with all its dependencies.
Example scenario: Plugin needs axios@1.4.0 which isn't available in the main application.
When to use:
How to use:
The backstage-cli package bundle command automates the required steps. Run it from within a plugin directory:
cd my-backstage-plugin
yarn backstage-cli package bundle --output-destination /path/to/dynamic-plugins-root
# Creates a self-contained bundle in the /path/to/dynamic-plugins-root/my-backstage-plugin/ sub-folder
Batch bundling: When bundling many plugins from the same monorepo, use --pre-packed-dir to avoid redundant work:
# First, build a shared dist workspace
backstage-cli build-workspace dist-workspace --alwaysPack ...plugin-packages
# Then bundle each plugin using the pre-packed output
cd plugins/my-backstage-plugin
backstage-cli package bundle --pre-packed-dir ../../dist-workspace
See the full list of options in the CLI reference.
What the command does:
*-node or *-common)workspace:^ and backstage:^ dependencies on both the main plugin package and its transitively-required monorepo packages (*-node or *-common)node_modules containing all required dependenciesdist/.config-schema.json so they are available for validation at load timeBenefits: