Upgrading your internal developer platform shouldn't feel like navigating a minefield. Upcoming Red Hat Developer Hub release 2.1 introduces major performance and architectural updates—including upstream Backstage's new front-end system and standalone OCI plugin images—but getting there requires rethinking how your custom plug-ins are built and mounted.
Two architectural shifts require preparation before upgrading: adopting the new front-end system and migrating plug-ins to standalone OCI images. Red Hat Developer Hub 2.1 includes other breaking changes not covered here; the upcoming release notes and migration documentation provide a complete list.
Important
Update custom front-end plug-ins and dynamic configurations before upgrading to Red Hat Developer Hub 2.1. Older plug-ins that rely on Scalprum mount points cannot load in the 2.1 application shell and will cause the deployment to fail on startup.
Whether you are a platform administrator managing Helm values and Operator configurations or a plugin author maintaining TypeScript blueprints, this guide covers what changed, how configuration maps to the new extension model, and how to test your setup with early 2.1 builds.
The new front-end system: Why you must update plug-ins first
In Red Hat Developer Hub 1.10, you could switch an instance between the old front-end system (packages/app
) and the new front-end system (packages/app-next
) using environment flags:
APP_CONFIG_app_packageName=app-next
ENABLE_STANDARD_MODULE_FEDERATION=true
That toggle lets teams test their plug-ins against the new system while keeping the default setup intact.
In Red Hat Developer Hub 2.1, the previous front-end system and Scalprum—the module federation framework used to dynamically inject plug-ins at runtime—have been removed. packages/app-next
is now the default packages/app
shell. lugins that rely on dynamic Scalprum mount points (dynamicPlugins.frontend
) will fail to mount in the browser.
Removing the old front-end system eliminates roughly 40,000 lines of legacy code, cuts image sizes, and aligns Red Hat Developer Hub directly with upstream Backstage extension blueprints.
Recommended custom plugin migration workflow (plugin authors)
When migrating custom plug-ins, the Red Hat Developer Hub engineering team follows this 6-step process:
- Discover: Check
package.json
,src/plugin.ts
, routes, API factories, components, and current dynamic plugin mount points to catalog what the plug-in exports. - Choose the alpha export: Use an
./alpha
export so the plug-in supports the new front-end system while keeping its root export (.
) working for 1.10 instances. - Migrate extensions: Convert React components to Backstage extension blueprints (
PageBlueprint
,EntityCardBlueprint
,EntityContentBlueprint
). - Update
package.json
: Add the./alpha
export map alongside the root export. - Update local dev wiring: Add a new front-end system dev app entry point (
dev/index.tsx
ordev/nfs.tsx
) using@backstage/frontend-defaults
to test the plug-in locally. - Verify: Run
yarn tsc
from your workspace root and run a build to check types and exports.
What changed for plugin authors
In the old front-end system (in Red Hat Developer Hub version 1.10 and earlier), plug-ins exported standard React components (PluginRoot
), and platform engineers wired those components into UI slots using YAML mount points.
In the new front-end system (in Red Hat Developer Hub 1.10 onwards), plug-ins define their own placement in code through extension blueprints:
// src/alpha.tsx — entry point for the new frontend system
import { createFrontendPlugin, PageBlueprint } from '@backstage/frontend-plugin-api';
import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha';
import { rootRouteRef } from './routes';
const myPage = PageBlueprint.make({
params: {
path: '/my-tools',
routeRef: rootRouteRef,
title: 'My Developer Tools',
loader: () => import('./components/MyToolsPage').then(m => <m.MyToolsPage />),
},
});
const myEntityCard = EntityCardBlueprint.make({
name: 'service-status',
params: {
filter: { kind: 'component' },
loader: () => import('./components/StatusCard').then(m => <m.StatusCard />),
},
});
export default createFrontendPlugin({
pluginId: 'my-tools',
extensions: [myPage, myEntityCard],
});
The dual-export pattern
To keep your plug-in working in Red Hat Developer Hub 1.10 while preparing it for 2.1, export both entry points in package.json
:
{
"name": "backstage-plugin-my-tools",
"exports": {
".": "./src/index.ts",
"./alpha": "./src/alpha.tsx",
"./package.json": "./package.json"
}
}
Developer Hub 1.10 loads .
, while Developer Hub 2.1 reads ./alpha
.
Backstage dynamic plug-ins: What changed in YAML configuration (platform administrators)
In Red Hat Developer Hub 1.x, configuration files explicitly wired plugin components into layout slots. In the new front-end system, plug-ins provide sensible defaults in code, and administrators use YAML to override or disable those defaults.
Old front-end system (dynamicPlugins.frontend ) | New front-end system (app.* ) |
---|---|
Adopter declares UI placement in YAML (mountPoints , importName ) | Plug-ins declare defaults in code; adopters override using app.extensions |
Route and sidebar defined under dynamicRoutes | Inferred automatically from page:* extensions |
Overview cards configured under entity.page.overview/cards | Configured under entity-card:* extensions |
Cross-plugin links wired using routeBindings | Declared in app.routes.bindings |
Disabling UI: remove YAML block or set enabled: false | Set <extension-id>: false in app.extensions |
Example: Overriding the placement of an entity card
Old syntax (dynamic-plugins.yaml
):
dynamicPlugins:
frontend:
my-company.backstage-plugin-custom:
mountPoints:
- mountPoint: entity.page.overview/cards
importName: CustomOverviewCard
config:
layout:
gridColumn: "1 / span 6"
if:
allOf:
- isKind: component
New syntax in 2.1 (app-config.yaml
or pluginConfig
):
app:
extensions:
- entity-card:custom/overview-card:
config:
filter:
kind: component
type: info
Example: Disabling a default card
To hide a built-in card in Red Hat Developer Hub 2.1:
app:
extensions:
- entity-card:catalog/labels: false
Configuration merging and the app.extensions
array
In Red Hat Developer Hub 1.x, front-end layout was split across several files. Default mount points lived in dynamic-plugins.default.yaml
, wrappers had their own defaults, and administrators added overrides in dynamic-plugins.yaml
and app-config.yaml
. Because dynamicPlugins.frontend
was a map of objects, Backstage merged these files key by key, which made it hard to track where a given card or route came from.
In Red Hat Developer Hub 2.1, app-config.yaml
becomes the sole place to customize the interface. Because plug-ins now define layout defaults directly in TypeScript using extension blueprints, dynamic-plugins.default.yaml
drops front-end layout configuration entirely.
First, plug-ins set their own defaults in TypeScript using extension blueprints. Because routes, cards, and default placement are defined in code, dynamic-plugins.default.yaml
no longer carries front-end layout configuration.
Furthermore, Backstage treats app.extensions
as a YAML array:
app:
extensions:
- entity-card:custom/overview-card:
config:
filter:
kind: component
In Backstage, configuration files merge objects key by key, but arrays are replaced completely. If you define app.extensions
in more than one configuration file, the higher-priority file overwrites the entire array without merging entries by ID.
Because arrays overwrite instead of merging, defining app.extensions
in app-config.yaml
replaces default plugin configurations cleanly without requiring multi-file reconciliation.
Removal of dynamic plugin wrappers
In Red Hat Developer Hub 1.x, dynamic plugin wrappers were packaged directly inside the core Developer Hub container image in the folder ./dynamic-plugins/dist/
. Because these wrappers lived inside the main binary container image, they were tied to the Red Hat Developer Hub release cycle. Patching a plugin bug usually meant waiting for a platform patch release or building a custom container image yourself.
In Red Hat Developer Hub 2.1, all wrappers have been removed from the Developer Hub container image (see RHIDP-10858 and PR #5299). Plug-ins are now published as independent, standalone OCI container images built through Red Hat Konflux pipelines and available on quay.io.
Migration skills for coding agents
To help migrate plugins and configuration files, the Developer Hub team has created 2 assistant skills:
The plugin migration skill (rhdh-plugin-nfs-migration)
Available in the rhdh-skills repository, this skill walks an AI agent through the 6-step plugin migration:
- Inspects
package.json
, routes, and components. - Generates extension blueprints (
PageBlueprint
,EntityCardBlueprint
,EntityContentBlueprint
). - Sets up the
./alpha
export map. - Adds a dev app harness and runs TypeScript verification.
Community note: We developed this skill to automate migrating Red Hat’s internal plugin catalog and wanted to share the tooling with the community. While it is an experimental community utility rather than a commercially supported Red Hat feature, it can significantly accelerate your custom plugin refactoring.
The configuration upgrade helper (rhdh-upgrade-helper)
Available in the rhdh-users-skill-pack repository, this skill inspects Helm values, app-config.yaml
, and dynamic-plugins.yaml
files:
- Flags old
./dynamic-plugins/dist/
wrapper paths. - Finds and removes unsupported
!plugin-path
suffixes. - Converts legacy
dynamicPlugins.frontend
mount points intoapp.extensions
blocks.
Testing migrations locally
You can test migrated plug-ins and configuration locally before deploying them to a cluster using RHDH Local.
The main
branch of rhdh-local
runs Red Hat Developer Hub 1.10 (quay.io/rhdh-community/rhdh:1.10
). To test against 2.1 development builds, check out the release-2.1
branch, where compose.yaml
uses RHDH_IMAGE=quay.io/rhdh-community/rhdh:next
:
git clone https://github.com/redhat-developer/rhdh-local.git
cd rhdh-local
git checkout release-2.1
podman compose up -d
Once started, point rhdh-local
at your updated dynamic-plugins.yaml
and verify that your plug-ins load properly.
Migration checklist
Before upgrading an environment to Red Hat Developer Hub 2.1:
- Custom front-end plug-ins implement
createFrontendPlugin
and export using./alpha
. - Plugin
package.json
files export both.
and./alpha
so they work across versions. - Convert
dynamicPlugins.frontend
configuration blocks toapp.extensions
andapp.routes.bindings
. - Remove all
./dynamic-plugins/dist/
wrapper paths fromdynamic-plugins.yaml
. - Catalog plug-ins use the
ref://
reference syntax. APP_CONFIG_app_packageName
andENABLE_STANDARD_MODULE_FEDERATION
are removed from deployment templates.- Catalog index image references are updated to 2.1.
- A test deployment boots cleanly in staging or locally with
rhdh-local
on therelease-2.1
branch.
What's next
Ready to build your internal developer portal? Explore Red Hat Developer Hub and start a no-cost, 60-day trial. To see how the architecture works, review the upstream Backstage front-end system documentation.