diff --git a/.changeset/remove-nav-item-blueprint.md b/.changeset/remove-nav-item-blueprint.md
new file mode 100644
index 0000000000..b54e59f74c
--- /dev/null
+++ b/.changeset/remove-nav-item-blueprint.md
@@ -0,0 +1,17 @@
+---
+'@backstage/frontend-plugin-api': minor
+'@backstage/plugin-app': patch
+'@backstage/plugin-catalog': patch
+'@backstage/plugin-search': patch
+'@backstage/plugin-home': patch
+'@backstage/plugin-api-docs': patch
+'@backstage/plugin-scaffolder': patch
+'@backstage/plugin-techdocs': patch
+'@backstage/plugin-user-settings': patch
+'@backstage/plugin-devtools': patch
+'@backstage/plugin-catalog-unprocessed-entities': patch
+'@backstage/plugin-app-visualizer': patch
+'@backstage/frontend-test-utils': patch
+---
+
+Removed the deprecated `NavItemBlueprint`. Navigation items are now discovered from page extensions via their `title` and `icon` params. The app nav extension still accepts legacy `nav-item` extensions for backward compatibility.
diff --git a/app-config.yaml b/app-config.yaml
index 11f812bb3d..42e7a62966 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -63,13 +63,6 @@ app:
# - apis.plugin.graphiql.browse.gitlab: true
# - graphiql-endpoint:graphiql/gitlab: true
- - nav-item:search: false
- - nav-item:user-settings: false
- - nav-item:catalog
- - nav-item:api-docs
- - nav-item:scaffolder
- - nav-item:app-visualizer
-
# Opt in to the experimental BUI scaffolder form theme
- sub-page:scaffolder/templates:
config:
diff --git a/docs/.well-known/skills/app-frontend-system-migration/SKILL.md b/docs/.well-known/skills/app-frontend-system-migration/SKILL.md
index 0bfbf0714c..9bb12bd700 100644
--- a/docs/.well-known/skills/app-frontend-system-migration/SKILL.md
+++ b/docs/.well-known/skills/app-frontend-system-migration/SKILL.md
@@ -60,7 +60,7 @@ Even with feature discovery enabled, you can disable specific extensions via con
app:
extensions:
- page:techdocs: false
- - nav-item:search: false
+ - page:search: false
```
### How Discovery Works with Manual Imports
diff --git a/docs/.well-known/skills/mui-to-bui-migration/SKILL.md b/docs/.well-known/skills/mui-to-bui-migration/SKILL.md
index c47286ec30..80ea04ba9c 100644
--- a/docs/.well-known/skills/mui-to-bui-migration/SKILL.md
+++ b/docs/.well-known/skills/mui-to-bui-migration/SKILL.md
@@ -882,8 +882,7 @@ import { CheckboxGroup, Checkbox } from '@backstage/ui';
Some Backstage APIs still require MUI-compatible icon types:
-- **NavItemBlueprint** (`@backstage/frontend-plugin-api`): The `icon` prop expects MUI `IconComponent` type. Remix icons
- are not type-compatible.
+- **PageBlueprint** (`@backstage/frontend-plugin-api`): The `icon` param on page extensions expects an `IconElement`. MUI icon components can still be used via ``.
- **Timeline** (`@material-ui/lab`): No BUI equivalent exists.
For these cases, keep using MUI components.
diff --git a/docs/features/search/declarative-integration.md b/docs/features/search/declarative-integration.md
index cea5225f2f..5be84e34fc 100644
--- a/docs/features/search/declarative-integration.md
+++ b/docs/features/search/declarative-integration.md
@@ -52,16 +52,15 @@ _Example disabling the search page extension_
app:
extensions:
- page:search: false # ✨
- - nav-item:search: false # ✨
```
-_Example setting the search sidebar item title_
+_Example setting the search page title (used in the sidebar)_
```yaml
# app-config.yaml
app:
extensions:
- - nav-item:search: # ✨
+ - page:search: # ✨
config:
title: 'Search Page'
```
diff --git a/docs/frontend-system/architecture/15-plugins.md b/docs/frontend-system/architecture/15-plugins.md
index d124a0e61d..2d16223b68 100644
--- a/docs/frontend-system/architecture/15-plugins.md
+++ b/docs/frontend-system/architecture/15-plugins.md
@@ -129,26 +129,20 @@ A plugin might not always behave exactly the way you want. It could be that you
```tsx
import plugin from '@backstage/plugin-catalog';
+import { PageBlueprint } from '@backstage/frontend-plugin-api';
+import CustomCatalogIcon from '@material-ui/icons/Category';
export default plugin.withOverrides({
// These overrides are merged with the original extensions
extensions: [
- // Override the catalog nav item to use a custom icon
- plugin.getExtension('nav-item:catalog').override({
- factory: origFactory => [
- NavItemBlueprint.dataRefs.target({
- ...origFactory().get(NavItemBlueprint.dataRefs.target),
- icon: CustomCatalogIcon,
+ // Override the catalog index page with a custom icon and implementation
+ plugin.getExtension('page:catalog').override({
+ factory: origFactory =>
+ origFactory({
+ icon: ,
+ loader: () =>
+ import('./CustomCatalogIndexPage').then(m => ),
}),
- ],
- }),
- // Override the catalog index page with a completely custom implementation
- PageBlueprint.make({
- params: {
- path: '/catalog',
- routeRef: plugin.routes.catalogIndex,
- loader: () => import('./CustomCatalogIndexPage').then(m => ),
- },
}),
],
});
diff --git a/docs/frontend-system/building-apps/03-built-in-extensions.md b/docs/frontend-system/building-apps/03-built-in-extensions.md
index 637aded411..f61f3112bf 100644
--- a/docs/frontend-system/building-apps/03-built-in-extensions.md
+++ b/docs/frontend-system/building-apps/03-built-in-extensions.md
@@ -164,7 +164,7 @@ Extension responsible for rendering the logo and items in the app's sidebar.
| Name | Description | Type | Optional | Default | Extension creator |
| ------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
| content | Overrides the default content of the navbar. | [NavContentBlueprint.dataRefs.component](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.NavContentBlueprint.html) | true | - | [NavContentBlueprint](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.NavContentBlueprint.html) |
-| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) |
+| items | Legacy nav item target objects. | `core.nav-item.target` | true | - | Legacy extensions only; nav items are auto-discovered from page extensions. |
### App routes
diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md
index 7265cd7cf2..cbdc3d1f92 100644
--- a/docs/frontend-system/building-apps/08-migrating.md
+++ b/docs/frontend-system/building-apps/08-migrating.md
@@ -686,7 +686,7 @@ createApp({
#### App Root Sidebar
-New apps feature a built-in sidebar extension which is created by using the `NavContentBlueprint` in `src/modules/nav/Sidebar.tsx`. The default implementation of the sidebar in this blueprint will render some items explicitly in different groups, and then render the rest of the items. Nav items are auto-discovered from page extensions registered under `app/routes` (no explicit `NavItemBlueprint` required), with metadata from page config, nav item extensions, or plugin defaults.
+New apps feature a built-in sidebar extension which is created by using the `NavContentBlueprint` in `src/modules/nav/Sidebar.tsx`. The default implementation of the sidebar in this blueprint will render some items explicitly in different groups, and then render the rest of the items. Nav items are auto-discovered from page extensions registered under `app/routes`, with metadata from page config or plugin defaults.
In order to migrate your existing sidebar, you will want to create an override for the `app/nav` extension. You can do this by copying the standard of having a `src/modules/nav/` folder, which can contain an extension which you can install into the `app` in the form of a `module`.
@@ -740,14 +740,7 @@ The deprecated `items` prop (a flat list compatible with `: false`.
#### App Root Routes
diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md
index 7b41311ea4..60f6f6ea4a 100644
--- a/docs/frontend-system/building-plugins/01-index.md
+++ b/docs/frontend-system/building-plugins/01-index.md
@@ -46,7 +46,7 @@ The plugin ID should be a lowercase dash-separated string, while the plugin inst
The plugin that we created above is empty, and doesn't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/20-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page.
-To create a new extension you typically use pre-defined [extension blueprints](../architecture/23-extension-blueprints.md), provided either by the framework itself or by other plugins. In this case we'll use `PageBlueprint` and `NavItemBlueprint`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/36-routes.md#creating-a-route-reference) to use as a reference for our page, allowing us to dynamically create URLs that link to our page.
+To create a new extension you typically use pre-defined [extension blueprints](../architecture/23-extension-blueprints.md), provided either by the framework itself or by other plugins. In this case we'll use `PageBlueprint` from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/36-routes.md#creating-a-route-reference) to use as a reference for our page, allowing us to dynamically create URLs that link to our page.
```tsx title="in src/routes.ts"
import { createRouteRef } from '@backstage/frontend-plugin-api';
@@ -62,8 +62,8 @@ export const rootRouteRef = createRouteRef();
import {
createFrontendPlugin,
PageBlueprint,
- NavItemBlueprint,
} from '@backstage/frontend-plugin-api';
+import ExampleIcon from '@material-ui/icons/Extension';
import { rootRouteRef } from './routes';
// Note that these extensions aren't exported, only the plugin itself is.
@@ -75,6 +75,10 @@ const examplePage = PageBlueprint.make({
// This is the default path of this page, but integrators are free to override it
path: '/example',
+ // The title and icon are used to populate the app sidebar automatically
+ title: 'Example',
+ icon: ,
+
// Page extensions are always dynamically loaded using React.lazy().
// All of the functionality of this page is implemented in the
// ExamplePage component, which is a regular React component.
@@ -84,19 +88,10 @@ const examplePage = PageBlueprint.make({
},
});
-// This nav item is provided to the app.nav extension, and will by default be rendered as a sidebar item
-const exampleNavItem = NavItemBlueprint.make({
- params: {
- routeRef: rootRouteRef,
- title: 'Example',
- icon: ExampleIcon, // Custom SvgIcon, or one from the Material UI icon library
- },
-});
-
// The same plugin as above, now with the extensions added
export const examplePlugin = createFrontendPlugin({
pluginId: 'example',
- extensions: [examplePage, exampleNavItem],
+ extensions: [examplePage],
// We can also make routes available to other plugins.
// highlight-start
routes: {
diff --git a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md
index e8c9ea2ffb..f9a3bb696f 100644
--- a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md
+++ b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md
@@ -15,10 +15,6 @@ These are the [extension blueprints](../architecture/23-extension-blueprints.md)
An API extension is used to add or override [Utility API factories](../utility-apis/01-index.md) in the app. They are commonly used by plugins for both internal and shared APIs. There are also many built-in Api extensions provided by the framework that you are able to override.
-### NavItem (deprecated) - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.NavItemBlueprint.html)
-
-The `NavItemBlueprint` is deprecated. The app now auto-discovers navigation items from page extensions, so explicit nav item extensions are no longer needed. To migrate, ensure your plugin and/or page extensions have a `title` and `icon` set — these are used to populate the sidebar automatically.
-
### Page - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.PageBlueprint.html)
Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes. Pages automatically inherit the plugin's `title` and `icon` as defaults, which can be overridden per-page via `PageBlueprint` params.
diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs
index 58a48a3e95..fbcc18165e 100644
--- a/packages/create-app/templates/default-app/app-config.yaml.hbs
+++ b/packages/create-app/templates/default-app/app-config.yaml.hbs
@@ -6,12 +6,6 @@ app:
packages: all
extensions:
- # Disable the nav items that we're manually rendering in packages/app/src/modules/nav/Sidebar.tsx
- - nav-item:search: false
- - nav-item:user-settings: false
- - nav-item:catalog: false
- - nav-item:scaffolder: false
-
# Configure the catalog index page to be the root page, this is normally mounted on /catalog
- page:catalog:
config:
diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md
index e908bc7184..9c7911b8c8 100644
--- a/packages/frontend-plugin-api/report.api.md
+++ b/packages/frontend-plugin-api/report.api.md
@@ -1821,43 +1821,6 @@ export const microsoftAuthApiRef: ApiRef_2<
readonly $$type: '@backstage/ApiRef';
};
-// @public @deprecated
-export const NavItemBlueprint: ExtensionBlueprint_2<{
- kind: 'nav-item';
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- };
- output: ExtensionDataRef_2<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- dataRefs: {
- target: ConfigurableExtensionDataRef_2<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- },
- 'core.nav-item.target',
- {}
- >;
- };
-}>;
-
// @public (undocumented)
export const NotFoundErrorPage: {
(props: NotFoundErrorPageProps): JSX.Element | null;
diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx
deleted file mode 100644
index 0255d66604..0000000000
--- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2024 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { createExtensionTester } from '@backstage/frontend-test-utils';
-import { createRouteRef } from '../routing';
-import { NavItemBlueprint } from './NavItemBlueprint';
-
-describe('NavItemBlueprint', () => {
- const mockRouteRef = createRouteRef();
- const MockIcon = () => null;
-
- it('should return an extension with sensible defaults', () => {
- const extension = NavItemBlueprint.make({
- params: {
- icon: MockIcon,
- routeRef: mockRouteRef,
- title: 'TEST',
- },
- });
-
- expect(extension).toMatchInlineSnapshot(`
- {
- "$$type": "@backstage/ExtensionDefinition",
- "T": undefined,
- "attachTo": {
- "id": "app/nav",
- "input": "items",
- },
- "configSchema": {
- "parse": [Function],
- "schema": [Function],
- },
- "disabled": false,
- "factory": [Function],
- "if": undefined,
- "inputs": {},
- "kind": "nav-item",
- "name": undefined,
- "output": [
- [Function],
- ],
- "override": [Function],
- "toString": [Function],
- "version": "v2",
- }
- `);
- });
-
- it('should return the correct extension data', () => {
- const extension = NavItemBlueprint.make({
- params: {
- icon: MockIcon,
- routeRef: mockRouteRef,
- title: 'TEST',
- },
- });
-
- const tester = createExtensionTester(extension);
-
- expect(tester.get(NavItemBlueprint.dataRefs.target)).toEqual({
- title: 'TEST',
- icon: MockIcon,
- routeRef: mockRouteRef,
- });
- });
-
- it('should allow overriding of the title using config', () => {
- const extension = NavItemBlueprint.make({
- params: {
- icon: MockIcon,
- routeRef: mockRouteRef,
- title: 'TEST',
- },
- });
-
- const tester = createExtensionTester(extension, {
- config: { title: 'OVERRIDDEN' },
- });
-
- expect(tester.get(NavItemBlueprint.dataRefs.target)).toEqual({
- title: 'OVERRIDDEN',
- icon: MockIcon,
- routeRef: mockRouteRef,
- });
- });
-});
diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts
deleted file mode 100644
index d3e967fde8..0000000000
--- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2024 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { z } from 'zod/v4';
-import { IconComponent } from '../icons/types';
-import { RouteRef } from '../routing';
-import { createExtensionBlueprint, createExtensionDataRef } from '../wiring';
-
-// TODO(Rugvip): Should this be broken apart into separate refs? title/icon/routeRef
-const targetDataRef = createExtensionDataRef<{
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
-}>().with({ id: 'core.nav-item.target' });
-
-/**
- * Creates extensions that make up the items of the nav bar.
- *
- * @public
- * @deprecated Nav items are now automatically inferred from `PageBlueprint`
- * extensions based on their `title` and `icon` params. You can remove your
- * `NavItemBlueprint` usage and instead pass `title` and `icon` directly to
- * the `PageBlueprint`.
- */
-export const NavItemBlueprint = createExtensionBlueprint({
- kind: 'nav-item',
- attachTo: { id: 'app/nav', input: 'items' },
- output: [targetDataRef],
- dataRefs: {
- target: targetDataRef,
- },
- factory: (
- {
- icon,
- routeRef,
- title,
- }: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- },
- { config },
- ) => [
- targetDataRef({
- title: config.title ?? title,
- icon,
- routeRef,
- }),
- ],
- configSchema: {
- title: z.string().optional(),
- },
-});
diff --git a/packages/frontend-plugin-api/src/blueprints/index.ts b/packages/frontend-plugin-api/src/blueprints/index.ts
index a571f6ef73..df57ebb050 100644
--- a/packages/frontend-plugin-api/src/blueprints/index.ts
+++ b/packages/frontend-plugin-api/src/blueprints/index.ts
@@ -20,7 +20,6 @@ export {
} from './AnalyticsImplementationBlueprint';
export { ApiBlueprint } from './ApiBlueprint';
export { AppRootElementBlueprint } from './AppRootElementBlueprint';
-export { NavItemBlueprint } from './NavItemBlueprint';
export { PageBlueprint } from './PageBlueprint';
export { SubPageBlueprint } from './SubPageBlueprint';
export { PluginHeaderActionBlueprint } from './PluginHeaderActionBlueprint';
diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx
index fa4dc2023a..3d30ae3d6b 100644
--- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx
+++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx
@@ -22,12 +22,12 @@ import { ConfigReader } from '@backstage/config';
import { JsonObject } from '@backstage/types';
import {
createExtension,
+ createExtensionDataRef,
ExtensionDefinition,
coreExtensionData,
RouteRef,
useRouteRef,
IconComponent,
- NavItemBlueprint,
createFrontendPlugin,
FrontendFeature,
createFrontendModule,
@@ -49,6 +49,13 @@ const DEFAULT_MOCK_CONFIG = {
backend: { baseUrl: 'http://localhost:7007' },
};
+// Must match the data ref in @backstage/plugin-app/src/extensions/legacyNavItem.ts
+const legacyNavItemTargetDataRef = createExtensionDataRef<{
+ title: string;
+ icon: IconComponent;
+ routeRef: RouteRef;
+}>().with({ id: 'core.nav-item.target' });
+
/**
* Options to customize the behavior of the test app.
* @public
@@ -143,7 +150,7 @@ const appPluginOverride = appPlugin.withOverrides({
{inputs.items.map(
(item: (typeof inputs.items)[number], index: number) => {
const { icon, title, routeRef } = item.get(
- NavItemBlueprint.dataRefs.target,
+ legacyNavItemTargetDataRef,
);
return (
diff --git a/plugins/api-docs/README-alpha.md b/plugins/api-docs/README-alpha.md
index a4c3979787..939999aa71 100644
--- a/plugins/api-docs/README-alpha.md
+++ b/plugins/api-docs/README-alpha.md
@@ -32,7 +32,6 @@ To link that a component provides or consumes an API, see the [`providesApis`](h
- [Packages](#packages)
- [Routes](#routes)
- [Extensions](#extensions)
- - [Apis Nav Item](#apis-nav-item)
- [Apis Explorer Page](#apis-explore-page)
- [Apis Entity Cards](#apis-entities-cards)
- [Has Apis Entity Card](#has-apis-entity-card)
@@ -135,94 +134,6 @@ Route binding is also possible through code. For more information, see [this](ht
### Extensions
-#### Apis Nav Item
-
-This [nav item](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) extension adds a link to the Apis Explorer page in the main app sidebar.
-
-| Kind | Namespace | Name | Id |
-| ---------- | ---------- | ---- | ------------------- |
-| `nav-item` | `api-docs` | - | `nav-item:api-docs` |
-
-##### Disable
-
-This extension is enabled by default when you install the `api-docs` plugin, but you can disable it and prevent it from showing up in the sidebar by setting the following configuration:
-
-```yaml
-# app-config.yaml
-app:
- extensions:
- # this is the extension id and it follows the naming pattern bellow:
- # /:
- # example disabling the apis docs nav item extension
- - nav-item:api-docs: false
- # or
- # - nav-item:api-docs:
- # disabled: true
-```
-
-To enable the extension again, simple remove the previous `nav-item:api-docs: false` configuration or do:
-
-```yaml
-# app-config.yaml
-app:
- extensions:
- # this is the extension id and it follows the naming pattern bellow:
- # /:
- - nav-item:api-docs
- # or
- # - nav-item:api-docs: true
- # or
- # - nav-item:api-docs:
- # disabled: false
-```
-
-##### Config
-
-The apis nav item can be customized under the `app.extensions.nav-item:api-docs.config` key in `app-config.yaml`. Configurations include:
-
-```yaml
-# app-config.yaml
-# example configuring the apis docs nav item extension
-app:
- extensions:
- # this is the extension id and it follows the naming pattern bellow:
- # /:
- - nav-item:api-docs:
- config:
- # The nav item title text, defaults to "APIs"
- title: 'Apis Explorer'
- # The nav item path text, defaults to "/api-docs"
- path: '/apis-explorer'
-```
-
-##### Override
-
-The apis nav item icon can only be changed by overriding the extension, as the icon cannot be changed via the `app-config.yaml` file.
-
-Here is an example overriding the nav item extension with a custom icon component:
-
-```tsx
-import {
- createFrontendModule,
- createNavItemExtension,
-} from '@backstage/backstage-plugin-api';
-import { MyCustomApiDocsIcon } from './components';
-
-export default createFrontendModule({
- pluginId: 'api-docs',
- extensions: [
- createNavItemExtension({
- // It's your choice whether to use the original extension's title or a different one
- title: 'APIs',
- // Setting a custom icon component
- icon: MyCustomApiDocsIcon,
- }),
- ],
-});
-```
-
-For more information about where to place extension overrides, see the official [documentation](https://backstage.io/docs/frontend-system/architecture/extension-overrides).
-
#### Apis Explore Page
This `api-docs` plugin installs an "Apis Explore" page extension that helps you visualize apis registered in the Backstage software catalog.
@@ -235,9 +146,6 @@ This `api-docs` plugin installs an "Apis Explore" page extension that helps you
The explore page extension is enable by default when you install the `api-docs` plugin, for disabling it, set the configuration below:
-> [!CAUTION]
-> The `api-docs` plugin also install a sidebar item that points to this page, remember to disable the nav item as well otherwise it will point to a not found page.
-
```yaml
# app-config.yaml
# example disabling the apis docs explorer page
diff --git a/plugins/api-docs/report-alpha.api.md b/plugins/api-docs/report-alpha.api.md
index 630e6aef89..4e3930d765 100644
--- a/plugins/api-docs/report-alpha.api.md
+++ b/plugins/api-docs/report-alpha.api.md
@@ -15,7 +15,6 @@ import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
import { ExternalRouteRef } from '@backstage/core-plugin-api';
import { FilterPredicate } from '@backstage/filter-predicates';
-import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react';
import { JSXElementConstructor } from 'react';
@@ -472,31 +471,6 @@ const _default: OverridableFrontendPlugin<
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
};
}>;
- 'nav-item:api-docs': OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- };
- }>;
'page:api-docs': OverridableExtensionDefinition<{
config: {
initiallySelectedFilter: 'all' | 'owned' | 'starred' | undefined;
diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx
index af4190bd13..b810be016e 100644
--- a/plugins/api-docs/src/alpha.tsx
+++ b/plugins/api-docs/src/alpha.tsx
@@ -18,7 +18,6 @@ import Grid from '@material-ui/core/Grid';
import {
ApiBlueprint,
- NavItemBlueprint,
PageBlueprint,
createFrontendPlugin,
} from '@backstage/frontend-plugin-api';
@@ -39,14 +38,6 @@ import {
EntityContentBlueprint,
} from '@backstage/plugin-catalog-react/alpha';
-const apiDocsNavItem = NavItemBlueprint.make({
- params: {
- title: 'APIs',
- routeRef: rootRoute,
- icon: () => ,
- },
-});
-
const apiDocsConfigApi = ApiBlueprint.make({
name: 'config',
params: defineParams =>
@@ -76,6 +67,8 @@ const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({
return originalFactory({
path: '/api-docs',
routeRef: rootRoute,
+ title: 'APIs',
+ icon: ,
loader: () =>
import('./components/ApiExplorerPage/DefaultApiExplorerPage').then(
m => (
@@ -222,7 +215,6 @@ export default createFrontendPlugin({
registerApi: registerComponentRouteRef,
},
extensions: [
- apiDocsNavItem,
apiDocsConfigApi,
apiDocsExplorerPage,
apiDocsHasApisEntityCard,
diff --git a/plugins/app-visualizer/README.md b/plugins/app-visualizer/README.md
index 865cd66746..cf15cdce71 100644
--- a/plugins/app-visualizer/README.md
+++ b/plugins/app-visualizer/README.md
@@ -4,7 +4,6 @@ A plugin to help explore the structure of your Backstage app.
This plugin provides the following extensions:
-| ID | Type | Description | Default Config |
-| ------------------------- | --------- | ------------------------------------ | ------------------------- |
-| `page:app-visualizer` | `Page` | The app visualizer page | `{ path: '/visualizer' }` |
-| `nav-item:app-visualizer` | `NavItem` | Nav item for the app visualizer page | |
+| ID | Type | Description | Default Config |
+| --------------------- | ------ | ----------------------- | ------------------------- |
+| `page:app-visualizer` | `Page` | The app visualizer page | `{ path: '/visualizer' }` |
diff --git a/plugins/app-visualizer/report.api.md b/plugins/app-visualizer/report.api.md
index f8636d8b4e..07a8fcef62 100644
--- a/plugins/app-visualizer/report.api.md
+++ b/plugins/app-visualizer/report.api.md
@@ -7,7 +7,6 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
-import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react';
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
@@ -19,31 +18,6 @@ const visualizerPlugin: OverridableFrontendPlugin<
{},
{},
{
- 'nav-item:app-visualizer': OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- };
- }>;
'page:app-visualizer': OverridableExtensionDefinition<{
kind: 'page';
name: undefined;
diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx
index e88f994627..d3c6bab057 100644
--- a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx
+++ b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx
@@ -19,11 +19,15 @@ import {
ExtensionDataRef,
coreExtensionData,
ApiBlueprint,
- NavItemBlueprint,
useApi,
routeResolutionApiRef,
appTreeApiRef,
+ createExtensionDataRef,
} from '@backstage/frontend-plugin-api';
+
+const legacyNavItemTargetDataRef = createExtensionDataRef().with({
+ id: 'core.nav-item.target',
+});
import {
Box,
Flex,
@@ -86,7 +90,7 @@ const getOutputColor = createOutputColorGenerator(
[coreExtensionData.routePath.id]: '#ffeb3b',
[coreExtensionData.routeRef.id]: '#9c27b0',
[ApiBlueprint.dataRefs.factory.id]: '#2196f3',
- [NavItemBlueprint.dataRefs.target.id]: '#ff9800',
+ [legacyNavItemTargetDataRef.id]: '#ff9800',
},
['#90caf9', '#ffcc80', '#a5d6a7', '#ef9a9a', '#fff59d', '#ce93d8', '#e6ee9c'],
@@ -335,7 +339,7 @@ const legendMap = {
'Utility API': ApiBlueprint.dataRefs.factory,
'Route Path': coreExtensionData.routePath,
'Route Ref': coreExtensionData.routeRef,
- 'Nav Target': NavItemBlueprint.dataRefs.target,
+ 'Nav Target': legacyNavItemTargetDataRef,
};
function Legend() {
diff --git a/plugins/app-visualizer/src/plugin.tsx b/plugins/app-visualizer/src/plugin.tsx
index fab8479c54..7a33d7f32e 100644
--- a/plugins/app-visualizer/src/plugin.tsx
+++ b/plugins/app-visualizer/src/plugin.tsx
@@ -17,7 +17,6 @@
import {
createFrontendPlugin,
createRouteRef,
- NavItemBlueprint,
PageBlueprint,
PluginHeaderActionBlueprint,
SubPageBlueprint,
@@ -31,6 +30,7 @@ const appVisualizerPage = PageBlueprint.make({
path: '/visualizer',
routeRef: rootRouteRef,
title: 'Visualizer',
+ icon: ,
},
});
@@ -82,14 +82,6 @@ const copyTreeAsJson = PluginHeaderActionBlueprint.make({
},
});
-export const appVisualizerNavItem = NavItemBlueprint.make({
- params: {
- title: 'Visualizer',
- icon: () => ,
- routeRef: rootRouteRef,
- },
-});
-
/** @public */
export const visualizerPlugin = createFrontendPlugin({
pluginId: 'app-visualizer',
@@ -101,7 +93,6 @@ export const visualizerPlugin = createFrontendPlugin({
appVisualizerTreePage,
appVisualizerDetailedPage,
appVisualizerTextPage,
- appVisualizerNavItem,
copyTreeAsJson,
],
});
diff --git a/plugins/app/src/extensions/AppNav.test.tsx b/plugins/app/src/extensions/AppNav.test.tsx
index 387c75a87a..c1434d7bfc 100644
--- a/plugins/app/src/extensions/AppNav.test.tsx
+++ b/plugins/app/src/extensions/AppNav.test.tsx
@@ -18,38 +18,46 @@ import { screen, waitFor, within } from '@testing-library/react';
import { renderTestApp } from '@backstage/frontend-test-utils';
import {
PageBlueprint,
- NavItemBlueprint,
+ createExtension,
createRouteRef,
} from '@backstage/frontend-plugin-api';
+import { legacyNavItemTargetDataRef } from './legacyNavItem';
const DEFAULT_CONFIG = {
app: { baseUrl: 'http://localhost:3000' },
backend: { baseUrl: 'http://localhost:7007' },
};
+const mockRouteRef = createRouteRef();
+
const mockPage = PageBlueprint.make({
name: 'my-plugin',
params: {
title: 'My Plugin',
icon: icon,
path: '/my-plugin',
- routeRef: createRouteRef(),
+ routeRef: mockRouteRef,
},
});
-const mockNavItem = NavItemBlueprint.make({
+const mockLegacyNavItem = createExtension({
+ kind: 'nav-item',
name: 'my-plugin',
- params: {
- title: 'My Plugin',
- icon: () => icon,
- routeRef: createRouteRef(),
- },
+ attachTo: { id: 'app/nav', input: 'items' },
+ output: [legacyNavItemTargetDataRef],
+ factory: () => [
+ legacyNavItemTargetDataRef({
+ title: 'Legacy Nav Title',
+ icon: () => legacy icon,
+ routeRef: mockRouteRef,
+ }),
+ ],
});
describe('AppNav', () => {
- it('should show a nav item for a page with an enabled nav-item extension', async () => {
+ it('should show a nav item for a page with title and icon', async () => {
renderTestApp({
- extensions: [mockPage, mockNavItem],
+ extensions: [mockPage],
config: DEFAULT_CONFIG,
});
@@ -60,40 +68,24 @@ describe('AppNav', () => {
});
});
- it('should hide a nav item when its nav-item extension is disabled via config', async () => {
- renderTestApp({
- extensions: [mockPage, mockNavItem],
- config: {
- ...DEFAULT_CONFIG,
- app: {
- ...DEFAULT_CONFIG.app,
- extensions: [{ 'nav-item:test/my-plugin': false }],
- },
+ it('should merge legacy nav item metadata when page has no explicit title', async () => {
+ const pageWithoutTitle = PageBlueprint.make({
+ name: 'legacy-plugin',
+ params: {
+ path: '/legacy-plugin',
+ routeRef: mockRouteRef,
+ icon: page icon,
},
});
+ renderTestApp({
+ extensions: [pageWithoutTitle, mockLegacyNavItem],
+ config: DEFAULT_CONFIG,
+ });
+
await waitFor(() => {
expect(
- within(screen.getByRole('navigation')).queryByText('My Plugin'),
- ).not.toBeInTheDocument();
- });
- });
-
- it('should still show a nav item for a page without a nav-item extension', async () => {
- renderTestApp({
- extensions: [mockPage],
- config: {
- ...DEFAULT_CONFIG,
- app: {
- ...DEFAULT_CONFIG.app,
- extensions: [{ 'nav-item:test/my-plugin': false }],
- },
- },
- });
-
- await waitFor(() => {
- expect(
- within(screen.getByRole('navigation')).getByText('My Plugin'),
+ within(screen.getByRole('navigation')).getByText('Legacy Nav Title'),
).toBeInTheDocument();
});
});
diff --git a/plugins/app/src/extensions/AppNav.tsx b/plugins/app/src/extensions/AppNav.tsx
index 09203303e5..4b5c456436 100644
--- a/plugins/app/src/extensions/AppNav.tsx
+++ b/plugins/app/src/extensions/AppNav.tsx
@@ -18,7 +18,6 @@ import {
createExtension,
coreExtensionData,
createExtensionInput,
- NavItemBlueprint,
routeResolutionApiRef,
appTreeApiRef,
IconComponent,
@@ -27,6 +26,7 @@ import {
RouteResolutionApi,
useApi,
} from '@backstage/frontend-plugin-api';
+import { legacyNavItemTargetDataRef } from './legacyNavItem';
import {
NavContentBlueprint,
NavContentComponent,
@@ -248,7 +248,7 @@ export const AppNav = createExtension({
name: 'nav',
attachTo: { id: 'app/layout', input: 'nav' },
inputs: {
- items: createExtensionInput([NavItemBlueprint.dataRefs.target]),
+ items: createExtensionInput([legacyNavItemTargetDataRef]),
content: createExtensionInput([NavContentBlueprint.dataRefs.component], {
singleton: true,
optional: true,
@@ -264,7 +264,7 @@ export const AppNav = createExtension({
yield coreExtensionData.reactElement(
- item.get(NavItemBlueprint.dataRefs.target),
+ item.get(legacyNavItemTargetDataRef),
)}
Content={Content}
/>,
diff --git a/plugins/catalog/src/alpha/navItems.tsx b/plugins/app/src/extensions/legacyNavItem.ts
similarity index 53%
rename from plugins/catalog/src/alpha/navItems.tsx
rename to plugins/app/src/extensions/legacyNavItem.ts
index e72c20ebf4..bda23dabe8 100644
--- a/plugins/catalog/src/alpha/navItems.tsx
+++ b/plugins/app/src/extensions/legacyNavItem.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,20 @@
* limitations under the License.
*/
-import HomeIcon from '@material-ui/icons/Home';
-import { NavItemBlueprint } from '@backstage/frontend-plugin-api';
-import { rootRouteRef } from '../routes';
+import {
+ createExtensionDataRef,
+ IconComponent,
+ RouteRef,
+} from '@backstage/frontend-plugin-api';
-export const catalogNavItem = NavItemBlueprint.make({
- params: {
- routeRef: rootRouteRef,
- title: 'Catalog',
- icon: HomeIcon,
- },
-});
-
-export default [catalogNavItem];
+/**
+ * @internal
+ *
+ * Data ref for legacy nav-item extensions. Kept for backward compatibility with
+ * extensions created by older versions of the framework.
+ */
+export const legacyNavItemTargetDataRef = createExtensionDataRef<{
+ title: string;
+ icon: IconComponent;
+ routeRef: RouteRef;
+}>().with({ id: 'core.nav-item.target' });
diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md
index 23d7e7b931..ef72269e22 100644
--- a/plugins/catalog-unprocessed-entities/report-alpha.api.md
+++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md
@@ -10,7 +10,6 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
-import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react';
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
@@ -40,31 +39,6 @@ const _default: OverridableFrontendPlugin<
params: ApiFactory,
) => ExtensionBlueprintParams;
}>;
- 'nav-item:catalog-unprocessed-entities': OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- };
- }>;
'page:catalog-unprocessed-entities': OverridableExtensionDefinition<{
kind: 'page';
name: undefined;
diff --git a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx
index 1bf2bc2bc4..8b829511e0 100644
--- a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx
+++ b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx
@@ -20,7 +20,6 @@ import {
fetchApiRef,
ApiBlueprint,
PageBlueprint,
- NavItemBlueprint,
SubPageBlueprint,
} from '@backstage/frontend-plugin-api';
@@ -52,6 +51,8 @@ export const catalogUnprocessedEntitiesPage = PageBlueprint.make({
params: {
path: '/catalog-unprocessed-entities',
routeRef: rootRouteRef,
+ title: 'Unprocessed Entities',
+ icon: ,
loader: () =>
import('../components/UnprocessedEntities').then(m => (
@@ -59,15 +60,6 @@ export const catalogUnprocessedEntitiesPage = PageBlueprint.make({
},
});
-/** @alpha */
-export const catalogUnprocessedEntitiesNavItem = NavItemBlueprint.make({
- params: {
- title: 'Unprocessed Entities',
- routeRef: rootRouteRef,
- icon: QueueIcon,
- },
-});
-
/**
* DevTools content for catalog unprocessed entities.
*
@@ -99,7 +91,6 @@ export default createFrontendPlugin({
extensions: [
catalogUnprocessedEntitiesApi,
catalogUnprocessedEntitiesPage,
- catalogUnprocessedEntitiesNavItem,
unprocessedEntitiesDevToolsContent,
],
});
diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md
index b3074b11bf..a682ddc9c6 100644
--- a/plugins/catalog/report-alpha.api.md
+++ b/plugins/catalog/report-alpha.api.md
@@ -21,7 +21,6 @@ import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
import { ExternalRouteRef } from '@backstage/core-plugin-api';
import { FilterPredicate } from '@backstage/filter-predicates';
-import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { IconLinkVerticalProps } from '@backstage/core-components';
import { JSX as JSX_2 } from 'react';
@@ -1027,31 +1026,6 @@ const _default: OverridableFrontendPlugin<
filter?: FilterPredicate | ((entity: Entity) => boolean);
};
}>;
- 'nav-item:catalog': OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- };
- }>;
'page:catalog': OverridableExtensionDefinition<{
config: {
pagination:
diff --git a/plugins/catalog/src/alpha/plugin.tsx b/plugins/catalog/src/alpha/plugin.tsx
index 243b93db9b..b2ea8d3eca 100644
--- a/plugins/catalog/src/alpha/plugin.tsx
+++ b/plugins/catalog/src/alpha/plugin.tsx
@@ -29,7 +29,6 @@ import {
import apis from './apis';
import pages from './pages';
import filters from './filters';
-import navItems from './navItems';
import entityCards from './entityCards';
import entityContents from './entityContents';
import entityIconLinks from './entityIconLinks';
@@ -58,7 +57,6 @@ export default createFrontendPlugin({
...apis,
...pages,
...filters,
- ...navItems,
...entityCards,
...entityContents,
...entityIconLinks,
diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md
index b911a82b48..2c3006ea12 100644
--- a/plugins/devtools/report-alpha.api.md
+++ b/plugins/devtools/report-alpha.api.md
@@ -10,7 +10,6 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
-import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react';
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
@@ -40,31 +39,6 @@ const _default: OverridableFrontendPlugin<
params: ApiFactory,
) => ExtensionBlueprintParams;
}>;
- 'nav-item:devtools': OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- };
- }>;
'page:devtools': OverridableExtensionDefinition<{
config: {
path: string | undefined;
diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx
index 0144308069..292abd282f 100644
--- a/plugins/devtools/src/alpha/plugin.tsx
+++ b/plugins/devtools/src/alpha/plugin.tsx
@@ -21,7 +21,6 @@ import {
fetchApiRef,
ApiBlueprint,
PageBlueprint,
- NavItemBlueprint,
SubPageBlueprint,
} from '@backstage/frontend-plugin-api';
@@ -72,6 +71,7 @@ export const devToolsPage = PageBlueprint.makeWithOverrides({
path: '/devtools',
routeRef: rootRouteRef,
title: 'DevTools',
+ icon: ,
},
{
inputs: {
@@ -133,15 +133,6 @@ export const devToolsScheduledTasksPage = SubPageBlueprint.make({
},
});
-/** @alpha */
-export const devToolsNavItem = NavItemBlueprint.make({
- params: {
- title: 'DevTools',
- routeRef: rootRouteRef,
- icon: BuildIcon,
- },
-});
-
/** @alpha */
export default createFrontendPlugin({
pluginId: 'devtools',
@@ -157,6 +148,5 @@ export default createFrontendPlugin({
devToolsInfoPage,
devToolsConfigPage,
devToolsScheduledTasksPage,
- devToolsNavItem,
],
});
diff --git a/plugins/home/report-alpha.api.md b/plugins/home/report-alpha.api.md
index 1014c626a2..179799cd24 100644
--- a/plugins/home/report-alpha.api.md
+++ b/plugins/home/report-alpha.api.md
@@ -13,7 +13,6 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api';
import { HomePageLayoutProps } from '@backstage/plugin-home-react/alpha';
import { HomePageWidgetBlueprintParams } from '@backstage/plugin-home-react/alpha';
import { HomePageWidgetData } from '@backstage/plugin-home-react/alpha';
-import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react';
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
@@ -81,31 +80,6 @@ const _default: OverridableFrontendPlugin<
inputs: {};
params: HomePageWidgetBlueprintParams;
}>;
- 'nav-item:home': OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- };
- }>;
'page:home': OverridableExtensionDefinition<{
config: {
path: string | undefined;
diff --git a/plugins/home/src/alpha.test.ts b/plugins/home/src/alpha.test.ts
index caa7d45cf7..900d886539 100644
--- a/plugins/home/src/alpha.test.ts
+++ b/plugins/home/src/alpha.test.ts
@@ -38,10 +38,6 @@ describe('Home Plugin Alpha', () => {
it('should export core home page extension', () => {
expect(homePlugin.getExtension('page:home')).toBeDefined();
});
-
- it('should export navigation item extension', () => {
- expect(homePlugin.getExtension('nav-item:home')).toBeDefined();
- });
});
describe('Optional Extensions (Disabled by Default)', () => {
@@ -99,7 +95,6 @@ describe('Home Plugin Alpha', () => {
it('should include all extensions in the correct order', () => {
// Core extensions (always enabled)
expect(homePlugin.getExtension('page:home')).toBeDefined();
- expect(homePlugin.getExtension('nav-item:home')).toBeDefined();
// Optional extensions (disabled by default)
expect(homePlugin.getExtension('api:home/visits')).toBeDefined();
diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx
index bdce683cee..2a9e86425f 100644
--- a/plugins/home/src/alpha.tsx
+++ b/plugins/home/src/alpha.tsx
@@ -28,7 +28,6 @@ import { lazy as reactLazy } from 'react';
import {
createExtensionInput,
PageBlueprint,
- NavItemBlueprint,
createFrontendPlugin,
createRouteRef,
AppRootElementBlueprint,
@@ -65,6 +64,8 @@ const homePage = PageBlueprint.makeWithOverrides({
path: '/home',
noHeader: true,
routeRef: rootRouteRef,
+ title: 'Home',
+ icon: ,
loader: async () => {
const LazyDefaultLayout = reactLazy(() =>
import('./alpha/DefaultHomePageLayout').then(m => ({
@@ -122,14 +123,6 @@ const visitsApi = ApiBlueprint.make({
}),
});
-const homeNavItem = NavItemBlueprint.make({
- params: {
- title: 'Home',
- routeRef: rootRouteRef,
- icon: HomeIcon,
- },
-});
-
const homePageToolkitWidget = HomePageWidgetBlueprint.make({
name: 'toolkit',
params: {
@@ -213,7 +206,6 @@ export default createFrontendPlugin({
info: { packageJson: () => import('../package.json') },
extensions: [
homePage,
- homeNavItem,
visitsApi,
visitListenerAppRootElement,
homePageToolkitWidget,
diff --git a/plugins/org/README-alpha.md b/plugins/org/README-alpha.md
index db8a84f35c..0f112b943a 100644
--- a/plugins/org/README-alpha.md
+++ b/plugins/org/README-alpha.md
@@ -305,67 +305,37 @@ For more information about where to place extension overrides, see the official
### My Groups Sidebar Item
-As the [NavItem](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) extension type does not support conditional rendering, this plugin does not provide navigation items, so to use the `MyGroupsSidebarItem` component, we recommend overriding the [App/Nav](https://backstage.io/docs/frontend-system/building-apps/built-in-extensions#app-nav) extension and adding the item statically.
-
-> [!IMPORTANT]
-> As you can see in the example below, we are using the same attachment point, inputs and outputs as the default App/Nav extension to avoid side effects on the NavItem and NavLogo extensions.
+This plugin does not provide a page extension for the groups sidebar item, since it requires conditional rendering based on the logged-in user. To use the `MyGroupsSidebarItem` component, add it to your custom sidebar implementation using the `NavContentBlueprint` in `packages/app/src/modules/nav/Sidebar.tsx`:
```tsx
-// ...
import { MyGroupsSidebarItem } from '@backstage/plugin-org';
import GroupIcon from '@material-ui/icons/People';
+import { NavContentBlueprint } from '@backstage/plugin-app-react';
-export default createFrontendModule({
- pluginId: 'app',
- extensions: [
- createExtension({
- // Name is necessary so the system knows that this extension will override the default app nav extension
- name: 'nav',
- // Keeping the same attachment point as in the default App/Nav extension
- attachTo: { id: 'app/layout', input: 'nav' },
- // Keeping the same inputs as in the default App/Nav extension
- inputs: {
- items: createExtensionInput({
- target: createNavItemExtension.targetDataRef,
- }),
- logos: createExtensionInput(
- {
- elements: createNavLogoExtension.logoElementsDataRef,
- },
- {
- singleton: true,
- optional: true,
- },
- ),
- },
- // Keeping the same output as in the default App/Nav extension
- output: {
- element: coreExtensionData.reactElement,
- },
- factory({ inputs }) {
- return {
- element: (
-
- {/* Code borrowed from the default extension implementation to render the logos and items inputs */}
-
-
- {inputs.items.map((item, index) => (
-
- ))}
- {/* Here is where we actually modifies the default implementation by adding a static item to render a group of squad pages */}
- }>
- {/* The MyGroupsSidebarItem provides quick access to the group(s) the logged in user is a member of directly in the sidebar. */}
-
-
-
- ),
- };
- },
- }),
- ],
+export const SidebarContent = NavContentBlueprint.make({
+ params: {
+ component: ({ navItems }) => {
+ const nav = navItems.withComponent(item => (
+ item.icon} to={item.href} text={item.title} />
+ ));
+
+ return (
+
+
+
+ }>
+ {nav.rest()}
+
+
+
+ );
+ },
+ },
});
```
+
+For more details on customizing the sidebar, see the [app migration guide](https://backstage.io/docs/frontend-system/building-apps/migrating#app-root-sidebar).
diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md
index 471f460241..31cee88452 100644
--- a/plugins/scaffolder/report-alpha.api.md
+++ b/plugins/scaffolder/report-alpha.api.md
@@ -20,7 +20,6 @@ import { FormField } from '@backstage/plugin-scaffolder-react/alpha';
import { formFieldsApiRef } from '@backstage/plugin-scaffolder-react/alpha';
import type { FormProps as FormProps_2 } from '@rjsf/core';
import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react';
-import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { IconLinkVerticalProps } from '@backstage/core-components';
import { JSX as JSX_2 } from 'react';
@@ -173,31 +172,6 @@ const _default: OverridableFrontendPlugin<
filter?: FilterPredicate | ((entity: Entity) => boolean);
};
}>;
- 'nav-item:scaffolder': OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- };
- }>;
'page:scaffolder': OverridableExtensionDefinition<{
config: {
path: string | undefined;
diff --git a/plugins/scaffolder/src/alpha/extensions.tsx b/plugins/scaffolder/src/alpha/extensions.tsx
index 1694114e00..8523fd9c13 100644
--- a/plugins/scaffolder/src/alpha/extensions.tsx
+++ b/plugins/scaffolder/src/alpha/extensions.tsx
@@ -20,7 +20,6 @@ import {
discoveryApiRef,
fetchApiRef,
identityApiRef,
- NavItemBlueprint,
PageBlueprint,
SubPageBlueprint,
} from '@backstage/frontend-plugin-api';
@@ -52,7 +51,8 @@ export const scaffolderPage = PageBlueprint.makeWithOverrides({
return originalFactory({
routeRef: rootRouteRef,
path: '/create',
- title: 'Create',
+ title: 'Create...',
+ icon: ,
});
},
});
@@ -154,14 +154,6 @@ export const scaffolderTemplatingExtensionsSubPage = SubPageBlueprint.make({
},
});
-export const scaffolderNavItem = NavItemBlueprint.make({
- params: {
- routeRef: rootRouteRef,
- title: 'Create...',
- icon: CreateComponentIcon,
- },
-});
-
export const repoUrlPickerFormField = FormFieldBlueprint.make({
name: 'repo-url-picker',
params: {
diff --git a/plugins/scaffolder/src/alpha/plugin.tsx b/plugins/scaffolder/src/alpha/plugin.tsx
index af79ea416a..18298e16c3 100644
--- a/plugins/scaffolder/src/alpha/plugin.tsx
+++ b/plugins/scaffolder/src/alpha/plugin.tsx
@@ -39,7 +39,6 @@ import {
repoOwnerPickerFormField,
repoUrlPickerFormField,
scaffolderApi,
- scaffolderNavItem,
scaffolderPage,
scaffolderTemplatesSubPage,
scaffolderTasksSubPage,
@@ -89,7 +88,6 @@ export default createFrontendPlugin({
scaffolderActionsSubPage,
scaffolderEditorSubPage,
scaffolderTemplatingExtensionsSubPage,
- scaffolderNavItem,
scaffolderEntityIconLink,
formDecoratorsApi,
formFieldsApi,
diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md
index 9639741350..b025b9498a 100644
--- a/plugins/search/report-alpha.api.md
+++ b/plugins/search/report-alpha.api.md
@@ -10,7 +10,6 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
-import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react';
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
@@ -44,31 +43,6 @@ const _default: OverridableFrontendPlugin<
params: ApiFactory,
) => ExtensionBlueprintParams;
}>;
- 'nav-item:search': OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- };
- }>;
'page:search': OverridableExtensionDefinition<{
config: {
noTrack: boolean;
@@ -214,33 +188,6 @@ export const searchApi: OverridableExtensionDefinition<{
) => ExtensionBlueprintParams;
}>;
-// @alpha (undocumented)
-export const searchNavItem: OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- };
-}>;
-
// @alpha (undocumented)
export const searchPage: OverridableExtensionDefinition<{
config: {
diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx
index a981afa1de..5b93fa6e1e 100644
--- a/plugins/search/src/alpha.tsx
+++ b/plugins/search/src/alpha.tsx
@@ -37,7 +37,6 @@ import {
ApiBlueprint,
createExtensionInput,
PageBlueprint,
- NavItemBlueprint,
configApiRef,
} from '@backstage/frontend-plugin-api';
@@ -110,6 +109,8 @@ export const searchPage = PageBlueprint.makeWithOverrides({
return originalFactory({
path: '/search',
routeRef: rootRouteRef,
+ title: 'Search',
+ icon: ,
loader: async () => {
const getResultItemComponent = (result: SearchResult) => {
const value = inputs.items.find(item =>
@@ -258,22 +259,13 @@ export const searchPage = PageBlueprint.makeWithOverrides({
},
});
-/** @alpha */
-export const searchNavItem = NavItemBlueprint.make({
- params: {
- routeRef: rootRouteRef,
- title: 'Search',
- icon: SearchIcon,
- },
-});
-
/** @alpha */
export default createFrontendPlugin({
pluginId: 'search',
title: 'Search',
icon: ,
info: { packageJson: () => import('../package.json') },
- extensions: [searchApi, searchPage, searchNavItem],
+ extensions: [searchApi, searchPage],
routes: {
root: rootRouteRef,
},
diff --git a/plugins/techdocs/report-alpha.api.md b/plugins/techdocs/report-alpha.api.md
index 81d6b5a547..2c166067b3 100644
--- a/plugins/techdocs/report-alpha.api.md
+++ b/plugins/techdocs/report-alpha.api.md
@@ -13,7 +13,6 @@ import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
import { FilterPredicate } from '@backstage/filter-predicates';
-import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { IconLinkVerticalProps } from '@backstage/core-components';
import { JSX as JSX_2 } from 'react';
@@ -259,31 +258,6 @@ const _default: OverridableFrontendPlugin<
filter?: FilterPredicate | ((entity: Entity) => boolean);
};
}>;
- 'nav-item:techdocs': OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef_2;
- };
- }>;
'page:techdocs': OverridableExtensionDefinition<{
kind: 'page';
name: undefined;
diff --git a/plugins/techdocs/src/alpha/index.tsx b/plugins/techdocs/src/alpha/index.tsx
index 6f77d1731e..aeda900a7f 100644
--- a/plugins/techdocs/src/alpha/index.tsx
+++ b/plugins/techdocs/src/alpha/index.tsx
@@ -21,7 +21,6 @@ import {
createFrontendPlugin,
ApiBlueprint,
PageBlueprint,
- NavItemBlueprint,
PluginHeaderActionBlueprint,
createExtensionInput,
coreExtensionData,
@@ -139,6 +138,8 @@ const techDocsPage = PageBlueprint.make({
params: {
path: '/docs',
routeRef: rootRouteRef,
+ title: 'Docs',
+ icon: ,
loader: () =>
import('./components/TechDocsIndexPageContent').then(m => (
@@ -265,15 +266,6 @@ const techDocsEntityContentEmptyState = createExtension({
factory: () => [],
});
-/** @alpha */
-const techDocsNavItem = NavItemBlueprint.make({
- params: {
- icon: () => ,
- title: 'Docs',
- routeRef: rootRouteRef,
- },
-});
-
const techDocsSupportAction = PluginHeaderActionBlueprint.make({
params: defineParams =>
defineParams({
@@ -293,7 +285,6 @@ export default createFrontendPlugin({
techDocsClientApi,
techDocsStorageApi,
TechDocsAddonsApiExtension,
- techDocsNavItem,
techDocsSupportAction,
techDocsPage,
techDocsReaderPage,
diff --git a/plugins/user-settings/report-alpha.api.md b/plugins/user-settings/report-alpha.api.md
index aa5b0e0237..de8b37a6d7 100644
--- a/plugins/user-settings/report-alpha.api.md
+++ b/plugins/user-settings/report-alpha.api.md
@@ -7,47 +7,21 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
-import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react';
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api';
-import { RouteRef } from '@backstage/frontend-plugin-api';
-import { RouteRef as RouteRef_2 } from '@backstage/core-plugin-api';
+import { RouteRef } from '@backstage/core-plugin-api';
+import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api';
import { TranslationRef } from '@backstage/frontend-plugin-api';
// @alpha (undocumented)
const _default: OverridableFrontendPlugin<
{
- root: RouteRef_2;
+ root: RouteRef;
},
{},
{
- 'nav-item:user-settings': OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- };
- }>;
'page:user-settings': OverridableExtensionDefinition<{
kind: 'page';
name: undefined;
@@ -62,7 +36,7 @@ const _default: OverridableFrontendPlugin<
output:
| ExtensionDataRef
| ExtensionDataRef<
- RouteRef,
+ RouteRef_2,
'core.routing.ref',
{
optional: true;
@@ -88,7 +62,7 @@ const _default: OverridableFrontendPlugin<
| ConfigurableExtensionDataRef
| ConfigurableExtensionDataRef
| ConfigurableExtensionDataRef<
- RouteRef,
+ RouteRef_2,
'core.routing.ref',
{
optional: true;
@@ -120,7 +94,7 @@ const _default: OverridableFrontendPlugin<
title?: string;
icon?: IconElement;
loader?: () => Promise;
- routeRef?: RouteRef;
+ routeRef?: RouteRef_2;
noHeader?: boolean;
};
}>;
@@ -136,7 +110,7 @@ const _default: OverridableFrontendPlugin<
output:
| ExtensionDataRef
| ExtensionDataRef<
- RouteRef,
+ RouteRef_2,
'core.routing.ref',
{
optional: true;
@@ -168,7 +142,7 @@ const _default: OverridableFrontendPlugin<
title: string;
icon?: IconElement;
loader: () => Promise;
- routeRef?: RouteRef;
+ routeRef?: RouteRef_2;
};
}>;
'sub-page:user-settings/feature-flags': OverridableExtensionDefinition<{
@@ -185,7 +159,7 @@ const _default: OverridableFrontendPlugin<
output:
| ExtensionDataRef
| ExtensionDataRef<
- RouteRef,
+ RouteRef_2,
'core.routing.ref',
{
optional: true;
@@ -206,7 +180,7 @@ const _default: OverridableFrontendPlugin<
title: string;
icon?: IconElement;
loader: () => Promise;
- routeRef?: RouteRef;
+ routeRef?: RouteRef_2;
};
}>;
'sub-page:user-settings/general': OverridableExtensionDefinition<{
@@ -223,7 +197,7 @@ const _default: OverridableFrontendPlugin<
output:
| ExtensionDataRef
| ExtensionDataRef<
- RouteRef,
+ RouteRef_2,
'core.routing.ref',
{
optional: true;
@@ -244,40 +218,13 @@ const _default: OverridableFrontendPlugin<
title: string;
icon?: IconElement;
loader: () => Promise;
- routeRef?: RouteRef;
+ routeRef?: RouteRef_2;
};
}>;
}
>;
export default _default;
-// @alpha (undocumented)
-export const settingsNavItem: OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- };
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- };
-}>;
-
// @alpha @deprecated (undocumented)
export const userSettingsTranslationRef: TranslationRef<
'user-settings',
diff --git a/plugins/user-settings/src/alpha.tsx b/plugins/user-settings/src/alpha.tsx
index 482a9711f5..86bc031056 100644
--- a/plugins/user-settings/src/alpha.tsx
+++ b/plugins/user-settings/src/alpha.tsx
@@ -18,7 +18,6 @@ import {
createExtensionInput,
createFrontendPlugin,
PageBlueprint,
- NavItemBlueprint,
SubPageBlueprint,
} from '@backstage/frontend-plugin-api';
import { Content } from '@backstage/core-components';
@@ -38,6 +37,7 @@ const userSettingsPage = PageBlueprint.make({
path: '/settings',
routeRef: settingsRouteRef,
title: 'Settings',
+ icon: ,
},
});
@@ -95,15 +95,6 @@ const featureFlagsSettingsPage = SubPageBlueprint.make({
},
});
-/** @alpha */
-export const settingsNavItem = NavItemBlueprint.make({
- params: {
- routeRef: settingsRouteRef,
- title: 'Settings',
- icon: SettingsIcon,
- },
-});
-
/**
* @alpha
*/
@@ -117,7 +108,6 @@ export default createFrontendPlugin({
generalSettingsPage,
authProvidersSettingsPage,
featureFlagsSettingsPage,
- settingsNavItem,
],
routes: {
root: settingsRouteRef,