switch from backstage.integrationFor to backstage.peerModules

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-02-02 22:57:06 +01:00
parent f5d56be363
commit 5e3ef57e4e
15 changed files with 64 additions and 59 deletions
-10
View File
@@ -1,10 +0,0 @@
---
'@backstage/cli': patch
'@backstage/cli-node': patch
'@backstage/plugin-catalog-backend-module-scaffolder-entity-model': patch
'@backstage/plugin-search-backend-module-catalog': patch
'@backstage/plugin-search-backend-module-techdocs': patch
'@backstage/plugin-scaffolder-backend-module-notifications': patch
---
Added support for the new `integrationFor` metadata field in `package.json`. This field enables cross-plugin module discovery by declaring which packages a module provides integration for. Tooling and documentation systems can use this metadata to surface relevant integrations and help users discover modules that work together.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/cli': patch
'@backstage/cli-node': patch
---
Added support for the new `peerModules` metadata field in `package.json`. This field allows plugin packages to declare modules that should be installed alongside them for cross-plugin integrations. The field is validated by `backstage-cli repo fix --publish`.
@@ -0,0 +1,8 @@
---
'@backstage/plugin-scaffolder-backend': patch
'@backstage/plugin-notifications-backend': patch
'@backstage/plugin-catalog-backend': patch
'@backstage/plugin-techdocs-backend': patch
---
Added `peerModules` metadata declaring recommended modules for cross-plugin integrations.
+18 -18
View File
@@ -168,39 +168,39 @@ The presence of this field is checked by the `backstage-cli package prepack` com
}
```
### `backstage.integrationFor`
### `backstage.peerModules`
For any module package, this optional field can be set to an array of package names that this module provides integration for. This field enables cross-plugin module discovery by declaring relationships between modules and the plugins they enhance or integrate with.
For plugin packages, this optional field declares modules that should be installed alongside this plugin for cross-plugin integrations. If the peer module's target plugin is present in the Backstage installation, you should typically have the peer module installed as well.
For example, a Catalog module that provides Scaffolder-related entity processing can declare that it integrates with the Scaffolder backend. Tooling and documentation systems can use this metadata to surface relevant integrations and help users discover modules that work together.
For example, if you use `@backstage/plugin-scaffolder-backend`, you should also install `@backstage/plugin-catalog-backend-module-scaffolder-entity-model` to enable catalog support for scaffolder entity templates. The scaffolder plugin declares this relationship through `peerModules`.
The field uses full package names rather than plugin IDs to allow precise targeting of specific packages. When referencing packages it is preferred to use a matching role, i.e. a `backend-plugin-module` referencing a `backend-plugin` package, but it is not required, in fact any of the packages in the target plugin's `pluginPackages` field can be used.
The field uses full package names to allow precise targeting of specific modules. This field can only be used on plugin packages (`backend-plugin` or `frontend-plugin` roles).
```js title="Example usage of the backstage.integrationFor field"
```js title="Example usage of the backstage.peerModules field"
{
"name": "@backstage/plugin-catalog-backend-module-scaffolder",
"name": "@backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "catalog",
"pluginPackage": "@backstage/plugin-catalog-backend",
"integrationFor": ["@backstage/plugin-scaffolder-backend"]
"role": "backend-plugin",
"pluginId": "scaffolder",
"peerModules": [
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model"
]
}
...
}
```
A module can declare integration with multiple packages:
A plugin can declare multiple peer modules:
```js title="Example of multi-plugin integration"
```js title="Example of multiple peer modules"
{
"name": "@example/plugin-catalog-backend-module-ci-status",
"name": "@example/plugin-catalog-backend",
"backstage": {
"role": "backend-plugin-module",
"role": "backend-plugin",
"pluginId": "catalog",
"pluginPackage": "@backstage/plugin-catalog-backend",
"integrationFor": [
"@backstage-community/plugin-jenkins-backend",
"@backstage-community/plugin-github-actions-backend"
"peerModules": [
"@example/plugin-search-backend-module-catalog",
"@example/plugin-explore-backend-module-catalog"
]
}
...
+1 -1
View File
@@ -25,7 +25,7 @@ export interface BackstagePackageJson {
pluginId?: string | null;
pluginPackage?: string;
pluginPackages?: string[];
integrationFor?: string[];
peerModules?: string[];
features?: Record<string, BackstagePackageFeatureType>;
};
// (undocumented)
@@ -92,9 +92,10 @@ export interface BackstagePackageJson {
pluginPackages?: string[];
/**
* Package names that this module provides integration for. When any of these packages are used, this module should be suggested as a related integration.
* Module packages that should be installed alongside this plugin for cross-plugin integrations.
* If the peer module's target plugin is present, you should have the peer module installed.
*/
integrationFor?: string[];
peerModules?: string[];
/**
* The feature types exported from the package, indexed by path.
@@ -430,15 +430,15 @@ export function fixPluginPackages(
}
}
export function fixIntegrationFor(pkg: FixablePackage) {
export function fixPeerModules(pkg: FixablePackage) {
const pkgBackstage = pkg.packageJson.backstage;
const role = pkgBackstage?.role;
if (!role) {
return;
}
const integrationFor = pkgBackstage.integrationFor;
if (integrationFor === undefined) {
const peerModules = pkgBackstage.peerModules;
if (peerModules === undefined) {
return;
}
@@ -447,25 +447,25 @@ export function fixIntegrationFor(pkg: FixablePackage) {
resolvePath(pkg.dir, 'package.json'),
);
// Validate that integrationFor is only used on module packages
if (role !== 'backend-plugin-module' && role !== 'frontend-plugin-module') {
// Validate that peerModules is only used on plugin packages
if (role !== 'backend-plugin' && role !== 'frontend-plugin') {
throw new Error(
`The 'backstage.integrationFor' field in "${pkg.packageJson.name}" can only be used on module packages (backend-plugin-module or frontend-plugin-module), but package has role '${role}' in "${packagePath}"`,
`The 'backstage.peerModules' field in "${pkg.packageJson.name}" can only be used on plugin packages (backend-plugin or frontend-plugin), but package has role '${role}' in "${packagePath}"`,
);
}
// Validate that integrationFor is an array
if (!Array.isArray(integrationFor)) {
// Validate that peerModules is an array
if (!Array.isArray(peerModules)) {
throw new Error(
`Invalid 'backstage.integrationFor' field in "${pkg.packageJson.name}", must be an array of package names in "${packagePath}"`,
`Invalid 'backstage.peerModules' field in "${pkg.packageJson.name}", must be an array of package names in "${packagePath}"`,
);
}
// Validate that all entries are non-empty strings
for (const entry of integrationFor) {
for (const entry of peerModules) {
if (typeof entry !== 'string' || entry.length === 0) {
throw new Error(
`Invalid entry in 'backstage.integrationFor' field in "${pkg.packageJson.name}", all entries must be non-empty package name strings in "${packagePath}"`,
`Invalid entry in 'backstage.peerModules' field in "${pkg.packageJson.name}", all entries must be non-empty package name strings in "${packagePath}"`,
);
}
}
@@ -485,7 +485,7 @@ export async function command(opts: OptionValues): Promise<void> {
fixRepositoryField,
fixPluginId,
fixPluginPackages,
fixIntegrationFor,
fixPeerModules,
// Run the publish preflight check too, to make sure we don't uncover errors during publishing
publishPreflightCheck,
);
@@ -5,10 +5,7 @@
"backstage": {
"role": "backend-plugin-module",
"pluginId": "catalog",
"pluginPackage": "@backstage/plugin-catalog-backend",
"integrationFor": [
"@backstage/plugin-scaffolder-backend"
]
"pluginPackage": "@backstage/plugin-catalog-backend"
},
"publishConfig": {
"access": "public"
+3
View File
@@ -11,6 +11,9 @@
"@backstage/plugin-catalog-common",
"@backstage/plugin-catalog-node",
"@backstage/plugin-catalog-react"
],
"peerModules": [
"@backstage/plugin-search-backend-module-catalog"
]
},
"publishConfig": {
@@ -9,6 +9,9 @@
"@backstage/plugin-notifications-backend",
"@backstage/plugin-notifications-common",
"@backstage/plugin-notifications-node"
],
"peerModules": [
"@backstage/plugin-scaffolder-backend-module-notifications"
]
},
"publishConfig": {
@@ -5,10 +5,7 @@
"backstage": {
"role": "backend-plugin-module",
"pluginId": "scaffolder",
"pluginPackage": "@backstage/plugin-scaffolder-backend",
"integrationFor": [
"@backstage/plugin-notifications-backend"
]
"pluginPackage": "@backstage/plugin-scaffolder-backend"
},
"publishConfig": {
"access": "public",
+3
View File
@@ -12,6 +12,9 @@
"@backstage/plugin-scaffolder-node",
"@backstage/plugin-scaffolder-node-test-utils",
"@backstage/plugin-scaffolder-react"
],
"peerModules": [
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model"
]
},
"publishConfig": {
@@ -5,10 +5,7 @@
"backstage": {
"role": "backend-plugin-module",
"pluginId": "search",
"pluginPackage": "@backstage/plugin-search-backend",
"integrationFor": [
"@backstage/plugin-catalog-backend"
]
"pluginPackage": "@backstage/plugin-search-backend"
},
"publishConfig": {
"access": "public",
@@ -5,10 +5,7 @@
"backstage": {
"role": "backend-plugin-module",
"pluginId": "search",
"pluginPackage": "@backstage/plugin-search-backend",
"integrationFor": [
"@backstage/plugin-techdocs-backend"
]
"pluginPackage": "@backstage/plugin-search-backend"
},
"publishConfig": {
"access": "public"
+3
View File
@@ -11,6 +11,9 @@
"@backstage/plugin-techdocs-common",
"@backstage/plugin-techdocs-node",
"@backstage/plugin-techdocs-react"
],
"peerModules": [
"@backstage/plugin-search-backend-module-techdocs"
]
},
"publishConfig": {