diff --git a/.changeset/remove-array-attachto-type.md b/.changeset/remove-array-attachto-type.md
new file mode 100644
index 0000000000..99b3513bc9
--- /dev/null
+++ b/.changeset/remove-array-attachto-type.md
@@ -0,0 +1,9 @@
+---
+'@backstage/frontend-plugin-api': minor
+---
+
+**BREAKING**: Removed type support for multiple attachment points in the `ExtensionDefinitionAttachTo` type. Extensions can no longer specify an array of attachment points in the `attachTo` property.
+
+The runtime still supports multiple attachment points for backward compatibility with existing compiled code, but new code will receive type errors if attempting to use this pattern.
+
+Extensions that previously used multiple attachment points should migrate to using a Utility API pattern instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended approach.
diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx
index 6c79002ad9..e21befc80e 100644
--- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx
+++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx
@@ -648,12 +648,13 @@ describe('createSpecializedApp', () => {
output: [coreExtensionData.reactElement],
factory: () => [coreExtensionData.reactElement(
)],
}),
+ // Test backward compatibility - runtime still supports multiple attachment points
createExtension({
name: 'cloned',
attachTo: [
{ id: 'test/a', input: 'children' },
{ id: 'test/b', input: 'children' },
- ],
+ ] as any,
output: [coreExtensionData.reactElement],
factory: () => [coreExtensionData.reactElement()],
}),
diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md
index 80e2ff8df4..89fc72992a 100644
--- a/packages/frontend-plugin-api/report.api.md
+++ b/packages/frontend-plugin-api/report.api.md
@@ -1205,26 +1205,7 @@ export type ExtensionDefinitionAttachTo<
input: string;
id?: never;
}
- | ExtensionInput
- /**
- * @deprecated Multiple attachment points are deprecated and will be removed in a future release. Use a Utility API instead to share functionality across multiple locations. See https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions for migration guidance.
- */
- | Array<
- | {
- id: string;
- input: string;
- relative?: never;
- }
- | {
- relative: {
- kind?: string;
- name?: string;
- };
- input: string;
- id?: never;
- }
- | ExtensionInput
- >;
+ | ExtensionInput;
// @public (undocumented)
export type ExtensionDefinitionParameters = {
diff --git a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts
index b297b3a3a9..06ad9b877e 100644
--- a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts
+++ b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts
@@ -32,12 +32,10 @@ describe('AnalyticsBlueprint', () => {
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
- "attachTo": [
- {
- "id": "api:app/analytics",
- "input": "implementations",
- },
- ],
+ "attachTo": {
+ "id": "api:app/analytics",
+ "input": "implementations",
+ },
"configSchema": undefined,
"disabled": false,
"factory": [Function],
diff --git a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts
index 91a0f27092..23b5982738 100644
--- a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts
+++ b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts
@@ -41,7 +41,7 @@ const factoryDataRef =
*/
export const AnalyticsImplementationBlueprint = createExtensionBlueprint({
kind: 'analytics',
- attachTo: [{ id: 'api:app/analytics', input: 'implementations' }],
+ attachTo: { id: 'api:app/analytics', input: 'implementations' },
output: [factoryDataRef],
dataRefs: {
factory: factoryDataRef,
diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts
index 989dcf4468..8f259be6f9 100644
--- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts
+++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts
@@ -202,12 +202,14 @@ describe('createExtension', () => {
});
});
- it('should create an extension with multiple attachment points', () => {
+ // Tests for backward compatibility - runtime still supports multiple attachment points
+ // but the TypeScript types no longer allow them
+ it('should create an extension with multiple attachment points (backward compat)', () => {
const extension = createExtension({
attachTo: [
{ id: 'root', input: 'default' },
{ id: 'other', input: 'default' },
- ],
+ ] as any,
output: [stringDataRef, numberDataRef.optional()],
factory: () => [stringDataRef('bar')],
});
@@ -216,14 +218,14 @@ describe('createExtension', () => {
);
});
- it('should create an extension with relative attachment points', () => {
+ it('should create an extension with relative attachment points (backward compat)', () => {
const extension = createExtension({
attachTo: [
{ relative: {}, input: 'tabs' },
{ relative: { kind: 'page' }, input: 'tabs' },
{ relative: { name: 'index' }, input: 'tabs' },
{ relative: { kind: 'page', name: 'index' }, input: 'tabs' },
- ],
+ ] as any,
output: [stringDataRef],
factory: () => [stringDataRef('bar')],
});
@@ -232,7 +234,7 @@ describe('createExtension', () => {
);
});
- it('should create an extension with relative attachment points by reference', () => {
+ it('should create an extension with relative attachment points by reference (backward compat)', () => {
const baseOpts = {
attachTo: { id: 'root', input: 'children' },
inputs: {
@@ -269,7 +271,7 @@ describe('createExtension', () => {
parent2.inputs.tabs,
parent3.inputs.tabs,
parent4.inputs.otherTabs,
- ],
+ ] as any,
output: [stringDataRef],
factory: () => [stringDataRef('bar')],
});
@@ -281,7 +283,7 @@ describe('createExtension', () => {
parent2.inputs.tabs,
parent3.inputs.tabs,
parent4.inputs.otherTabs,
- ],
+ ] as any,
});
expect(String(overrdeExtension)).toBe(
'ExtensionDefinition{attachTo=page:@tabs+/index@tabs+page:/index@otherTabs}',
diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts
index c1f704f61a..4d3eeece2c 100644
--- a/packages/frontend-plugin-api/src/wiring/createExtension.ts
+++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts
@@ -134,11 +134,10 @@ export type VerifyExtensionAttachTo<
*
* A standard attachment point declaration will specify the ID of the parent extension, as well as the name of the input to attach to.
*
- * There are three more advanced forms that are available for more complex use-cases:
+ * There are two more advanced forms that are available for more complex use-cases:
*
* 1. Relative attachment points: using the `relative` property instead of `id`, the attachment point is resolved relative to the current plugin.
* 2. Extension input references: using a reference in code to another extension's input in the same plugin. These references are always relative.
- * 3. Array of attachment points: an array of attachment points can be used to clone and attach to multiple extensions at once.
*
* @example
* ```ts
@@ -151,12 +150,6 @@ export type VerifyExtensionAttachTo<
* // Attach to a specific input of another extension
* const page = ParentBlueprint.make({ ... });
* const child = ChildBlueprint.make({ attachTo: page.inputs.children });
- *
- * // Attach to multiple parents at once (deprecated - use Utility APIs instead)
- * [
- * { id: 'page/home', input: 'widgets' },
- * { relative: { kind: 'page' }, input: 'widgets' },
- * ]
* ```
*
* @public
@@ -166,19 +159,7 @@ export type ExtensionDefinitionAttachTo<
> =
| { id: string; input: string; relative?: never }
| { relative: { kind?: string; name?: string }; input: string; id?: never }
- | ExtensionInput
- /**
- * @deprecated Multiple attachment points are deprecated and will be removed in a future release. Use a Utility API instead to share functionality across multiple locations. See https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions for migration guidance.
- */
- | Array<
- | { id: string; input: string; relative?: never }
- | {
- relative: { kind?: string; name?: string };
- input: string;
- id?: never;
- }
- | ExtensionInput
- >;
+ | ExtensionInput;
/** @public */
export type CreateExtensionOptions<
diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx
index 4759441187..7f3ef5cb88 100644
--- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx
+++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx
@@ -92,11 +92,7 @@ describe('createExtensionBlueprint', () => {
it('should allow creation of extension blueprints with a generator', () => {
const TestExtensionBlueprint = createExtensionBlueprint({
kind: 'test-extension',
- // Try multiple attachment points for this one
- attachTo: [
- { id: 'test-1', input: 'default' },
- { id: 'test-2', input: 'default' },
- ],
+ attachTo: { id: 'test-1', input: 'default' },
output: [coreExtensionData.reactElement],
*factory(params: { text: string }) {
yield coreExtensionData.reactElement({params.text}
);
@@ -112,10 +108,7 @@ describe('createExtensionBlueprint', () => {
expect(extension).toEqual({
$$type: '@backstage/ExtensionDefinition',
- attachTo: [
- { id: 'test-1', input: 'default' },
- { id: 'test-2', input: 'default' },
- ],
+ attachTo: { id: 'test-1', input: 'default' },
configSchema: undefined,
disabled: false,
inputs: {},
@@ -1345,7 +1338,9 @@ describe('createExtensionBlueprint', () => {
).toThrow('Refused to override params and factory at the same time');
});
- describe('with relative attachment points', () => {
+ // Tests for backward compatibility - runtime still supports multiple attachment points
+ // but the TypeScript types no longer allow them
+ describe('with relative attachment points (backward compat)', () => {
const dataRef = createExtensionDataRef().with({ id: 'test.data' });
it('should create an extension with relative attachment points', () => {
@@ -1356,7 +1351,7 @@ describe('createExtensionBlueprint', () => {
{ relative: { kind: 'page' }, input: 'tabs' },
{ relative: { name: 'index' }, input: 'tabs' },
{ relative: { kind: 'page', name: 'index' }, input: 'tabs' },
- ],
+ ] as any,
output: [dataRef],
factory: () => [dataRef('bar')],
});
@@ -1371,7 +1366,7 @@ describe('createExtensionBlueprint', () => {
{ relative: { kind: 'page' }, input: 'tabs' },
{ relative: { name: 'index' }, input: 'tabs' },
{ relative: { kind: 'page', name: 'index' }, input: 'tabs' },
- ],
+ ] as any,
params: {},
}),
),
@@ -1431,7 +1426,7 @@ describe('createExtensionBlueprint', () => {
parent2.inputs.tabs,
parent3.inputs.tabs,
parent4.inputs.otherTabs,
- ],
+ ] as any,
output: [dataRef],
factory: () => [dataRef('bar')],
});
@@ -1445,7 +1440,7 @@ describe('createExtensionBlueprint', () => {
parent2.inputs.tabs,
parent3.inputs.tabs,
parent4.inputs.otherTabs,
- ],
+ ] as any,
params: {},
}),
),
diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts
index 79749af77c..e080ab1f64 100644
--- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts
+++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts
@@ -140,6 +140,7 @@ describe('resolveExtensionDefinition', () => {
input: 'children',
});
+ // Test for backward compatibility - runtime still supports multiple attachment points
expect(
resolveExtensionDefinition(
OpaqueExtensionDefinition.toInternal({
@@ -157,7 +158,7 @@ describe('resolveExtensionDefinition', () => {
kind: 'k3',
input: 'children',
}),
- ],
+ ] as any,
}),
{ namespace: 'test' },
).attachTo,
diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts
index f99d586e87..605e75bfe8 100644
--- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts
+++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts
@@ -149,11 +149,11 @@ function resolveExtensionId(
}
function resolveAttachTo(
- attachTo: ExtensionDefinitionAttachTo,
+ attachTo: ExtensionDefinitionAttachTo | ExtensionDefinitionAttachTo[],
namespace?: string,
): ExtensionAttachToSpec {
const resolveSpec = (
- spec: Exclude>,
+ spec: ExtensionDefinitionAttachTo,
): { id: string; input: string } => {
if (OpaqueExtensionInput.isType(spec)) {
const { context } = OpaqueExtensionInput.toInternal(spec);