From 53b6549c856196c544f7296002704a5b03cd3409 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 26 Jan 2026 17:41:02 +0100 Subject: [PATCH] frontend-plugin-api: plugins now have pluginId Signed-off-by: Patrik Oldsberg --- .changeset/smooth-pants-wave.md | 9 +++++++++ .../src/compatWrapper/BackwardsCompatProvider.tsx | 2 +- .../core-compat-api/src/convertLegacyPlugin.test.tsx | 1 + packages/core-plugin-api/src/app/useApp.tsx | 2 +- .../src/tree/instantiateAppNodeTree.ts | 5 +++-- .../frontend-app-api/src/tree/resolveAppNodeSpecs.ts | 12 +++++++++--- .../src/wiring/createPluginInfoAttacher.ts | 5 ++++- .../src/wiring/createSpecializedApp.tsx | 3 ++- .../frontend-defaults/src/maybeCreateErrorPage.tsx | 6 +++++- packages/frontend-plugin-api/report.api.md | 3 ++- .../src/components/ExtensionBoundary.tsx | 2 +- .../src/wiring/createFrontendPlugin.ts | 10 ++++++++++ 12 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 .changeset/smooth-pants-wave.md diff --git a/.changeset/smooth-pants-wave.md b/.changeset/smooth-pants-wave.md new file mode 100644 index 0000000000..d736d4fe58 --- /dev/null +++ b/.changeset/smooth-pants-wave.md @@ -0,0 +1,9 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-app-api': patch +'@backstage/frontend-defaults': patch +'@backstage/core-plugin-api': patch +'@backstage/core-compat-api': patch +--- + +Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. diff --git a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx index 6abaa433aa..3d39f67804 100644 --- a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx +++ b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx @@ -66,7 +66,7 @@ export function toLegacyPlugin( legacy = { getId(): string { - return plugin.id; + return plugin.pluginId ?? plugin.id; }, get routes() { return {}; diff --git a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx index 81684216eb..5ded572ca2 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx +++ b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx @@ -43,6 +43,7 @@ describe('convertLegacyPlugin', () => { "id": "test", "info": [Function], "infoOptions": undefined, + "pluginId": "test", "routes": {}, "toString": [Function], "version": "v1", diff --git a/packages/core-plugin-api/src/app/useApp.tsx b/packages/core-plugin-api/src/app/useApp.tsx index fde807c317..5d789689cd 100644 --- a/packages/core-plugin-api/src/app/useApp.tsx +++ b/packages/core-plugin-api/src/app/useApp.tsx @@ -53,7 +53,7 @@ function toLegacyPlugin(plugin: FrontendPlugin): BackstagePlugin { legacy = { getId(): string { - return plugin.id; + return plugin.pluginId ?? plugin.id; }, get routes() { return {}; diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts index 0f641d020b..4d1394fe05 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts @@ -252,11 +252,12 @@ function resolveV2Inputs( return mapValues(inputMap, (input, inputName) => { const allAttachedNodes = attachments.get(inputName) ?? []; const collector = parentCollector.child({ inputName }); - const inputPluginId = node.spec.plugin.id; + const inputPluginId = node.spec.plugin.pluginId ?? node.spec.plugin.id; const attachedNodes = input.config.internal ? allAttachedNodes.filter(attachment => { - const attachmentPluginId = attachment.spec.plugin.id; + const attachmentPluginId = + attachment.spec.plugin.pluginId ?? attachment.spec.plugin.id; if (attachmentPluginId !== inputPluginId) { collector.report({ code: 'EXTENSION_INPUT_INTERNAL_IGNORED', diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts index d3b4ad6723..c2347c8eb9 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts @@ -57,7 +57,11 @@ export function resolveAppNodeSpecs(options: { if (forbidden.has(extension.id)) { collector.report({ code: 'EXTENSION_IGNORED', - message: `It is forbidden to override the '${extension.id}' extension, attempted by the '${extension.plugin.id}' plugin`, + message: `It is forbidden to override the '${ + extension.id + }' extension, attempted by the '${ + extension.plugin.pluginId ?? extension.plugin.id + }' plugin`, context: { plugin: extension.plugin, extensionId: extension.id, @@ -91,7 +95,7 @@ export function resolveAppNodeSpecs(options: { ); const appPlugin = - plugins.find(plugin => plugin.id === 'app') ?? + plugins.find(plugin => (plugin.pluginId ?? plugin.id) === 'app') ?? createFrontendPlugin({ pluginId: 'app', }); @@ -159,7 +163,9 @@ export function resolveAppNodeSpecs(options: { if (seenExtensionIds.has(extension.id)) { collector.report({ code: 'EXTENSION_IGNORED', - message: `The '${extension.id}' extension from the '${params.plugin.id}' plugin is a duplicate and will be ignored`, + message: `The '${extension.id}' extension from the '${ + params.plugin.pluginId ?? params.plugin.id + }' plugin is a duplicate and will be ignored`, context: { plugin: params.plugin, extensionId: extension.id, diff --git a/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts index dc2b54f534..22f8034be0 100644 --- a/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts +++ b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts @@ -77,7 +77,10 @@ export function createPluginInfoAttacher( }), }); - const infoWithOverrides = applyInfoOverrides(plugin.id, resolvedInfo); + const infoWithOverrides = applyInfoOverrides( + plugin.pluginId ?? plugin.id, + resolvedInfo, + ); return normalizePluginInfo(infoWithOverrides); }), }; diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index f902500e9f..59377e9545 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -401,7 +401,8 @@ function createApiFactories(options: { if (apiFactory) { const apiRefId = apiFactory.api.id; const ownerId = getApiOwnerId(apiRefId); - const pluginId = apiNode.spec.plugin.id ?? 'app'; + const pluginId = + apiNode.spec.plugin.pluginId ?? apiNode.spec.plugin.id ?? 'app'; const existingFactory = factoriesById.get(apiRefId); // This allows modules to override factories provided by the plugin, but diff --git a/packages/frontend-defaults/src/maybeCreateErrorPage.tsx b/packages/frontend-defaults/src/maybeCreateErrorPage.tsx index c0fb807deb..f78991a887 100644 --- a/packages/frontend-defaults/src/maybeCreateErrorPage.tsx +++ b/packages/frontend-defaults/src/maybeCreateErrorPage.tsx @@ -40,7 +40,11 @@ function AppErrorItem(props: { error: AppError }): JSX.Element { useEffect(() => { plugin?.info().then(setInfo, error => { // eslint-disable-next-line no-console - console.error(`Failed to load info for plugin ${plugin.id}: ${error}`); + console.error( + `Failed to load info for plugin ${ + plugin.pluginId ?? plugin.id + }: ${error}`, + ); }); }, [plugin]); diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index 76ca364952..c4c8ef96e7 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -1383,9 +1383,10 @@ export interface FrontendPlugin< readonly $$type: '@backstage/FrontendPlugin'; // (undocumented) readonly externalRoutes: TExternalRoutes; - // (undocumented) + // @deprecated readonly id: string; info(): Promise; + readonly pluginId: string; // (undocumented) readonly routes: TRoutes; } diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index fefa2dc521..f68194bf7c 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -90,7 +90,7 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) { ); const plugin = node.spec.plugin; - const pluginId = plugin.id ?? 'app'; + const pluginId = plugin.pluginId ?? plugin.id ?? 'app'; const pluginWrapperApi = useOptionalPluginWrapperApi(); diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 68b5826baa..18fd52162a 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -130,6 +130,15 @@ export interface FrontendPlugin< }, > { readonly $$type: '@backstage/FrontendPlugin'; + /** + * The plugin ID. + */ + readonly pluginId: string; + /** + * Deprecated alias for `pluginId`. + * + * @deprecated Use `pluginId` instead. + */ readonly id: string; readonly routes: TRoutes; readonly externalRoutes: TExternalRoutes; @@ -231,6 +240,7 @@ export function createFrontendPlugin< } return OpaqueFrontendPlugin.createInstance('v1', { + pluginId, id: pluginId, routes: options.routes ?? ({} as TRoutes), externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes),