diff --git a/docs/features/kubernetes/installation.md b/docs/features/kubernetes/installation.md
index 40b2dc0bcc..18352e78ad 100644
--- a/docs/features/kubernetes/installation.md
+++ b/docs/features/kubernetes/installation.md
@@ -17,32 +17,20 @@ The first step is to add the Kubernetes frontend plugin to your Backstage applic
yarn --cwd packages/app add @backstage/plugin-kubernetes
```
-Once the package has been installed, you need to import the plugin in your app by adding the "Kubernetes" tab to the respective catalog pages.
+Once installed, the plugin is automatically available in your app through the default feature discovery. It adds a "Kubernetes" tab to entity pages for entities that have Kubernetes resources associated with them. For more details and alternative installation methods, see [installing plugins](../../frontend-system/building-apps/05-installing-plugins.md).
-```tsx title="packages/app/src/components/catalog/EntityPage.tsx"
-/* highlight-add-next-line */
-import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';
+The Kubernetes tab is shown by default for entities where Kubernetes data is available, based on the entity annotations. You can customize the entity filter for the tab through `app-config.yaml`:
-// You can add the tab to any number of pages, the service page is shown as an
-// example here
-const serviceEntityPage = (
-
- {/* other tabs... */}
- {/* highlight-add-start */}
-
-
-
- {/* highlight-add-end */}
-
-);
+```yaml title="app-config.yaml"
+app:
+ extensions:
+ - entity-content:kubernetes/kubernetes:
+ config:
+ filter:
+ metadata.annotations.backstage.io/kubernetes-id:
+ $exists: true
```
-:::note Note
-
-The optional `refreshIntervalMs` property on the `EntityKubernetesContent` defines the interval in which the content automatically refreshes, if not set this will default to 10 seconds.
-
-:::
-
That's it! But now, we need the Kubernetes Backend plugin for the frontend to work.
## Adding Kubernetes Backend plugin
diff --git a/docs/features/search/getting-started--old.md b/docs/features/search/getting-started--old.md
new file mode 100644
index 0000000000..92809fab0f
--- /dev/null
+++ b/docs/features/search/getting-started--old.md
@@ -0,0 +1,351 @@
+---
+id: search-getting-started--old
+title: Getting Started with Search (Old Frontend System)
+description: How to set up and install Backstage Search
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./getting-started.md) instead.
+::::
+
+Search functions as a plugin to Backstage, so you will need to use Backstage to
+use Search.
+
+If you haven't setup Backstage already, start
+[here](../../getting-started/index.md).
+
+> If you used `npx @backstage/create-app`, and you have a search page defined in
+> `packages/app/src/components/search`, skip to
+> [`Customizing Search`](#customizing-search) below.
+
+## Adding Search to the Frontend
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/app add @backstage/plugin-search @backstage/plugin-search-react
+```
+
+Create a new `packages/app/src/components/search/SearchPage.tsx` file in your
+Backstage app with the following contents:
+
+```tsx
+import { Content, Header, Page } from '@backstage/core-components';
+import { Grid, List, Card, CardContent } from '@material-ui/core';
+import {
+ SearchBar,
+ SearchResult,
+ DefaultResultListItem,
+ SearchFilter,
+} from '@backstage/plugin-search-react';
+import { CatalogSearchResultListItem } from '@backstage/plugin-catalog';
+
+export const searchPage = (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {({ results }) => (
+
+ {results.map(result => {
+ switch (result.type) {
+ case 'software-catalog':
+ return (
+
+ );
+ default:
+ return (
+
+ );
+ }
+ })}
+
+ )}
+
+
+
+
+
+);
+```
+
+Bind the above Search Page to the `/search` route in your
+`packages/app/src/App.tsx` file, like this:
+
+```tsx
+import { SearchPage } from '@backstage/plugin-search';
+import { searchPage } from './components/search/SearchPage';
+
+const routes = (
+
+ }>
+ {searchPage}
+
+
+);
+```
+
+### Using the Search Modal
+
+In `Root.tsx`, add the `SidebarSearchModal` component:
+
+```bash
+import { SidebarSearchModal } from '@backstage/plugin-search';
+
+export const Root = ({ children }: PropsWithChildren<{}>) => (
+
+
+
+
+
+...
+```
+
+For more information about using `Root.tsx`, please see
+[the changelog](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md#0315).
+
+## Adding Search to the Backend
+
+Add the following plugins into your backend app:
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/backend add @backstage/plugin-search-backend @backstage/plugin-search-backend-module-pg @backstage/plugin-search-backend-module-catalog @backstage/plugin-search-backend-module-techdocs
+```
+
+Then add the following lines:
+
+```ts title="packages/backend/src/index.ts"
+const backend = createBackend();
+
+// Other plugins...
+
+/* highlight-add-start */
+// search plugin
+backend.add(import('@backstage/plugin-search-backend'));
+
+// search engines
+backend.add(import('@backstage/plugin-search-backend-module-pg'));
+
+// search collators
+backend.add(import('@backstage/plugin-search-backend-module-catalog'));
+backend.add(import('@backstage/plugin-search-backend-module-techdocs'));
+/* highlight-add-end */
+
+backend.start();
+```
+
+With the above setup Search will use the [Lunr](https://github.com/olivernn/lunr.js) in-memory Search Engine but if your have Postgres setup as your database then it will use Postgres as your Search Engine. Learn more in the [Search Engines](./search-engines.md) documentation.
+
+The above also sets up two Collators for you - Catalog and TechDocs - which will index content from these two locations so that you can easily search them. Learn more in the [Collators documentation](./collators.md).
+
+## Customizing Search
+
+### Frontend
+
+The Search Plugin web library (`@backstage/plugin-search-react`) exposes several default filter types as static properties,
+including `` and ``. These allow
+you to provide values relevant to your Backstage instance that, when selected,
+get passed to the backend.
+
+```tsx {2-5,8-11}
+
+
+
+
+
+
+```
+
+If you have advanced filter needs, you can specify your own filter component
+like this (although new core filter contributions are welcome):
+
+```tsx
+import { useSearch, SearchFilter } from '@backstage/plugin-search-react';
+
+const MyCustomFilter = () => {
+ // Note: filters contain filter data from other filter components. Be sure
+ // not to clobber other filters' data!
+ const { filters, setFilters } = useSearch();
+
+ return (/* ... */);
+};
+
+// Which could be rendered like this:
+
+```
+
+It's good practice for search results to highlight information that was used to
+return it in the first place! The code below highlights how you might specify a
+custom result item component, using the `` component as
+an example:
+
+```tsx {7-13}
+
+ {({ results }) => (
+
+ {results.map(result => {
+ // result.type is the index type defined by the collator.
+ switch (result.type) {
+ case 'software-catalog':
+ return (
+
+ );
+ // ...
+ }
+ })}
+
+ )}
+
+```
+
+> For more advanced customization of the Search frontend, also see how to guides such as [How to implement your own Search API](./how-to-guides--old.md#how-to-implement-your-own-search-api) and [How to customize search results highlighting styling](./how-to-guides--old.md#how-to-customize-search-results-highlighting-styling)
+
+### Backend
+
+Backstage Search isn't a search engine itself, rather, it provides an interface
+between your Backstage instance and a
+[Search Engine](./concepts.md#search-engines) of your choice. Currently, we only
+support two engines, an in-memory search Engine called Lunr and Elasticsearch.
+See [Search Engines](./search-engines.md) documentation for more information how
+to configure these in your Backstage instance.
+
+Backstage Search can be used to power search of anything! Plugins like the
+Catalog offer default [collators](./concepts.md#collators) (e.g.
+[DefaultCatalogCollator](https://github.com/backstage/backstage/blob/df12cc25aa4934a98bc42ed03c07f64a1a0a9d72/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts))
+which are responsible for providing documents
+[to be indexed](./concepts.md#documents-and-indices). You can register any
+number of collators with the `IndexBuilder` like this:
+
+```typescript
+const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine });
+
+const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({
+ frequency: { minutes: 10 },
+ timeout: { minutes: 15 },
+ initialDelay: { seconds: 3 },
+});
+
+const everyHourSchedule = env.scheduler.createScheduledTaskRunner({
+ frequency: { hours: 1 },
+ timeout: { minutes: 90 },
+ initialDelay: { seconds: 3 },
+});
+
+indexBuilder.addCollator({
+ schedule: every10MinutesSchedule,
+ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, {
+ discovery: env.discovery,
+ tokenManager: env.tokenManager,
+ }),
+});
+
+indexBuilder.addCollator({
+ schedule: everyHourSchedule,
+ factory: new MyCustomCollatorFactory(),
+});
+```
+
+Backstage Search builds and maintains its index
+[on a schedule](./concepts.md#the-scheduler). You can change how often the
+indexes are rebuilt for a given type of document. You may want to do this if
+your documents are updated more or less frequently. You can do so by configuring
+a scheduled `SchedulerServiceTaskRunner` to pass into the `schedule` value, like this:
+
+```typescript {3}
+const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({
+ frequency: { minutes: 10 },
+ timeout: { minutes: 15 },
+ initialDelay: { seconds: 3 },
+});
+
+indexBuilder.addCollator({
+ schedule: every10MinutesSchedule,
+ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, {
+ discovery: env.discovery,
+ tokenManager: env.tokenManager,
+ }),
+});
+```
+
+:::note Note
+
+if you are using the in-memory Lunr search engine, you probably want to
+implement a non-distributed `SchedulerServiceTaskRunner` like the following to ensure consistency
+if you're running multiple search backend nodes (alternatively, you can configure
+the search plugin to use a non-distributed database such as
+[SQLite](../../tutorials/configuring-plugin-databases.md#postgresql-and-sqlite-3)):
+
+:::
+
+```typescript
+import {
+ SchedulerServiceTaskRunner,
+ SchedulerServiceTaskInvocationDefinition,
+} from '@backstage/backend-plugin-api';
+
+const schedule: SchedulerServiceTaskRunner = {
+ run: async (task: SchedulerServiceTaskInvocationDefinition) => {
+ const startRefresh = async () => {
+ while (!task.signal?.aborted) {
+ try {
+ await task.fn(task.signal);
+ } catch {
+ // ignore intentionally
+ }
+
+ await new Promise(resolve => setTimeout(resolve, 600 * 1000));
+ }
+ };
+ startRefresh();
+ },
+};
+
+indexBuilder.addCollator({
+ schedule,
+ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, {
+ discovery: env.discovery,
+ tokenManager: env.tokenManager,
+ }),
+});
+```
+
+> For more advanced customization of the Search backend, also see how to guides such as [How to customize fields in the Software Catalog or TechDocs index](./how-to-guides--old.md#how-to-customize-fields-in-the-software-catalog-or-techdocs-index)
diff --git a/docs/features/search/getting-started.md b/docs/features/search/getting-started.md
index 155aaa2e58..5c64438f28 100644
--- a/docs/features/search/getting-started.md
+++ b/docs/features/search/getting-started.md
@@ -4,128 +4,60 @@ title: Getting Started with Search
description: How to set up and install Backstage Search
---
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./getting-started--old.md)
+instead.
+::::
+
Search functions as a plugin to Backstage, so you will need to use Backstage to
use Search.
If you haven't setup Backstage already, start
[here](../../getting-started/index.md).
-> If you used `npx @backstage/create-app`, and you have a search page defined in
-> `packages/app/src/components/search`, skip to
-> [`Customizing Search`](#customizing-search) below.
-
## Adding Search to the Frontend
```bash title="From your Backstage root directory"
yarn --cwd packages/app add @backstage/plugin-search @backstage/plugin-search-react
```
-Create a new `packages/app/src/components/search/SearchPage.tsx` file in your
-Backstage app with the following contents:
+Once installed, the search plugin is automatically available in your app through
+the default feature discovery. It provides a search page at `/search`, a search
+navigation item in the sidebar, and a search modal accessible from the sidebar.
+For more details and alternative installation methods, see
+[installing plugins](../../frontend-system/building-apps/05-installing-plugins.md).
-```tsx
-import { Content, Header, Page } from '@backstage/core-components';
-import { Grid, List, Card, CardContent } from '@material-ui/core';
-import {
- SearchBar,
- SearchResult,
- DefaultResultListItem,
- SearchFilter,
-} from '@backstage/plugin-search-react';
-import { CatalogSearchResultListItem } from '@backstage/plugin-catalog';
+### Configuring the search page
-export const searchPage = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {({ results }) => (
-
- {results.map(result => {
- switch (result.type) {
- case 'software-catalog':
- return (
-
- );
- default:
- return (
-
- );
- }
- })}
-
- )}
-
-
-
-
-
-);
+The search page can be configured through `app-config.yaml`. For example, to
+disable search result tracking:
+
+```yaml title="app-config.yaml"
+app:
+ extensions:
+ - page:search:
+ config:
+ noTrack: true
```
-Bind the above Search Page to the `/search` route in your
-`packages/app/src/App.tsx` file, like this:
+### Search result list items
-```tsx
-import { SearchPage } from '@backstage/plugin-search';
-import { searchPage } from './components/search/SearchPage';
+The search page automatically discovers and uses search result list item
+extensions provided by installed plugins. For example, the catalog plugin
+provides a `CatalogSearchResultListItem` and the TechDocs plugin provides a
+`TechDocsSearchResultListItem`. These are automatically registered when the
+respective plugins are installed.
-const routes = (
-
- }>
- {searchPage}
-
-
-);
-```
+You can also install additional search result list item extensions using the
+`SearchResultListItemBlueprint` from `@backstage/plugin-search-react/alpha`.
-### Using the Search Modal
+### Search filters
-In `Root.tsx`, add the `SidebarSearchModal` component:
-
-```bash
-import { SidebarSearchModal } from '@backstage/plugin-search';
-
-export const Root = ({ children }: PropsWithChildren<{}>) => (
-
-
-
-
-
-...
-```
-
-For more information about using `Root.tsx`, please see
-[the changelog](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md#0315).
+Similarly, search filter extensions are automatically discovered. You can add
+custom filters using the `SearchFilterBlueprint` or
+`SearchFilterResultTypeBlueprint` from `@backstage/plugin-search-react/alpha`.
## Adding Search to the Backend
@@ -165,70 +97,49 @@ The above also sets up two Collators for you - Catalog and TechDocs - which will
### Frontend
-The Search Plugin web library (`@backstage/plugin-search-react`) exposes several default filter types as static properties,
-including `` and ``. These allow
-you to provide values relevant to your Backstage instance that, when selected,
-get passed to the backend.
+The search plugin provides extension points for customizing the search
+experience through blueprints. You can add custom search result list items,
+filters, and result type filters.
-```tsx {2-5,8-11}
-
-
-
-
-
-
-```
-
-If you have advanced filter needs, you can specify your own filter component
-like this (although new core filter contributions are welcome):
+For example, to create a custom search result list item, use the
+`SearchResultListItemBlueprint` from `@backstage/plugin-search-react/alpha`:
```tsx
-import { useSearch, SearchFilter } from '@backstage/plugin-search-react';
+import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha';
-const MyCustomFilter = () => {
- // Note: filters contain filter data from other filter components. Be sure
- // not to clobber other filters' data!
- const { filters, setFilters } = useSearch();
-
- return (/* ... */);
-};
-
-// Which could be rendered like this:
-
+export const MySearchResultListItem = SearchResultListItemBlueprint.make({
+ name: 'my-result-item',
+ params: {
+ predicate: result => result.type === 'my-custom-type',
+ component: async () => {
+ const { MyResultItem } = await import('./components/MyResultItem');
+ return MyResultItem;
+ },
+ },
+});
```
-It's good practice for search results to highlight information that was used to
-return it in the first place! The code below highlights how you might specify a
-custom result item component, using the `` component as
-an example:
+Install this in your app by wrapping it in a frontend module and passing it to `createApp`:
-```tsx {7-13}
-
- {({ results }) => (
-
- {results.map(result => {
- // result.type is the index type defined by the collator.
- switch (result.type) {
- case 'software-catalog':
- return (
-
- );
- // ...
- }
- })}
-
- )}
-
+```tsx title="packages/app/src/search/searchModule.ts"
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import { MySearchResultListItem } from './MySearchResultListItem';
+
+export const searchCustomizations = createFrontendModule({
+ pluginId: 'search',
+ extensions: [MySearchResultListItem],
+});
+```
+
+```tsx title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/frontend-defaults';
+import { searchCustomizations } from './search/searchModule';
+
+const app = createApp({
+ features: [searchCustomizations],
+});
+
+export default app.createRoot();
```
> For more advanced customization of the Search frontend, also see how to guides such as [How to implement your own Search API](./how-to-guides.md#how-to-implement-your-own-search-api) and [How to customize search results highlighting styling](./how-to-guides.md#how-to-customize-search-results-highlighting-styling)
diff --git a/docs/features/search/how-to-guides--old.md b/docs/features/search/how-to-guides--old.md
new file mode 100644
index 0000000000..7ac8679137
--- /dev/null
+++ b/docs/features/search/how-to-guides--old.md
@@ -0,0 +1,367 @@
+---
+id: search-how-to-guides--old
+title: Search How-To guides (Old Frontend System)
+sidebar_label: How-To guides
+description: Search How To guides
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./how-to-guides.md) instead.
+::::
+
+## How to implement your own Search API
+
+The Search plugin provides implementation of one primary API by default: the
+[SearchApi](https://github.com/backstage/backstage/blob/db2666b980853c281b8fe77905d7639c5d255f13/plugins/search/src/apis.ts#L35),
+which is responsible for talking to the search-backend to query search results.
+
+There may be occasions where you need to implement this API yourself, to
+customize it to your own needs - for example if you have your own search backend
+that you want to talk to. The purpose of this guide is to walk you through how
+to do that in two steps.
+
+1. Implement the `SearchApi`
+ [interface](https://github.com/backstage/backstage/blob/db2666b980853c281b8fe77905d7639c5d255f13/plugins/search/src/apis.ts#L31)
+ according to your needs.
+
+ ```typescript
+ export class SearchClient implements SearchApi {
+ // your implementation
+ }
+ ```
+
+2. Override the API ref `searchApiRef` with your new implemented API in the
+ `App.tsx` using `ApiFactories`.
+ [Read more about App APIs](https://backstage.io/docs/api/utility-apis#app-apis).
+
+ ```typescript
+ const app = createApp({
+ apis: [
+ // SearchApi
+ createApiFactory({
+ api: searchApiRef,
+ deps: { discovery: discoveryApiRef },
+ factory({ discovery }) {
+ return new SearchClient({ discoveryApi: discovery });
+ },
+ }),
+ ],
+ });
+ ```
+
+## How to customize fields in the Software Catalog or TechDocs index
+
+Sometimes, you might want to have the ability to control which data passes into the search index
+in the catalog collator or customize data for a specific kind. You can easily achieve this
+by passing an `entityTransformer` callback to the `DefaultCatalogCollatorFactory`. This behavior
+is also possible for the `DefaultTechDocsCollatorFactory`. You can either simply amend the default behavior
+or even write an entirely new document (which should still follow some required basic structure).
+
+> `authorization` and `location` cannot be modified via a `entityTransformer`, `location` can be modified only through `locationTemplate`.
+
+```ts title="packages/backend/src/plugins/search.ts"
+const catalogEntityTransformer: CatalogCollatorEntityTransformer = (
+ entity: Entity,
+) => {
+ if (entity.kind === 'SomeKind') {
+ return {
+ // customize here output for 'SomeKind' kind
+ };
+ }
+
+ return {
+ // and customize default output
+ ...defaultCatalogCollatorEntityTransformer(entity),
+ text: 'my super cool text',
+ };
+};
+
+indexBuilder.addCollator({
+ collator: DefaultCatalogCollatorFactory.fromConfig(env.config, {
+ discovery: env.discovery,
+ tokenManager: env.tokenManager,
+ /* highlight-add-next-line */
+ entityTransformer: catalogEntityTransformer,
+ }),
+});
+
+const techDocsEntityTransformer: TechDocsCollatorEntityTransformer = (
+ entity: Entity,
+) => {
+ return {
+ // add more fields to the index
+ tags: entity.metadata.tags,
+ };
+};
+
+const techDocsDocumentTransformer: TechDocsCollatorDocumentTransformer = (
+ doc: MkSearchIndexDoc,
+) => {
+ return {
+ // add more fields to the index
+ bost: doc.boost,
+ };
+};
+
+indexBuilder.addCollator({
+ collator: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
+ discovery: env.discovery,
+ tokenManager: env.tokenManager,
+ /* highlight-add-next-line */
+ entityTransformer: techDocsEntityTransformer,
+ /* highlight-add-next-line */
+ documentTransformer: techDocsDocumentTransformer,
+ }),
+});
+```
+
+## How to customize search results highlighting styling
+
+The default highlighting styling for matched terms in search results is your
+browsers default styles for the `` HTML tag. If you want to customize
+how highlighted terms look you can follow Backstage's guide on how to
+[Customizing Your App's UI](https://backstage.io/docs/conf/user-interface)
+to create an override with your preferred styling.
+
+For example, using the new MUI V4+V5 unified theming method, the following will result
+in highlighted words to be bold & underlined:
+
+```typescript jsx title=packages/app/src/theme/theme.ts
+import {
+ createBaseThemeOptions,
+ createUnifiedTheme,
+ palettes,
+ UnifiedTheme,
+} from '@backstage/theme';
+
+export const myLightTheme: UnifiedTheme = createUnifiedTheme({
+ ...createBaseThemeOptions({
+ palette: palettes.light,
+ }),
+ defaultPageTheme: 'home',
+ components: {
+ /** @ts-ignore This is temporarily necessary until MUI V5 transition is completed. */
+ BackstageHighlightedSearchResultText: {
+ styleOverrides: {
+ highlight: {
+ color: 'inherit',
+ backgroundColor: 'inherit',
+ fontWeight: 'bold',
+ textDecoration: 'underline',
+ },
+ },
+ },
+ },
+});
+```
+
+```typescript jsx title= packages/app/src/App.tsx
+
+const app : BackstageApp = createApp({
+ ...
+ themes: [{
+ id: 'my-light-theme',
+ title: 'Light Theme',
+ variant: 'light',
+ icon: ,
+ Provider: ({ children }) => ()
+ }]
+});
+```
+
+Obviously if you wanted a dark theme, you would need to provide that as well.
+
+## How to render search results using extensions
+
+Extensions for search results let you customize components used to render search result items, It is possible to provide your own search result item extensions or use the ones provided by plugin packages.
+
+### 1. Providing an extension in your plugin package
+
+> Note: You must use the `plugin.provide()` function to make a search item renderer available. Unlike rendering a list in a standard MUI Table or similar, you cannot simply provide
+> a rendering function to the `` component.
+
+Using the example below, you can provide an extension to be used as a search result item:
+
+```tsx title="plugins/your-plugin/src/plugin.ts"
+import { createPlugin } from '@backstage/core-plugin-api';
+import { createSearchResultListItemExtension } from '@backstage/plugin-search-react';
+
+const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' });
+
+export const YourSearchResultListItemExtension = plugin.provide(
+ createSearchResultListItemExtension({
+ name: 'YourSearchResultListItem',
+ component: () =>
+ import('./components').then(m => m.YourSearchResultListItem),
+ }),
+);
+```
+
+If your list item accept props, you can extend the `SearchResultListItemExtensionProps` with your component specific props:
+
+```tsx
+export const YourSearchResultListItemExtension: (
+ props: SearchResultListItemExtensionProps,
+) => JSX.Element | null = plugin.provide(
+ createSearchResultListItemExtension({
+ name: 'YourSearchResultListItem',
+ component: () =>
+ import('./components').then(m => m.YourSearchResultListItem),
+ }),
+);
+```
+
+Additionally, you can define a predicate function that receives a result and returns whether your extension should be used to render it or not:
+
+```tsx title="plugins/your-plugin/src/plugin.ts"
+import { createPlugin } from '@backstage/core-plugin-api';
+import { createSearchResultListItemExtension } from '@backstage/plugin-search-react';
+
+const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' });
+
+export const YourSearchResultListItemExtension = plugin.provide(
+ createSearchResultListItemExtension({
+ name: 'YourSearchResultListItem',
+ component: () =>
+ import('./components').then(m => m.YourSearchResultListItem),
+ // Only results matching your type will be rendered by this extension
+ predicate: result => result.type === 'YOUR_RESULT_TYPE',
+ }),
+);
+```
+
+Remember to export your new extension via your plugin's `index.ts` so that it is available from within your app:
+
+```tsx title="plugins/your-plugin/src/index.ts"
+export { YourSearchResultListItem } from './plugin.ts';
+```
+
+For more details, see the [createSearchResultListItemExtension](https://backstage.io/api/stable/functions/_backstage_plugin-search-react.index.createSearchResultListItemExtension.html) API reference.
+
+### 2. Custom search result extension in the SearchPage
+
+Once you have exposed your item renderer via the `plugin.provide()` function, you can now override the default search item renderers and tell the `` component
+which renderers to use. Note that the order of the renderers matters! The first one that matches via its predicate function will be used.
+
+Here is an example of customizing your `SearchPage`:
+
+```tsx title="packages/app/src/components/searchPage.tsx"
+import { Grid, Paper } from '@material-ui/core';
+import BuildIcon from '@material-ui/icons/Build';
+
+import {
+ Page,
+ Header,
+ Content,
+ DocsIcon,
+ CatalogIcon,
+} from '@backstage/core-components';
+import { SearchBar, SearchResult } from '@backstage/plugin-search-react';
+
+// Your search result item extension
+import { YourSearchResultListItem } from '@backstage/your-plugin';
+
+// Extensions provided by other plugin developers
+import { ToolSearchResultListItem } from '@backstage/plugin-explore';
+import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';
+import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized';
+
+// This example omits other components, like filter and pagination
+const SearchPage = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+ } />
+ } />
+ } />
+
+
+
+
+
+);
+
+export const searchPage = ;
+```
+
+> **Important**: A default result item extension (one that does not have a predicate) should be placed as the last child, so it can be used only when no other extensions match the result being rendered.
+> If a non-default extension is specified, the `DefaultResultListItem` component will be used.
+
+### 2. Custom search result extension in the SidebarSearchModal
+
+You may be using the SidebarSearchModal component. In this case, you can customize the search items in this component as follows:
+
+```tsx title="packages/app/src/components/Root/Root.tsx"
+import { SidebarSearchModal } from '@backstage/plugin-search';
+...
+export const Root = ({ children }: PropsWithChildren<{}>) => {
+ const styles = useStyles();
+
+ return
+
+ ...
+ } />,
+ /* Provide an existing search item renderer */
+ } />
+ ]} />
+ ...
+
+ {children}
+ ;
+};
+```
+
+### 3. Custom search result extension in a custom SearchModal
+
+Assuming you have completely customized your SearchModal, here's an example that renders results with extensions:
+
+```tsx title="packages/app/src/components/searchModal.tsx"
+import { DialogContent, DialogTitle, Paper } from '@material-ui/core';
+import BuildIcon from '@material-ui/icons/Build';
+
+import { DocsIcon, CatalogIcon } from '@backstage/core-components';
+import { SearchBar, SearchResult } from '@backstage/plugin-search-react';
+
+// Your search result item extension
+import { YourSearchResultListItem } from '@backstage/your-plugin';
+
+// Extensions provided by other plugin developers
+import { ToolSearchResultListItem } from '@backstage/plugin-explore';
+import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';
+import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized';
+
+export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => (
+ <>
+
+
+
+
+
+
+
+ } />
+ } />
+ } />
+ {/* As a "default" extension, it does not define a predicate function,
+ so it must be the last child to render results that do not match the above extensions */}
+
+
+
+ >
+);
+```
+
+There are other more specific search results layout components that also accept result item extensions, check their documentation: [SearchResultList](https://backstage.io/storybook/?path=/story/plugins-search-searchresultlist--with-result-item-extensions) and [SearchResultGroup](https://backstage.io/storybook/?path=/story/plugins-search-searchresultgroup--with-result-item-extensions).
diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md
index 018c70479d..8c0f430886 100644
--- a/docs/features/search/how-to-guides.md
+++ b/docs/features/search/how-to-guides.md
@@ -5,6 +5,13 @@ sidebar_label: How-To guides
description: Search How To guides
---
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./how-to-guides--old.md)
+instead.
+::::
+
## How to implement your own Search API
The Search plugin provides implementation of one primary API by default: the
@@ -26,24 +33,9 @@ to do that in two steps.
}
```
-2. Override the API ref `searchApiRef` with your new implemented API in the
- `App.tsx` using `ApiFactories`.
- [Read more about App APIs](https://backstage.io/docs/api/utility-apis#app-apis).
-
- ```typescript
- const app = createApp({
- apis: [
- // SearchApi
- createApiFactory({
- api: searchApiRef,
- deps: { discovery: discoveryApiRef },
- factory({ discovery }) {
- return new SearchClient({ discoveryApi: discovery });
- },
- }),
- ],
- });
- ```
+2. Override the default API extension by creating a custom API extension using
+ `createApiExtension` from `@backstage/frontend-plugin-api`, and install it
+ in your app. See the [Utility APIs](../../frontend-system/utility-apis/01-index.md) documentation for details on how to create and install custom API extensions.
## How to customize fields in the Software Catalog or TechDocs index
@@ -119,7 +111,7 @@ how highlighted terms look you can follow Backstage's guide on how to
[Customizing Your App's UI](https://backstage.io/docs/conf/user-interface)
to create an override with your preferred styling.
-For example, using the new MUI V4+V5 unified theming method, the following will result
+For example, using the unified theming method, the following will result
in highlighted words to be bold & underlined:
```typescript jsx title=packages/app/src/theme/theme.ts
@@ -151,211 +143,69 @@ export const myLightTheme: UnifiedTheme = createUnifiedTheme({
});
```
-```typescript jsx title= packages/app/src/App.tsx
-
-const app : BackstageApp = createApp({
- ...
- themes: [{
- id: 'my-light-theme',
- title: 'Light Theme',
- variant: 'light',
- icon: ,
- Provider: ({ children }) => ()
- }]
-});
-```
-
-Obviously if you wanted a dark theme, you would need to provide that as well.
+Custom themes are installed as extensions in the new frontend system. See the
+[theming documentation](../../frontend-system/building-apps/02-configuring-extensions.md)
+for details on how to install custom themes.
## How to render search results using extensions
-Extensions for search results let you customize components used to render search result items, It is possible to provide your own search result item extensions or use the ones provided by plugin packages.
+Extensions for search results let you customize components used to render
+search result items. It is possible to provide your own search result item
+extensions or use the ones provided by plugin packages.
-### 1. Providing an extension in your plugin package
+### Providing a search result list item extension
-> Note: You must use the `plugin.provide()` function to make a search item renderer available. Unlike rendering a list in a standard MUI Table or similar, you cannot simply provide
-> a rendering function to the `` component.
+In the new frontend system, search result list item extensions are created
+using the `SearchResultListItemBlueprint` from
+`@backstage/plugin-search-react/alpha`:
-Using the example below, you can provide an extension to be used as a search result item:
+```tsx title="plugins/your-plugin/src/extensions.ts"
+import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha';
-```tsx title="plugins/your-plugin/src/plugin.ts"
-import { createPlugin } from '@backstage/core-plugin-api';
-import { createSearchResultListItemExtension } from '@backstage/plugin-search-react';
-
-const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' });
-
-export const YourSearchResultListItemExtension = plugin.provide(
- createSearchResultListItemExtension({
- name: 'YourSearchResultListItem',
- component: () =>
- import('./components').then(m => m.YourSearchResultListItem),
- }),
-);
-```
-
-If your list item accept props, you can extend the `SearchResultListItemExtensionProps` with your component specific props:
-
-```tsx
-export const YourSearchResultListItemExtension: (
- props: SearchResultListItemExtensionProps,
-) => JSX.Element | null = plugin.provide(
- createSearchResultListItemExtension({
- name: 'YourSearchResultListItem',
- component: () =>
- import('./components').then(m => m.YourSearchResultListItem),
- }),
-);
-```
-
-Additionally, you can define a predicate function that receives a result and returns whether your extension should be used to render it or not:
-
-```tsx title="plugins/your-plugin/src/plugin.ts"
-import { createPlugin } from '@backstage/core-plugin-api';
-import { createSearchResultListItemExtension } from '@backstage/plugin-search-react';
-
-const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' });
-
-export const YourSearchResultListItemExtension = plugin.provide(
- createSearchResultListItemExtension({
- name: 'YourSearchResultListItem',
- component: () =>
- import('./components').then(m => m.YourSearchResultListItem),
- // Only results matching your type will be rendered by this extension
+export const YourSearchResultListItem = SearchResultListItemBlueprint.make({
+ name: 'your-result-item',
+ params: {
predicate: result => result.type === 'YOUR_RESULT_TYPE',
- }),
-);
+ component: async () => {
+ const { YourSearchResultListItem } = await import('./components');
+ return YourSearchResultListItem;
+ },
+ },
+});
```
-Remember to export your new extension via your plugin's `index.ts` so that it is available from within your app:
+The extension is then exported from your plugin's alpha entry point and
+automatically discovered when the plugin is installed.
-```tsx title="plugins/your-plugin/src/index.ts"
-export { YourSearchResultListItem } from './plugin.ts';
+If you need to provide a search result list item extension from your app
+rather than a plugin, wrap it in a frontend module and pass it to `createApp`:
+
+```tsx title="packages/app/src/search/searchModule.ts"
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import { YourSearchResultListItem } from './YourSearchResultListItem';
+
+export const searchCustomizations = createFrontendModule({
+ pluginId: 'search',
+ extensions: [YourSearchResultListItem],
+});
```
-For more details, see the [createSearchResultListItemExtension](https://backstage.io/api/stable/functions/_backstage_plugin-search-react.index.createSearchResultListItemExtension.html) API reference.
+```tsx title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/frontend-defaults';
+import { searchCustomizations } from './search/searchModule';
-### 2. Custom search result extension in the SearchPage
+const app = createApp({
+ features: [searchCustomizations],
+});
-Once you have exposed your item renderer via the `plugin.provide()` function, you can now override the default search item renderers and tell the `` component
-which renderers to use. Note that the order of the renderers matters! The first one that matches via its predicate function will be used.
-
-Here is an example of customizing your `SearchPage`:
-
-```tsx title="packages/app/src/components/searchPage.tsx"
-import { Grid, Paper } from '@material-ui/core';
-import BuildIcon from '@material-ui/icons/Build';
-
-import {
- Page,
- Header,
- Content,
- DocsIcon,
- CatalogIcon,
-} from '@backstage/core-components';
-import { SearchBar, SearchResult } from '@backstage/plugin-search-react';
-
-// Your search result item extension
-import { YourSearchResultListItem } from '@backstage/your-plugin';
-
-// Extensions provided by other plugin developers
-import { ToolSearchResultListItem } from '@backstage/plugin-explore';
-import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';
-import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized';
-
-// This example omits other components, like filter and pagination
-const SearchPage = () => (
-
-
-
-
-
-
-
-
-
-
-
-
- } />
- } />
- } />
-
-
-
-
-
-);
-
-export const searchPage = ;
+export default app.createRoot();
```
-> **Important**: A default result item extension (one that does not have a predicate) should be placed as the last child, so it can be used only when no other extensions match the result being rendered.
-> If a non-default extension is specified, the `DefaultResultListItem` component will be used.
+### Search result item ordering
-### 2. Custom search result extension in the SidebarSearchModal
-
-You may be using the SidebarSearchModal component. In this case, you can customize the search items in this component as follows:
-
-```tsx title="packages/app/src/components/Root/Root.tsx"
-import { SidebarSearchModal } from '@backstage/plugin-search';
-...
-export const Root = ({ children }: PropsWithChildren<{}>) => {
- const styles = useStyles();
-
- return
-
- ...
- } />,
- /* Provide an existing search item renderer */
- } />
- ]} />
- ...
-
- {children}
- ;
-};
-```
-
-### 3. Custom search result extension in a custom SearchModal
-
-Assuming you have completely customized your SearchModal, here's an example that renders results with extensions:
-
-```tsx title="packages/app/src/components/searchModal.tsx"
-import { DialogContent, DialogTitle, Paper } from '@material-ui/core';
-import BuildIcon from '@material-ui/icons/Build';
-
-import { DocsIcon, CatalogIcon } from '@backstage/core-components';
-import { SearchBar, SearchResult } from '@backstage/plugin-search-react';
-
-// Your search result item extension
-import { YourSearchResultListItem } from '@backstage/your-plugin';
-
-// Extensions provided by other plugin developers
-import { ToolSearchResultListItem } from '@backstage/plugin-explore';
-import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';
-import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized';
-
-export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => (
- <>
-
-
-
-
-
-
-
- } />
- } />
- } />
- {/* As a "default" extension, it does not define a predicate function,
- so it must be the last child to render results that do not match the above extensions */}
-
-
-
- >
-);
-```
+When multiple search result list item extensions are installed, the search page
+uses them to render results based on their predicate functions. The first
+extension whose predicate matches a given result is used to render it. Extensions
+without a predicate act as fallback renderers and should be ordered last.
There are other more specific search results layout components that also accept result item extensions, check their documentation: [SearchResultList](https://backstage.io/storybook/?path=/story/plugins-search-searchresultlist--with-result-item-extensions) and [SearchResultGroup](https://backstage.io/storybook/?path=/story/plugins-search-searchresultgroup--with-result-item-extensions).
diff --git a/docs/features/software-catalog/catalog-customization--old.md b/docs/features/software-catalog/catalog-customization--old.md
new file mode 100644
index 0000000000..c4ce27dc9e
--- /dev/null
+++ b/docs/features/software-catalog/catalog-customization--old.md
@@ -0,0 +1,508 @@
+---
+id: catalog-customization--old
+title: Catalog Customization (Old Frontend System)
+description: How to add custom filters or interface elements to the Backstage software catalog
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./catalog-customization.md) instead.
+::::
+
+The Backstage software catalog comes with a default `CatalogIndexPage` to filter and find catalog entities. This is already set up by default by `@backstage/create-app`. If you want to change the default index page - to set the initially selected filter, adjust columns, add actions, or to add a custom filter to the catalog - the following sections will show you how.
+
+## Pagination
+
+Initial support for pagination of the `CatalogIndexPage` was added in v1.21.0 of Backstage, so make sure you are on that version or newer to use this feature. To enable pagination you simply need to pass in the `pagination` prop like this:
+
+```tsx title="packages/app/src/App.tsx"
+} />
+```
+
+## Initially Selected Filter
+
+By default, the initially selected filter defaults to Owned. If you are still building up your catalog this may show an empty list to start. If you would prefer this to show All as the default, here's how you can make that change:
+
+```tsx title="packages/app/src/App.tsx"
+}
+/>
+```
+
+Possible options are: owned, starred, or all
+
+## Initially Selected Kind
+
+By default, the initially selected Kind when viewing the Catalog is Component, but you may have reasons that you want this to be different. Let's say at your Organization they would like it to always default to Domain, here's how you would do that:
+
+```tsx title="packages/app/src/App.tsx"
+} />
+```
+
+Possible options are all the [default Kinds](system-model.md) as well as any custom Kinds that you have added.
+
+## Owner Picker Mode
+
+The Owner filter by default will only contain a list of Users and/or Groups that actually own an entity in the Catalog, now you may have reason to change this. Here's how:
+
+```tsx title="packages/app/src/App.tsx"
+} />
+```
+
+Possible options are: owners-only or all
+
+## Table Options
+
+The tables used within Backstage are built on top of [`@material-table/core`](https://material-table-core.github.io/) and the `CatalogIndexPage` has a `tableOptions` prop that allows you to customize the underlying table to a certain extent, but there are some hard coded Backstage settings that can't be changed. Here's an example of how to use this prop to disable the search filter field in the table's header:
+
+```tsx title="packages/app/src/App.tsx"
+}
+/>
+```
+
+There are many options that can be set using `tableOptions`, the full list of settings can be found in the [`@material-table/core` `Options` interface](https://github.com/material-table-core/core/blob/v3.1.0/types/index.d.ts#L323) (this link goes to `v3.1.0` of `@material-table/core` as that is the version currently used by Backstage).
+
+## Customize Columns
+
+The columns you see in the `CatalogIndexPage` were selected to be a good starting point for most, but there may be cases where you would like to add or remove columns from existing or custom Kinds.
+
+### Adding a column to an existing Kind
+
+Suppose we want to add a new User Email column to the `User` kind in the Catalog. We can do this by overriding the `columns` that we pass into the `CatalogIndexPage` component in our `App.tsx`. First, we need to match the entity kind that we want to override, and then define the columns to show:
+
+```tsx title="packages/app/src/App.tsx"
+{/* prettier-ignore */ /* highlight-add-start */}
+const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => {
+ if (entityListContext.filters.kind?.value === 'user') {
+ return [
+ // Render existing columns
+ ...CatalogTable.defaultColumnsFunc(entityListContext),
+ // Add new columns here
+ ];
+ }
+
+ return CatalogTable.defaultColumnsFunc(entityListContext);
+};
+{/* prettier-ignore */ /* highlight-add-end */}
+```
+
+Then, we can implement the `createUserEmailColumn` function and add it to the list of columns. `field` is used to access the data from the entity, while `render` lets us customize how we display the data:
+
+```tsx title="packages/app/src/App.tsx"
+{/* highlight-add-start */}
+const createUserEmailColumn = (): TableColumn => ({
+ title: 'User Email',
+ field: 'entity.spec.profile.email',
+ render: ({ entity }) => (
+
+ ),
+});
+{/* highlight-add-end */}
+
+const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => {
+ if (entityListContext.filters.kind?.value === 'user') {
+ return [
+ // Render existing columns
+ ...CatalogTable.defaultColumnsFunc(entityListContext),
+ // Add new columns here
+ {/* highlight-add-next-line */}
+ createUserEmailColumn(),
+ ];
+ }
+
+ return CatalogTable.defaultColumnsFunc(entityListContext);
+};
+```
+
+Finally, we can pass the `myColumnsFunc` to the `CatalogIndexPage` component:
+
+```tsx title="packages/app/src/App.tsx"
+const routes = (
+
+
+ }
+ />
+ {/* Other routes */}
+
+)
+```
+
+### Adding columns to a custom or specific Kind
+
+Another use case for customization is when adding a custom `Kind`. This feature is available in Backstage >= `v1.23.0`. For example:
+
+```tsx title="packages/app/src/App.tsx"
+import {
+ CatalogEntityPage,
+ CatalogIndexPage,
+ catalogPlugin,
+ {/* highlight-add-start */}
+ CatalogTable,
+ CatalogTableColumnsFunc,
+ {/* highlight-add-end */}
+} from '@backstage/plugin-catalog';
+
+{/* highlight-add-start */}
+const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => {
+ if (entityListContext.filters.kind?.value === 'MyKind') {
+ return [
+ CatalogTable.columns.createNameColumn(),
+ CatalogTable.columns.createOwnerColumn(),
+ ];
+ }
+
+ return CatalogTable.defaultColumnsFunc(entityListContext);
+};
+{/* highlight-add-end */}
+
+{/* highlight-remove-next-line */}
+} />
+{/* highlight-add-next-line */}
+} />
+```
+
+:::note Note
+
+In the examples above, the contents of the files have been shortened for simplicity.
+
+:::
+
+## Customize Actions
+
+The `CatalogIndexPage` comes with three default actions - view, edit, and star. You might want to add more.
+
+To do this, first you'll need to add `@mui/utils` to your `packages/app/package.json`:
+
+```sh
+yarn --cwd packages/app add @mui/utils
+```
+
+Then you'll do the following:
+
+```tsx title="packages/app/src/App.tsx"
+import {
+ AlertDisplay,
+ OAuthRequestDialog,
+ SignInPage,
+ {/* highlight-add-next-line */}
+ TableProps,
+} from '@backstage/core-components';
+
+import {
+ CatalogEntityPage,
+ CatalogIndexPage,
+ {/* highlight-add-next-line */}
+ CatalogTableRow,
+ catalogPlugin,
+} from '@backstage/plugin-catalog';
+
+{/* highlight-add-start */}
+import { Typography } from '@material-ui/core';
+import OpenInNew from '@material-ui/icons/OpenInNew';
+import { visuallyHidden } from '@mui/utils';
+{/* highlight-add-end */}
+
+{/* highlight-add-start */}
+const customActions: TableProps['actions'] = [
+ ({ entity }) => {
+ const url = 'https://backstage.io/';
+ const title = `View - ${entity.metadata.name}`;
+
+ return {
+ icon: () => (
+ <>
+ {title}
+
+ >
+ ),
+ tooltip: title,
+ disabled: !url,
+ onClick: () => {
+ if (!url) return;
+ window.open(url, '_blank');
+ },
+ };
+ },
+];
+{/* highlight-add-end */}
+
+{/* highlight-remove-next-line */}
+} />
+{/* highlight-add-next-line */}
+} />
+```
+
+:::note Note
+
+In the example above, the contents of `App.tsx` has been shortened for simplicity.
+
+:::
+
+The above customization will override the existing actions. Currently, the only way to keep them and add your own is to also include the existing actions in your array by copying them from the [`defaultActions`](https://github.com/backstage/backstage/blob/57397e7d6d2d725712c439f4ab93f2ac6aa27bf8/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx#L113-L168).
+
+## Customize Filters
+
+There are various ways to customize filters: adjusting the existing filters with props, adding or removing default filters, creating brand-new custom filters, etc. The following sections cover these cases:
+
+### Default Filter Props
+
+There are a set of default filters that you can use, which surface all the props mentioned earlier in this document. Here's how they can be used:
+
+```tsx title="packages/app/src/App.tsx"
+import { DefaultFilters } from '@backstage/plugin-catalog-react';
+
+
+
+ >
+ }
+ />
+ }
+/>;
+```
+
+### Removing Default Filters
+
+If you have reasons not to use the Lifecycle, Tag, and Processing Status filters, here's an example of how to remove them:
+
+```tsx title="packages/app/src/App.tsx"
+import {
+ EntityKindPicker,
+ EntityTypePicker,
+ UserListPicker,
+ EntityOwnerPicker,
+ EntityNamespacePicker,
+} from '@backstage/plugin-catalog-react';
+
+
+
+
+
+
+
+ >
+ }
+ />
+ }
+/>;
+```
+
+### Custom Filters
+
+You can add custom filters. For example, suppose that we want to allow filtering by a custom annotation added to entities, `company.com/security-tier`. Here is how we can build a filter to support that need.
+
+First we need to create a new filter that implements the `EntityFilter` interface:
+
+```ts
+import { EntityFilter } from '@backstage/plugin-catalog-react';
+import { Entity } from '@backstage/catalog-model';
+
+class EntitySecurityTierFilter implements EntityFilter {
+ constructor(readonly values: string[]) {}
+ filterEntity(entity: Entity): boolean {
+ const tier = entity.metadata.annotations?.['company.com/security-tier'];
+ return tier !== undefined && this.values.includes(tier);
+ }
+}
+```
+
+The `EntityFilter` interface permits backend filters, which are passed along to the `catalog-backend` - or frontend filters, which are applied after entities are loaded from the backend.
+
+We'll use this filter to extend the default filters in a type-safe way. Let's create the custom filter shape extending the default somewhere alongside this filter:
+
+```ts
+export type CustomFilters = DefaultEntityFilters & {
+ securityTiers?: EntitySecurityTierFilter;
+};
+```
+
+To control this filter, we can create a React component that shows checkboxes for the security tiers. This component will make use of the `useEntityList` hook, which accepts this extended filter type as a [generic](https://www.typescriptlang.org/docs/handbook/2/generics.html) parameter:
+
+```tsx
+export const EntitySecurityTierPicker = () => {
+ // The securityTiers key is recognized due to the CustomFilter generic
+ const {
+ filters: { securityTiers },
+ updateFilters,
+ } = useEntityList();
+
+ // Toggles the value, depending on whether it's already selected
+ function onChange(value: string) {
+ const newTiers = securityTiers?.values.includes(value)
+ ? securityTiers.values.filter(tier => tier !== value)
+ : [...(securityTiers?.values ?? []), value];
+ updateFilters({
+ securityTiers: newTiers.length
+ ? new EntitySecurityTierFilter(newTiers)
+ : undefined,
+ });
+ }
+
+ const tierOptions = ['1', '2', '3'];
+ return (
+
+ Security Tier
+
+ {tierOptions.map(tier => (
+ onChange(tier)}
+ />
+ }
+ label={`Tier ${tier}`}
+ />
+ ))}
+
+
+ );
+};
+```
+
+Now we can add the component to `CatalogIndexPage`:
+
+```tsx title="packages/app/src/App.tsx"
+{/* prettier-ignore */ /* highlight-add-start */}
+import { DefaultFilters } from '@backstage/plugin-catalog-react';
+{/* prettier-ignore */ /* highlight-add-end */}
+
+const routes = (
+
+
+ {/* highlight-remove-next-line */}
+ } />
+ {/* highlight-add-start */}
+
+
+
+ >
+ }
+ />
+ }
+ />
+ {/* highlight-add-end */}
+ {/* ... */}
+
+);
+```
+
+The same method can be used to customize the _default_ filters with a different interface - for such usage, the generic argument isn't needed since the filter shape remains the same as the default.
+
+## Advanced Customization
+
+For those where none of the above fits their needs you can take the option of creating a fully custom `CatalogIndexPage`.
+
+```tsx title="packages/app/src/components/catalog/CustomCatalogIndex.tsx"
+import {
+ PageWithHeader,
+ Content,
+ ContentHeader,
+ SupportButton,
+} from '@backstage/core-components';
+import { useApi, configApiRef } from '@backstage/core-plugin-api';
+import { CatalogTable } from '@backstage/plugin-catalog';
+import {
+ EntityListProvider,
+ CatalogFilterLayout,
+ EntityKindPicker,
+ EntityLifecyclePicker,
+ EntityNamespacePicker,
+ EntityOwnerPicker,
+ EntityProcessingStatusPicker,
+ EntityTagPicker,
+ EntityTypePicker,
+ UserListPicker,
+} from '@backstage/plugin-catalog-react';
+
+export const CustomCatalogPage = () => {
+ const orgName =
+ useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
+
+ return (
+
+
+
+ All your software catalog entities
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+```
+
+The above is a very basic version of a fully custom `CatalogIndexPage`, you'll want to explore the various props to see what you can all do with them. This was built off the building blocks seen in the [`DefaultCatalogPage`](https://github.com/backstage/backstage/blob/master/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx)
+
+:::note Note
+
+The catalog index page is designed to have a minimal code footprint to support easy customization, but creating a replica does introduce a possibility of drifting out of date over time. Be sure to check the catalog [CHANGELOG](https://github.com/backstage/backstage/blob/master/plugins/catalog/CHANGELOG.md) periodically.
+
+:::
+
+To use this custom `CatalogIndexPage` which we called `CustomCatalogPage`, you'll need to make the following change:
+
+```tsx title="packages/app/src/App.tsx"
+const routes = (
+
+
+ {/* highlight-remove-next-line */}
+ } />
+ {/* highlight-add-start */}
+ }>
+
+
+ {/* highlight-add-end */}
+ {/* ... */}
+
+);
+```
diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md
index 2e984d2942..33789c9b9e 100644
--- a/docs/features/software-catalog/catalog-customization.md
+++ b/docs/features/software-catalog/catalog-customization.md
@@ -4,423 +4,157 @@ title: Catalog Customization
description: How to add custom filters or interface elements to the Backstage software catalog
---
-The Backstage software catalog comes with a default `CatalogIndexPage` to filter and find catalog entities. This is already set up by default by `@backstage/create-app`. If you want to change the default index page - to set the initially selected filter, adjust columns, add actions, or to add a custom filter to the catalog - the following sections will show you how.
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./catalog-customization--old.md)
+instead.
+::::
-## Pagination
+The Backstage software catalog comes with a default catalog index page and entity pages that are highly configurable through `app-config.yaml`. This guide covers how to customize the catalog in the new frontend system.
-Initial support for pagination of the `CatalogIndexPage` was added in v1.21.0 of Backstage, so make sure you are on that version or newer to use this feature. To enable pagination you simply need to pass in the `pagination` prop like this:
+## Catalog index page
-```tsx title="packages/app/src/App.tsx"
-} />
+The catalog index page can be configured through extensions in `app-config.yaml`. For example, to enable pagination:
+
+```yaml title="app-config.yaml"
+app:
+ extensions:
+ - page:catalog:
+ config:
+ pagination: true
```
-## Initially Selected Filter
+You can also configure pagination with additional options:
-By default, the initially selected filter defaults to Owned. If you are still building up your catalog this may show an empty list to start. If you would prefer this to show All as the default, here's how you can make that change:
-
-```tsx title="packages/app/src/App.tsx"
-}
-/>
+```yaml title="app-config.yaml"
+app:
+ extensions:
+ - page:catalog:
+ config:
+ pagination:
+ mode: offset
+ limit: 20
```
-Possible options are: owned, starred, or all
+### Catalog filters
-## Initially Selected Kind
+The catalog index page includes a set of default filters (kind, type, owner, lifecycle, tag, namespace, processing status). These filters can be configured through extensions. For example, to set the initial kind filter:
-By default, the initially selected Kind when viewing the Catalog is Component, but you may have reasons that you want this to be different. Let's say at your Organization they would like it to always default to Domain, here's how you would do that:
-
-```tsx title="packages/app/src/App.tsx"
-} />
+```yaml title="app-config.yaml"
+app:
+ extensions:
+ - catalog-filter:catalog/kind:
+ config:
+ initialFilter: domain
```
-Possible options are all the [default Kinds](system-model.md) as well as any custom Kinds that you have added.
+To set the initial list filter to "all" instead of "owned":
-## Owner Picker Mode
-
-The Owner filter by default will only contain a list of Users and/or Groups that actually own an entity in the Catalog, now you may have reason to change this. Here's how:
-
-```tsx title="packages/app/src/App.tsx"
-} />
+```yaml title="app-config.yaml"
+app:
+ extensions:
+ - catalog-filter:catalog/list:
+ config:
+ initialFilter: all
```
-Possible options are: owners-only or all
+### Custom filters
-## Table Options
+You can create custom catalog filters using the `CatalogFilterBlueprint` from `@backstage/plugin-catalog-react/alpha`. For example, to add a custom security tier filter:
-The tables used within Backstage are built on top of [`@material-table/core`](https://material-table-core.github.io/) and the `CatalogIndexPage` has a `tableOptions` prop that allows you to customize the underlying table to a certain extent, but there are some hard coded Backstage settings that can't be changed. Here's an example of how to use this prop to disable the search filter field in the table's header:
+```tsx title="packages/app/src/catalog/SecurityTierFilter.tsx"
+import { CatalogFilterBlueprint } from '@backstage/plugin-catalog-react/alpha';
-```tsx title="packages/app/src/App.tsx"
-}
-/>
-```
-
-There are many options that can be set using `tableOptions`, the full list of settings can be found in the [`@material-table/core` `Options` interface](https://github.com/material-table-core/core/blob/v3.1.0/types/index.d.ts#L323) (this link goes to `v3.1.0` of `@material-table/core` as that is the version currently used by Backstage).
-
-## Customize Columns
-
-The columns you see in the `CatalogIndexPage` were selected to be a good starting point for most, but there may be cases where you would like to add or remove columns from existing or custom Kinds.
-
-### Adding a column to an existing Kind
-
-Suppose we want to add a new User Email column to the `User` kind in the Catalog. We can do this by overriding the `columns` that we pass into the `CatalogIndexPage` component in our `App.tsx`. First, we need to match the entity kind that we want to override, and then define the columns to show:
-
-```tsx title="packages/app/src/App.tsx"
-{/* prettier-ignore */ /* highlight-add-start */}
-const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => {
- if (entityListContext.filters.kind?.value === 'user') {
- return [
- // Render existing columns
- ...CatalogTable.defaultColumnsFunc(entityListContext),
- // Add new columns here
- ];
- }
-
- return CatalogTable.defaultColumnsFunc(entityListContext);
-};
-{/* prettier-ignore */ /* highlight-add-end */}
-```
-
-Then, we can implement the `createUserEmailColumn` function and add it to the list of columns. `field` is used to access the data from the entity, while `render` lets us customize how we display the data:
-
-```tsx title="packages/app/src/App.tsx"
-{/* highlight-add-start */}
-const createUserEmailColumn = (): TableColumn => ({
- title: 'User Email',
- field: 'entity.spec.profile.email',
- render: ({ entity }) => (
-
- ),
-});
-{/* highlight-add-end */}
-
-const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => {
- if (entityListContext.filters.kind?.value === 'user') {
- return [
- // Render existing columns
- ...CatalogTable.defaultColumnsFunc(entityListContext),
- // Add new columns here
- {/* highlight-add-next-line */}
- createUserEmailColumn(),
- ];
- }
-
- return CatalogTable.defaultColumnsFunc(entityListContext);
-};
-```
-
-Finally, we can pass the `myColumnsFunc` to the `CatalogIndexPage` component:
-
-```tsx title="packages/app/src/App.tsx"
-const routes = (
-
-
- }
- />
- {/* Other routes */}
-
-)
-```
-
-### Adding columns to a custom or specific Kind
-
-Another use case for customization is when adding a custom `Kind`. This feature is available in Backstage >= `v1.23.0`. For example:
-
-```tsx title="packages/app/src/App.tsx"
-import {
- CatalogEntityPage,
- CatalogIndexPage,
- catalogPlugin,
- {/* highlight-add-start */}
- CatalogTable,
- CatalogTableColumnsFunc,
- {/* highlight-add-end */}
-} from '@backstage/plugin-catalog';
-
-{/* highlight-add-start */}
-const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => {
- if (entityListContext.filters.kind?.value === 'MyKind') {
- return [
- CatalogTable.columns.createNameColumn(),
- CatalogTable.columns.createOwnerColumn(),
- ];
- }
-
- return CatalogTable.defaultColumnsFunc(entityListContext);
-};
-{/* highlight-add-end */}
-
-{/* highlight-remove-next-line */}
-} />
-{/* highlight-add-next-line */}
-} />
-```
-
-:::note Note
-
-In the examples above, the contents of the files have been shortened for simplicity.
-
-:::
-
-## Customize Actions
-
-The `CatalogIndexPage` comes with three default actions - view, edit, and star. You might want to add more.
-
-To do this, first you'll need to add `@mui/utils` to your `packages/app/package.json`:
-
-```sh
-yarn --cwd packages/app add @mui/utils
-```
-
-Then you'll do the following:
-
-```tsx title="packages/app/src/App.tsx"
-import {
- AlertDisplay,
- OAuthRequestDialog,
- SignInPage,
- {/* highlight-add-next-line */}
- TableProps,
-} from '@backstage/core-components';
-
-import {
- CatalogEntityPage,
- CatalogIndexPage,
- {/* highlight-add-next-line */}
- CatalogTableRow,
- catalogPlugin,
-} from '@backstage/plugin-catalog';
-
-{/* highlight-add-start */}
-import { Typography } from '@material-ui/core';
-import OpenInNew from '@material-ui/icons/OpenInNew';
-import { visuallyHidden } from '@mui/utils';
-{/* highlight-add-end */}
-
-{/* highlight-add-start */}
-const customActions: TableProps['actions'] = [
- ({ entity }) => {
- const url = 'https://backstage.io/';
- const title = `View - ${entity.metadata.name}`;
-
- return {
- icon: () => (
- <>
- {title}
-
- >
- ),
- tooltip: title,
- disabled: !url,
- onClick: () => {
- if (!url) return;
- window.open(url, '_blank');
- },
- };
+export const securityTierFilter = CatalogFilterBlueprint.make({
+ name: 'security-tier',
+ params: {
+ loader: async () => {
+ const { EntitySecurityTierPicker } = await import(
+ './EntitySecurityTierPicker'
+ );
+ return ;
+ },
},
-];
-{/* highlight-add-end */}
-
-{/* highlight-remove-next-line */}
-} />
-{/* highlight-add-next-line */}
-} />
+});
```
-:::note Note
+Then install it as a frontend module:
-In the example above, the contents of `App.tsx` has been shortened for simplicity.
+```tsx title="packages/app/src/catalog/catalogCustomizations.tsx"
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import { securityTierFilter } from './SecurityTierFilter';
-:::
-
-The above customization will override the existing actions. Currently, the only way to keep them and add your own is to also include the existing actions in your array by copying them from the [`defaultActions`](https://github.com/backstage/backstage/blob/57397e7d6d2d725712c439f4ab93f2ac6aa27bf8/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx#L113-L168).
-
-## Customize Filters
-
-There are various ways to customize filters: adjusting the existing filters with props, adding or removing default filters, creating brand-new custom filters, etc. The following sections cover these cases:
-
-### Default Filter Props
-
-There are a set of default filters that you can use, which surface all the props mentioned earlier in this document. Here's how they can be used:
-
-```tsx title="packages/app/src/App.tsx"
-import { DefaultFilters } from '@backstage/plugin-catalog-react';
-
-
-
- >
- }
- />
- }
-/>;
+export default createFrontendModule({
+ pluginId: 'catalog',
+ extensions: [securityTierFilter],
+});
```
-### Removing Default Filters
-
-If you have reasons not to use the Lifecycle, Tag, and Processing Status filters, here's an example of how to remove them:
+Then register the module in your app:
```tsx title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/frontend-defaults';
+import catalogCustomizations from './catalog/catalogCustomizations';
+
+const app = createApp({
+ features: [catalogCustomizations],
+});
+
+export default app.createRoot();
+```
+
+### Removing default filters
+
+Default filters can be disabled through `app-config.yaml` by setting them to `false`:
+
+```yaml title="app-config.yaml"
+app:
+ extensions:
+ - catalog-filter:catalog/lifecycle: false
+ - catalog-filter:catalog/tag: false
+ - catalog-filter:catalog/processing-status: false
+```
+
+## Customizing columns, actions, and table options
+
+In the old frontend system, customizing the catalog table columns, row actions,
+and table options was done by passing props directly to the `CatalogIndexPage`
+component. In the new frontend system, these customizations are done by
+overriding the `page:catalog` extension.
+
+For example, to customize the catalog index page with custom columns or actions,
+you can override the page extension using a frontend module:
+
+```tsx title="packages/app/src/catalog/customCatalogPage.tsx"
import {
- EntityKindPicker,
- EntityTypePicker,
- UserListPicker,
- EntityOwnerPicker,
- EntityNamespacePicker,
-} from '@backstage/plugin-catalog-react';
+ PageBlueprint,
+ createFrontendModule,
+ createRouteRef,
+} from '@backstage/frontend-plugin-api';
-
-
-
-
-
-
- >
- }
- />
- }
-/>;
+const customCatalogPage = PageBlueprint.make({
+ params: {
+ path: '/catalog',
+ routeRef: createRouteRef({ aliasFor: 'catalog.catalogIndex' }),
+ loader: async () => {
+ const { CustomCatalogPage } = await import('./CustomCatalogPage');
+ return ;
+ },
+ },
+});
+
+export default createFrontendModule({
+ pluginId: 'catalog',
+ extensions: [customCatalogPage],
+});
```
-### Custom Filters
+Inside your custom catalog page component you have full control over the table
+columns, actions, and options. You can compose a page using components from
+`@backstage/plugin-catalog` and `@backstage/plugin-catalog-react`:
-You can add custom filters. For example, suppose that we want to allow filtering by a custom annotation added to entities, `company.com/security-tier`. Here is how we can build a filter to support that need.
-
-First we need to create a new filter that implements the `EntityFilter` interface:
-
-```ts
-import { EntityFilter } from '@backstage/plugin-catalog-react';
-import { Entity } from '@backstage/catalog-model';
-
-class EntitySecurityTierFilter implements EntityFilter {
- constructor(readonly values: string[]) {}
- filterEntity(entity: Entity): boolean {
- const tier = entity.metadata.annotations?.['company.com/security-tier'];
- return tier !== undefined && this.values.includes(tier);
- }
-}
-```
-
-The `EntityFilter` interface permits backend filters, which are passed along to the `catalog-backend` - or frontend filters, which are applied after entities are loaded from the backend.
-
-We'll use this filter to extend the default filters in a type-safe way. Let's create the custom filter shape extending the default somewhere alongside this filter:
-
-```ts
-export type CustomFilters = DefaultEntityFilters & {
- securityTiers?: EntitySecurityTierFilter;
-};
-```
-
-To control this filter, we can create a React component that shows checkboxes for the security tiers. This component will make use of the `useEntityList` hook, which accepts this extended filter type as a [generic](https://www.typescriptlang.org/docs/handbook/2/generics.html) parameter:
-
-```tsx
-export const EntitySecurityTierPicker = () => {
- // The securityTiers key is recognized due to the CustomFilter generic
- const {
- filters: { securityTiers },
- updateFilters,
- } = useEntityList();
-
- // Toggles the value, depending on whether it's already selected
- function onChange(value: string) {
- const newTiers = securityTiers?.values.includes(value)
- ? securityTiers.values.filter(tier => tier !== value)
- : [...(securityTiers?.values ?? []), value];
- updateFilters({
- securityTiers: newTiers.length
- ? new EntitySecurityTierFilter(newTiers)
- : undefined,
- });
- }
-
- const tierOptions = ['1', '2', '3'];
- return (
-
- Security Tier
-
- {tierOptions.map(tier => (
- onChange(tier)}
- />
- }
- label={`Tier ${tier}`}
- />
- ))}
-
-
- );
-};
-```
-
-Now we can add the component to `CatalogIndexPage`:
-
-```tsx title="packages/app/src/App.tsx"
-{/* prettier-ignore */ /* highlight-add-start */}
-import { DefaultFilters } from '@backstage/plugin-catalog-react';
-{/* prettier-ignore */ /* highlight-add-end */}
-
-const routes = (
-
-
- {/* highlight-remove-next-line */}
- } />
- {/* highlight-add-start */}
-
-
-
- >
- }
- />
- }
- />
- {/* highlight-add-end */}
- {/* ... */}
-
-);
-```
-
-The same method can be used to customize the _default_ filters with a different interface - for such usage, the generic argument isn't needed since the filter shape remains the same as the default.
-
-## Advanced Customization
-
-For those where none of the above fits their needs you can take the option of creating a fully custom `CatalogIndexPage`.
-
-```tsx title="packages/app/src/components/catalog/CustomCatalogIndex.tsx"
+```tsx title="packages/app/src/catalog/CustomCatalogPage.tsx"
import {
PageWithHeader,
Content,
@@ -475,41 +209,20 @@ export const CustomCatalogPage = () => {
};
```
-The above is a very basic version of a fully custom `CatalogIndexPage`, you'll want to explore the various props to see what you can all do with them. This was built off the building blocks seen in the [`DefaultCatalogPage`](https://github.com/backstage/backstage/blob/master/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx)
-
:::note Note
-The catalog index page is designed to have a minimal code footprint to support easy customization, but creating a replica does introduce a possibility of drifting out of date over time. Be sure to check the catalog [CHANGELOG](https://github.com/backstage/backstage/blob/master/plugins/catalog/CHANGELOG.md) periodically.
+The catalog index page is designed to have a minimal code footprint to support
+easy customization, but creating a replica does introduce a possibility of
+drifting out of date over time. Be sure to check the catalog
+[CHANGELOG](https://github.com/backstage/backstage/blob/master/plugins/catalog/CHANGELOG.md)
+periodically.
:::
-To use this custom `CatalogIndexPage` which we called `CustomCatalogPage`, you'll need to make the following change:
+For more details on extension overrides and the different override patterns
+available, see the [extension overrides](../../frontend-system/architecture/25-extension-overrides.md) documentation.
-```tsx title="packages/app/src/App.tsx"
-const routes = (
-
-
- {/* highlight-remove-next-line */}
- } />
- {/* highlight-add-start */}
- }>
-
-
- {/* highlight-add-end */}
- {/* ... */}
-
-);
-```
-
-## New Frontend System
-
-This section of the documentation explains how to create and configure catalog extensions in the [new frontend system](../../frontend-system/index.md).
-
-:::warning Warning
-
-This section is a work in progress.
-
-:::
+## Entity page
### Entity filters
diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md
index 37419b8bcf..a2b7a2efea 100644
--- a/docs/features/software-templates/index.md
+++ b/docs/features/software-templates/index.md
@@ -85,25 +85,9 @@ Once the template has finished running, and from the screenshot above, when its
There could be situations where you would like to disable the
`Register Existing Component` button for your users.
-To do so, you need to explicitly disable the default route binding from the `scaffolderPlugin.registerComponent` to the Catalog Import page.
+To do so, you can disable the route binding in your `app-config.yaml`:
-This can be done in `backstage/packages/app/src/App.tsx`:
-
-```diff
- const app = createApp({
- apis,
- bindRoutes({ bind }) {
- bind(scaffolderPlugin.externalRoutes, {
-+ registerComponent: false,
-- registerComponent: catalogImportPlugin.routes.importPage,
- viewTechDoc: techdocsPlugin.routes.docRoot,
- });
-})
-```
-
-OR in `app-config.yaml`:
-
-```yaml
+```yaml title="app-config.yaml"
app:
routes:
bindings:
diff --git a/docs/features/software-templates/writing-custom-field-extensions--old.md b/docs/features/software-templates/writing-custom-field-extensions--old.md
new file mode 100644
index 0000000000..2ba95c8602
--- /dev/null
+++ b/docs/features/software-templates/writing-custom-field-extensions--old.md
@@ -0,0 +1,332 @@
+---
+id: writing-custom-field-extensions--old
+title: Writing Custom Field Extensions (Old Frontend System)
+description: How to write your own field extensions
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./writing-custom-field-extensions.md) instead.
+::::
+
+Collecting input from the user is a very large part of the scaffolding process
+and Software Templates as a whole. Sometimes the built in components and fields
+just aren't good enough, and sometimes you want to enrich the form that the
+users sees with better inputs that fit better.
+
+This is where `Custom Field Extensions` come in.
+
+With them you can show your own `React` Components and use them to control the
+state of the JSON schema, as well as provide your own validation functions to
+validate the data too.
+
+## Creating a Field Extension
+
+Field extensions are a way to combine an ID, a `React` Component and a
+`validation` function together in a modular way that you can then use to pass to
+the `Scaffolder` frontend plugin in your own `App.tsx`.
+
+You can create your own Field Extension by using the
+[`createScaffolderFieldExtension`](https://backstage.io/api/stable/variables/_backstage_plugin-scaffolder.index.createScaffolderFieldExtension.html)
+`API` like below.
+
+As an example, we will create a component that validates whether a string is in the `Kebab-case` pattern:
+
+```tsx
+//packages/app/src/scaffolder/ValidateKebabCase/ValidateKebabCaseExtension.tsx
+import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
+import type { FieldValidation } from '@rjsf/utils';
+import FormControl from '@material-ui/core/FormControl';
+import FormHelperText from '@material-ui/core/FormHelperText';
+import Input from '@material-ui/core/Input';
+import InputLabel from '@material-ui/core/InputLabel';
+/*
+ This is the actual component that will get rendered in the form
+*/
+export const ValidateKebabCase = ({
+ onChange,
+ rawErrors,
+ required,
+ formData,
+}: FieldExtensionComponentProps) => {
+ return (
+ 0 && !formData}
+ >
+ Name
+ onChange(e.target?.value)}
+ />
+
+ Use only letters, numbers, hyphens and underscores
+
+
+ );
+};
+
+/*
+ This is a validation function that will run when the form is submitted.
+ You will get the value from the `onChange` handler before as the value here to make sure that the types are aligned\
+*/
+
+export const validateKebabCaseValidation = (
+ value: string,
+ validation: FieldValidation,
+) => {
+ const kebabCase = /^[a-z0-9-_]+$/g.test(value);
+
+ if (kebabCase === false) {
+ validation.addError(
+ `Only use letters, numbers, hyphen ("-") and underscore ("_").`,
+ );
+ }
+};
+```
+
+```tsx
+// packages/app/src/scaffolder/ValidateKebabCase/extensions.ts
+
+/*
+ This is where the magic happens and creates the custom field extension.
+
+ Note that if you're writing extensions part of a separate plugin,
+ then please use `scaffolderPlugin.provide` from there instead and export it part of your `plugin.ts` rather than re-using the `scaffolder.plugin`.
+*/
+
+import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
+import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react';
+import {
+ ValidateKebabCase,
+ validateKebabCaseValidation,
+} from './ValidateKebabCaseExtension';
+
+export const ValidateKebabCaseFieldExtension = scaffolderPlugin.provide(
+ createScaffolderFieldExtension({
+ name: 'ValidateKebabCase',
+ component: ValidateKebabCase,
+ validation: validateKebabCaseValidation,
+ }),
+);
+```
+
+```tsx
+// packages/app/src/scaffolder/ValidateKebabCase/index.ts
+
+export { ValidateKebabCaseFieldExtension } from './extensions';
+```
+
+Once all these files are in place, you then need to provide your custom
+extension to the `scaffolder` plugin.
+
+You do this in `packages/app/src/App.tsx`. You need to provide the
+`customFieldExtensions` as children to the `ScaffolderPage`.
+
+```tsx
+const routes = (
+
+ ...
+ } />
+ ...
+
+);
+```
+
+Should look something like this instead:
+
+```tsx
+import { ValidateKebabCaseFieldExtension } from './scaffolder/ValidateKebabCase';
+import { ScaffolderFieldExtensions } from '@backstage/plugin-scaffolder-react';
+
+const routes = (
+
+ ...
+ }>
+
+
+
+
+ ...
+
+);
+```
+
+### Async Validation Function
+
+A validation function can be asynchronous and use [Utility APIs](https://backstage.io/docs/api/utility-apis/) via the `ApiHolder` in the [field validation context](https://backstage.io/api/stable/types/_backstage_plugin-scaffolder-react.index.CustomFieldValidator.html). The example below uses the `catalogApiRef` to check if the submitted value (in this scenario an entity ref) exists in the catalog.
+
+```tsx
+import { FieldValidation } from '@rjsf/utils';
+import { ApiHolder } from '@backstage/core-plugin-api';
+import { catalogApiRef } from '@backstage/plugin-catalog-react';
+
+/*
+ This validation function checks if the submitted entity ref value is present in the catalog.
+*/
+
+export const customFieldExtensionValidator = async (
+ value: string,
+ validation: FieldValidation,
+ context: { apiHolder: ApiHolder },
+) => {
+ const catalogApi = context.apiHolder.get(catalogApiRef);
+
+ if ((await catalogApi?.getEntityByRef(value)) === undefined) {
+ validation.addError('Entity not found');
+ }
+};
+```
+
+## Using the Custom Field Extension
+
+Once it's been passed to the `ScaffolderPage` you should now be able to use the
+`ui:field` property in your templates to point it to the name of the
+`customFieldExtension` that you registered.
+
+Something like this:
+
+```yaml
+apiVersion: scaffolder.backstage.io/v1beta3
+kind: Template
+metadata:
+ name: Test template
+ title: Test template with custom extension
+ description: Test template
+spec:
+ parameters:
+ - title: Fill in some steps
+ required:
+ - name
+ properties:
+ name:
+ title: Name
+ type: string
+ description: My custom name for the component
+ ui:field: ValidateKebabCase
+ steps:
+ [...]
+```
+
+## Access Data from other Fields
+
+Custom fields extensions can read data from other fields in the form via the form context. This
+is something that we discourage due to the coupling that it creates, but is sometimes still
+the most sensible solution.
+
+```tsx
+const CustomFieldExtensionComponent = (props: FieldExtensionComponentProps) => {
+ const { formData } = props.formContext;
+ ...
+};
+
+const CustomFieldExtension = scaffolderPlugin.provide(
+ createScaffolderFieldExtension({
+ name: ...,
+ component: CustomFieldExtensionComponent,
+ validation: ...
+ })
+);
+```
+
+## Previewing Custom Field Extensions
+
+You can preview custom field extensions you write in the Backstage UI using the Custom Field Explorer
+(accessible via the `/create/edit` route by default):
+
+
+
+In order to make your new custom field extension available in the explorer you will have to define a
+JSON schema that describes the input/output types on your field like in the following example:
+
+```tsx
+//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx
+export const MyCustomExtensionWithOptionsSchema = {
+ uiOptions: {
+ type: 'object',
+ properties: {
+ focused: {
+ description: 'Whether to focus this field',
+ type: 'boolean',
+ },
+ },
+ },
+ returnValue: { type: 'string' },
+};
+
+export const MyCustomExtensionWithOptions = ({
+ onChange,
+ rawErrors,
+ required,
+ formData,
+}: FieldExtensionComponentProps) => {
+ return (
+ 0 && !formData}
+ onChange={onChange}
+ focused={focused}
+ />
+ );
+};
+```
+
+```tsx
+// packages/app/src/scaffolder/MyCustomExtensionWithOptions/extensions.ts
+...
+import { MyCustomExtensionWithOptions, MyCustomExtensionWithOptionsSchema } from './MyCustomExtensionWithOptions';
+
+export const MyCustomFieldWithOptionsExtension = scaffolderPlugin.provide(
+ createScaffolderFieldExtension({
+ name: 'MyCustomExtensionWithOptions',
+ component: MyCustomExtensionWithOptions,
+ schema: MyCustomExtensionWithOptionsSchema,
+ }),
+);
+```
+
+We recommend using a library like [zod](https://github.com/colinhacks/zod) to define your schema
+and the provided `makeFieldSchemaFromZod` helper utility function to generate both the JSON schema
+and type for your field props to preventing having to duplicate the definitions:
+
+```tsx
+//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx
+...
+import { z } from 'zod/v3';
+import { makeFieldSchemaFromZod } from '@backstage/plugin-scaffolder';
+
+const MyCustomExtensionWithOptionsFieldSchema = makeFieldSchemaFromZod(
+ z.string(),
+ z.object({
+ focused: z
+ .boolean()
+ .optional()
+ .describe('Whether to focus this field'),
+ }),
+);
+
+export const MyCustomExtensionWithOptionsSchema = MyCustomExtensionWithOptionsFieldSchema.schema;
+
+type MyCustomExtensionWithOptionsProps = typeof MyCustomExtensionWithOptionsFieldSchema.type;
+
+export const MyCustomExtensionWithOptions = ({
+ onChange,
+ rawErrors,
+ required,
+ formData,
+}: MyCustomExtensionWithOptionsProps) => {
+ return (
+ 0 && !formData}
+ onChange={onChange}
+ focused={focused}
+ />
+ );
+};
+```
diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md
index f2e8486a07..94505aa2fd 100644
--- a/docs/features/software-templates/writing-custom-field-extensions.md
+++ b/docs/features/software-templates/writing-custom-field-extensions.md
@@ -4,6 +4,13 @@ title: Writing Custom Field Extensions
description: How to write your own field extensions
---
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./writing-custom-field-extensions--old.md)
+instead.
+::::
+
Collecting input from the user is a very large part of the scaffolding process
and Software Templates as a whole. Sometimes the built in components and fields
just aren't good enough, and sometimes you want to enrich the form that the
@@ -18,26 +25,26 @@ validate the data too.
## Creating a Field Extension
Field extensions are a way to combine an ID, a `React` Component and a
-`validation` function together in a modular way that you can then use to pass to
-the `Scaffolder` frontend plugin in your own `App.tsx`.
+`validation` function together in a modular way that you can then register as
+an extension in your Backstage app.
-You can create your own Field Extension by using the
-[`createScaffolderFieldExtension`](https://backstage.io/api/stable/variables/_backstage_plugin-scaffolder.index.createScaffolderFieldExtension.html)
-`API` like below.
+You can create your own field extension by using the `FormFieldBlueprint` from
+`@backstage/plugin-scaffolder-react/alpha` together with `createFormField`, which
+types the component, validation, and optional schema.
-As an example, we will create a component that validates whether a string is in the `Kebab-case` pattern:
+As an example, we will create a component that validates whether a string is in the `Kebab-case` pattern.
+
+First, create the component and validation function:
```tsx
-//packages/app/src/scaffolder/ValidateKebabCase/ValidateKebabCaseExtension.tsx
+// packages/app/src/scaffolder/ValidateKebabCase/ValidateKebabCaseExtension.tsx
import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
import type { FieldValidation } from '@rjsf/utils';
import FormControl from '@material-ui/core/FormControl';
import FormHelperText from '@material-ui/core/FormHelperText';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
-/*
- This is the actual component that will get rendered in the form
-*/
+
export const ValidateKebabCase = ({
onChange,
rawErrors,
@@ -63,11 +70,6 @@ export const ValidateKebabCase = ({
);
};
-/*
- This is a validation function that will run when the form is submitted.
- You will get the value from the `onChange` handler before as the value here to make sure that the types are aligned\
-*/
-
export const validateKebabCaseValidation = (
value: string,
validation: FieldValidation,
@@ -82,71 +84,58 @@ export const validateKebabCaseValidation = (
};
```
+Then, create the extension using `FormFieldBlueprint` and `createFormField`:
+
```tsx
// packages/app/src/scaffolder/ValidateKebabCase/extensions.ts
-
-/*
- This is where the magic happens and creates the custom field extension.
-
- Note that if you're writing extensions part of a separate plugin,
- then please use `scaffolderPlugin.provide` from there instead and export it part of your `plugin.ts` rather than re-using the `scaffolder.plugin`.
-*/
-
-import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
-import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react';
+import {
+ FormFieldBlueprint,
+ createFormField,
+} from '@backstage/plugin-scaffolder-react/alpha';
import {
ValidateKebabCase,
validateKebabCaseValidation,
} from './ValidateKebabCaseExtension';
-export const ValidateKebabCaseFieldExtension = scaffolderPlugin.provide(
- createScaffolderFieldExtension({
- name: 'ValidateKebabCase',
- component: ValidateKebabCase,
- validation: validateKebabCaseValidation,
- }),
-);
+export const ValidateKebabCaseFieldExtension = FormFieldBlueprint.make({
+ name: 'validate-kebab-case',
+ params: {
+ field: async () =>
+ createFormField({
+ name: 'ValidateKebabCase',
+ component: ValidateKebabCase,
+ validation: validateKebabCaseValidation,
+ }),
+ },
+});
```
```tsx
// packages/app/src/scaffolder/ValidateKebabCase/index.ts
-
export { ValidateKebabCaseFieldExtension } from './extensions';
```
-Once all these files are in place, you then need to provide your custom
-extension to the `scaffolder` plugin.
+Once the extension is created, install it in your app by wrapping it in a frontend module and passing it to `createApp`:
-You do this in `packages/app/src/App.tsx`. You need to provide the
-`customFieldExtensions` as children to the `ScaffolderPage`.
+```tsx title="packages/app/src/scaffolder/scaffolderModule.ts"
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import { ValidateKebabCaseFieldExtension } from './ValidateKebabCase';
-```tsx
-const routes = (
-
- ...
- } />
- ...
-
-);
+export const scaffolderCustomizations = createFrontendModule({
+ pluginId: 'scaffolder',
+ extensions: [ValidateKebabCaseFieldExtension],
+});
```
-Should look something like this instead:
+```tsx title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/frontend-defaults';
+import { scaffolderCustomizations } from './scaffolder/scaffolderModule';
-```tsx
-import { ValidateKebabCaseFieldExtension } from './scaffolder/ValidateKebabCase';
-import { ScaffolderFieldExtensions } from '@backstage/plugin-scaffolder-react';
+const app = createApp({
+ features: [scaffolderCustomizations],
+});
-const routes = (
-
- ...
- }>
-
-
-
-
- ...
-
-);
+export default app.createRoot();
```
### Async Validation Function
@@ -158,10 +147,6 @@ import { FieldValidation } from '@rjsf/utils';
import { ApiHolder } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
-/*
- This validation function checks if the submitted entity ref value is present in the catalog.
-*/
-
export const customFieldExtensionValidator = async (
value: string,
validation: FieldValidation,
@@ -177,11 +162,8 @@ export const customFieldExtensionValidator = async (
## Using the Custom Field Extension
-Once it's been passed to the `ScaffolderPage` you should now be able to use the
-`ui:field` property in your templates to point it to the name of the
-`customFieldExtension` that you registered.
-
-Something like this:
+Once registered, you can use the `ui:field` property in your templates to
+reference the name of the custom field extension:
```yaml
apiVersion: scaffolder.backstage.io/v1beta3
@@ -212,18 +194,28 @@ is something that we discourage due to the coupling that it creates, but is some
the most sensible solution.
```tsx
+import {
+ FormFieldBlueprint,
+ createFormField,
+} from '@backstage/plugin-scaffolder-react/alpha';
+import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
+
const CustomFieldExtensionComponent = (props: FieldExtensionComponentProps) => {
const { formData } = props.formContext;
...
};
-const CustomFieldExtension = scaffolderPlugin.provide(
- createScaffolderFieldExtension({
- name: ...,
- component: CustomFieldExtensionComponent,
- validation: ...
- })
-);
+const CustomFieldExtension = FormFieldBlueprint.make({
+ name: 'custom-field',
+ params: {
+ field: async () =>
+ createFormField({
+ name: 'custom-field',
+ component: CustomFieldExtensionComponent,
+ validation: ...,
+ }),
+ },
+});
```
## Previewing Custom Field Extensions
@@ -237,7 +229,10 @@ In order to make your new custom field extension available in the explorer you w
JSON schema that describes the input/output types on your field like in the following example:
```tsx
-//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx
+// packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx
+import FormControl from '@material-ui/core/FormControl';
+import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
+
export const MyCustomExtensionWithOptionsSchema = {
uiOptions: {
type: 'object',
@@ -256,7 +251,10 @@ export const MyCustomExtensionWithOptions = ({
rawErrors,
required,
formData,
+ uiSchema,
}: FieldExtensionComponentProps) => {
+ const focused = uiSchema['ui:options']?.focused;
+
return (
+ createFormField({
+ name: 'MyCustomExtensionWithOptions',
+ component: MyCustomExtensionWithOptions,
+ schema: MyCustomExtensionWithOptionsSchema,
+ }),
+ },
+});
```
We recommend using a library like [zod](https://github.com/colinhacks/zod) to define your schema
and the provided `makeFieldSchemaFromZod` helper utility function to generate both the JSON schema
-and type for your field props to preventing having to duplicate the definitions:
+and type for your field props to prevent having to duplicate the definitions:
```tsx
-//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx
-...
+// packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx
+import FormControl from '@material-ui/core/FormControl';
import { z } from 'zod/v3';
import { makeFieldSchemaFromZod } from '@backstage/plugin-scaffolder';
const MyCustomExtensionWithOptionsFieldSchema = makeFieldSchemaFromZod(
z.string(),
z.object({
- focused: z
- .boolean()
- .optional()
- .describe('Whether to focus this field'),
+ focused: z.boolean().optional().describe('Whether to focus this field'),
}),
);
-export const MyCustomExtensionWithOptionsSchema = MyCustomExtensionWithOptionsFieldSchema.schema;
+export const MyCustomExtensionWithOptionsSchema =
+ MyCustomExtensionWithOptionsFieldSchema.schema;
-type MyCustomExtensionWithOptionsProps = typeof MyCustomExtensionWithOptionsFieldSchema.type;
+type MyCustomExtensionWithOptionsProps =
+ typeof MyCustomExtensionWithOptionsFieldSchema.type;
export const MyCustomExtensionWithOptions = ({
onChange,
rawErrors,
required,
formData,
+ uiSchema,
}: MyCustomExtensionWithOptionsProps) => {
+ const focused = uiSchema['ui:options']?.focused;
+
return (
{
+ const mid = Math.ceil(properties.length / 2);
+
+ return (
+ <>
+
{title}
+
In two column layout!!
+
+ {properties.slice(0, mid).map(prop => (
+
+ {prop.content}
+
+ ))}
+ {properties.slice(mid).map(prop => (
+
+ {prop.content}
+
+ ))}
+
+ {description}
+ >
+ );
+};
+
+export const TwoColumnLayout = scaffolderPlugin.provide(
+ createScaffolderLayout({
+ name: 'TwoColumn',
+ component: TwoColumn,
+ }),
+);
+```
+
+After you have registered your component as a custom layout then you need to provide the `layouts` to the `ScaffolderPage`:
+
+```tsx
+import { MyCustomFieldExtension } from './scaffolder/MyCustomExtension';
+import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts';
+
+const routes = (
+
+ ...
+ }>
+
+
+
+
+ ...
+
+);
+```
+
+## Using the custom step layout
+
+Any component that has been passed to the `ScaffolderPage` as children of the `ScaffolderLayouts` component can be used as a `ui:ObjectFieldTemplate` in your template file:
+
+```yaml
+parameters:
+ - title: Fill in some steps
+ ui:ObjectFieldTemplate: TwoColumn
+```
diff --git a/docs/features/software-templates/writing-custom-step-layouts.md b/docs/features/software-templates/writing-custom-step-layouts.md
index ad766270f2..03e496617d 100644
--- a/docs/features/software-templates/writing-custom-step-layouts.md
+++ b/docs/features/software-templates/writing-custom-step-layouts.md
@@ -4,85 +4,19 @@ title: Writing custom step layouts
description: How to override the default step form layout
---
-Every form in each step rendered in the frontend uses the default form layout from [react-jsonschema-form](https://rjsf-team.github.io/react-jsonschema-form/docs/). It is possible to override this behaviour by supplying a `ui:ObjectFieldTemplate` property for a particular step:
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./writing-custom-step-layouts--old.md)
+instead.
+::::
-```yaml
-parameters:
- - title: Fill in some steps
- ui:ObjectFieldTemplate: TwoColumn
-```
+:::caution
+Custom step layouts are not yet supported in the new frontend system. The
+scaffolder plugin does not provide an extension blueprint or input for
+registering custom layouts in the new system.
-This is the same [field](https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/custom-templates#objectfieldtemplate) used by [react-jsonschema-form](https://rjsf-team.github.io/react-jsonschema-form/docs/) but we need to add a couple of steps to ensure that the string value of `TwoColumn` above is resolved to a react component.
-
-## Registering a React component as a custom step layout
-
-The [createScaffolderLayout](https://backstage.io/api/stable/functions/_backstage_plugin-scaffolder-react.index.createScaffolderLayout.html) function is used to mark a component as a custom step layout:
-
-```tsx
-import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
-import {
- createScaffolderLayout,
- LayoutTemplate,
-} from '@backstage/plugin-scaffolder-react';
-import { Grid } from '@material-ui/core';
-
-const TwoColumn: LayoutTemplate = ({ properties, description, title }) => {
- const mid = Math.ceil(properties.length / 2);
-
- return (
- <>
-
{title}
-
In two column layout!!
-
- {properties.slice(0, mid).map(prop => (
-
- {prop.content}
-
- ))}
- {properties.slice(mid).map(prop => (
-
- {prop.content}
-
- ))}
-
- {description}
- >
- );
-};
-
-export const TwoColumnLayout = scaffolderPlugin.provide(
- createScaffolderLayout({
- name: 'TwoColumn',
- component: TwoColumn,
- }),
-);
-```
-
-After you have registered your component as a custom layout then you need to provide the `layouts` to the `ScaffolderPage`:
-
-```tsx
-import { MyCustomFieldExtension } from './scaffolder/MyCustomExtension';
-import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts';
-
-const routes = (
-
- ...
- }>
-
-
-
-
- ...
-
-);
-```
-
-## Using the custom step layout
-
-Any component that has been passed to the `ScaffolderPage` as children of the `ScaffolderLayouts` component can be used as a `ui:ObjectFieldTemplate` in your template file:
-
-```yaml
-parameters:
- - title: Fill in some steps
- ui:ObjectFieldTemplate: TwoColumn
-```
+If you need custom step layouts, you can continue using the
+[old frontend system](./writing-custom-step-layouts--old.md) approach with
+`createScaffolderLayout` and the `ScaffolderLayouts` component.
+:::
diff --git a/docs/features/techdocs/getting-started--old.md b/docs/features/techdocs/getting-started--old.md
new file mode 100644
index 0000000000..989adf0ef9
--- /dev/null
+++ b/docs/features/techdocs/getting-started--old.md
@@ -0,0 +1,242 @@
+---
+id: techdocs-getting-started--old
+title: Getting Started (Old Frontend System)
+description: Getting Started Documentation
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./getting-started.md) instead.
+::::
+
+TechDocs functions as a plugin in Backstage and ships with it installed out of the box, so you will need to use Backstage to use TechDocs.
+
+If you haven't setup Backstage already, start [here](../../getting-started/index.md).
+
+## Adding TechDocs frontend plugin
+
+The first step is to add the TechDocs plugin to your Backstage application.
+Navigate to your new Backstage application directory. And then to your
+`packages/app` directory, and install the `@backstage/plugin-techdocs` package.
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/app add @backstage/plugin-techdocs
+```
+
+Once the package has been installed, you need to import the plugin in your app.
+
+In `packages/app/src/App.tsx`, import `TechDocsPage` and add the following to
+`FlatRoutes`:
+
+```tsx title="packages/app/src/App.tsx"
+import {
+ DefaultTechDocsHome,
+ TechDocsIndexPage,
+ TechDocsReaderPage,
+} from '@backstage/plugin-techdocs';
+
+const AppRoutes = () => {
+
+ {/* ... other plugin routes */}
+ }>
+
+
+ }
+ />
+ ;
+};
+```
+
+It would be nice to decorate your pages with something else... Having a link that redirects you to a new issue page when you highlight text in your documentation would be really cool, right? Let's learn how to do this using the TechDocs Addon Framework!
+
+With the [TechDocs Addon framework](https://backstage.io/docs/features/techdocs/addons#installing-and-using-addons), you can render React components in documentation pages and these Addons can be provided by any Backstage plugin. The framework is exported by the [@backstage/plugin-techdocs-react](https://www.npmjs.com/package/@backstage/plugin-techdocs-react) package and there is a `` Addon in the [@backstage/plugin-techdocs-module-addons-contrib](https://www.npmjs.com/package/@backstage/plugin-techdocs-module-addons-contrib) package for you to use once you have these two dependencies installed:
+
+```tsx
+import {
+ DefaultTechDocsHome,
+ TechDocsIndexPage,
+ TechDocsReaderPage,
+} from '@backstage/plugin-techdocs';
+/* highlight-add-start */
+import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
+import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
+/* highlight-add-end */
+
+const AppRoutes = () => {
+
+ {/* ... other plugin routes */}
+ }>
+
+
+ }
+ >
+ {/* highlight-add-start */}
+
+
+
+ {/* highlight-add-end */}
+
+ ;
+};
+```
+
+I know, you're curious to see how it looks, aren't you? See the image below:
+
+
+
+
+
+By clicking the open new issue button, you will be redirected to the new issue page according to the source code provider you are using:
+
+
+
+
+
+That's it! Now, we need the TechDocs Backend plugin for the frontend to work.
+
+## Adding TechDocs Backend plugin
+
+First we need to install the `@backstage/plugin-techdocs-backend` package.
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/backend add @backstage/plugin-techdocs-backend
+```
+
+Then in your backend `index.ts` you will add the following line.
+
+```ts title="packages/backend/src/index.ts"
+const backend = createBackend();
+
+// Other plugins...
+
+/* highlight-add-start */
+backend.add(import('@backstage/plugin-techdocs-backend'));
+/* highlight-add-end */
+
+backend.start();
+```
+
+That's it! TechDocs frontend and backend have now been added to your Backstage
+app. Now let us tweak some configurations to suit your needs.
+
+## Setting the configuration
+
+**See [TechDocs Configuration Options](configuration.md) for complete
+configuration reference.**
+
+### Should TechDocs Backend generate docs?
+
+```yaml
+techdocs:
+ builder: 'local'
+```
+
+Note that we recommend generating docs on CI/CD instead. Read more in the
+"Basic" and "Recommended" sections of the
+[TechDocs Architecture](architecture.md). But if you want to get started quickly
+set `techdocs.builder` to `'local'` so that TechDocs Backend is responsible for
+generating documentation sites. If set to `'external'`, Backstage will assume
+that the sites are being generated on each entity's CI/CD pipeline, and are
+being stored in a storage somewhere.
+
+When `techdocs.builder` is set to `'external'`, TechDocs becomes more or less a
+read-only experience where it serves static files from a storage containing all
+the generated documentation.
+
+### Choosing storage (publisher)
+
+TechDocs needs to know where to store generated documentation sites and where to
+fetch the sites from. This is managed by a
+[Publisher](./concepts.md#techdocs-publisher). Examples: Google Cloud Storage,
+Amazon S3, or local filesystem of Backstage server.
+
+It is okay to use the local filesystem in a "basic" setup when you are trying
+out Backstage for the first time. At a later time, review
+[Using Cloud Storage](./using-cloud-storage.md).
+
+```yaml
+techdocs:
+ builder: 'local'
+ publisher:
+ type: 'local'
+```
+
+### Disabling Docker in Docker situation (Optional)
+
+You can skip this if your `techdocs.builder` is set to `'external'`.
+
+The TechDocs Backend plugin runs a docker container with mkdocs installed to
+generate the frontend of the docs from source files (Markdown). If you are
+deploying Backstage using Docker, this will mean that your Backstage Docker
+container will try to run another Docker container for TechDocs Backend.
+
+To avoid this problem, we have a configuration available. You can set a value in
+your `app-config.yaml` that tells the techdocs generator if it should run the
+`local` mkdocs or run it from `docker`. This defaults to running as `docker` if
+no config is provided.
+
+```yaml
+techdocs:
+ builder: 'local'
+ publisher:
+ type: 'local'
+ generator:
+ runIn: local
+```
+
+Setting `generator.runIn` to `local` means you will have to make sure your
+environment is compatible with techdocs.
+
+You will have to install the `mkdocs` and `mkdocs-techdocs-core` package from
+pip, optionally also `graphviz` and `plantuml` from your OS package manager (e.g.
+apt).
+
+You can do so by including the following lines right above `USER node` of your
+`Dockerfile`:
+
+```Dockerfile
+RUN apt-get update && \
+ apt-get install -y python3 python3-pip python3-venv && \
+ rm -rf /var/lib/apt/lists/*
+
+ENV VIRTUAL_ENV=/opt/venv
+RUN python3 -m venv $VIRTUAL_ENV
+ENV PATH="$VIRTUAL_ENV/bin:$PATH"
+
+RUN pip3 install mkdocs-techdocs-core
+```
+
+Please be aware that the version requirement could change, you need to check our
+[`Dockerfile`](https://github.com/backstage/techdocs-container/blob/main/Dockerfile)
+and make sure to match with it.
+
+On a Debian-based Docker container, Python packages must be either installed using
+the OS package manager or within a virtual environment (see the
+[related PEP](https://peps.python.org/pep-0668/)). Alternative is to use e.g.
+[pipx](https://pypa.github.io/pipx/) for installing Python packages in an isolated
+environment.
+
+The above Dockerfile snippet installs the latest `mkdocs-techdoc-core` package.
+Version numbers can be found in the corresponding
+[changelog](https://github.com/backstage/mkdocs-techdocs-core#changelog). In
+case you want to pin the version, use the example below:
+
+```Dockerfile
+RUN pip3 install mkdocs-techdocs-core==1.2.3
+```
+
+Note: We recommend Python version 3.11 or higher.
+
+> Caveat: Please install the `mkdocs-techdocs-core` package after all other
+> Python packages. The order is important to make sure we get correct version of
+> some of the dependencies.
+
+## Additional reading
+
+- [Creating and publishing your docs](creating-and-publishing.md)
+- [Back to README](README.md)
diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md
index 76c231ff0e..db5c5d1c17 100644
--- a/docs/features/techdocs/getting-started.md
+++ b/docs/features/techdocs/getting-started.md
@@ -4,6 +4,13 @@ title: Getting Started
description: Getting Started Documentation
---
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./getting-started--old.md)
+instead.
+::::
+
TechDocs functions as a plugin in Backstage and ships with it installed out of the box, so you will need to use Backstage to use TechDocs.
If you haven't setup Backstage already, start [here](../../getting-started/index.md).
@@ -11,88 +18,52 @@ If you haven't setup Backstage already, start [here](../../getting-started/index
## Adding TechDocs frontend plugin
The first step is to add the TechDocs plugin to your Backstage application.
-Navigate to your new Backstage application directory. And then to your
-`packages/app` directory, and install the `@backstage/plugin-techdocs` package.
```bash title="From your Backstage root directory"
yarn --cwd packages/app add @backstage/plugin-techdocs
```
-Once the package has been installed, you need to import the plugin in your app.
+Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](../../frontend-system/building-apps/05-installing-plugins.md).
-In `packages/app/src/App.tsx`, import `TechDocsPage` and add the following to
-`FlatRoutes`:
+The plugin provides a docs index page at `/docs` and a reader page for individual documentation sites, along with a "Docs" navigation item in the sidebar and a documentation tab on entity pages.
+
+## Using TechDocs Addons
+
+The TechDocs Addon framework lets you render React components in documentation pages. Addons are provided as separate plugin modules.
+
+For example, to add the Report Issue addon, first install the package:
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/app add @backstage/plugin-techdocs-module-addons-contrib
+```
+
+Then install the addon module in your app:
```tsx title="packages/app/src/App.tsx"
-import {
- DefaultTechDocsHome,
- TechDocsIndexPage,
- TechDocsReaderPage,
-} from '@backstage/plugin-techdocs';
+import { createApp } from '@backstage/frontend-defaults';
+import { techDocsReportIssueAddonModule } from '@backstage/plugin-techdocs-module-addons-contrib/alpha';
-const AppRoutes = () => {
-
- {/* ... other plugin routes */}
- }>
-
-
- }
- />
- ;
-};
+const app = createApp({
+ features: [techDocsReportIssueAddonModule],
+});
+
+export default app.createRoot();
```
-It would be nice to decorate your pages with something else... Having a link that redirects you to a new issue page when you highlight text in your documentation would be really cool, right? Let's learn how to do this using the TechDocs Addon Framework!
+The same package also provides `techDocsExpandableNavigationAddonModule`, `techDocsTextSizeAddonModule`, and `techDocsLightBoxAddonModule`.
-With the [TechDocs Addon framework](https://backstage.io/docs/features/techdocs/addons#installing-and-using-addons), you can render React components in documentation pages and these Addons can be provided by any Backstage plugin. The framework is exported by the [@backstage/plugin-techdocs-react](https://www.npmjs.com/package/@backstage/plugin-techdocs-react) package and there is a `` Addon in the [@backstage/plugin-techdocs-module-addons-contrib](https://www.npmjs.com/package/@backstage/plugin-techdocs-module-addons-contrib) package for you to use once you have these two dependencies installed:
-
-```tsx
-import {
- DefaultTechDocsHome,
- TechDocsIndexPage,
- TechDocsReaderPage,
-} from '@backstage/plugin-techdocs';
-/* highlight-add-start */
-import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
-import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
-/* highlight-add-end */
-
-const AppRoutes = () => {
-
- {/* ... other plugin routes */}
- }>
-
-
- }
- >
- {/* highlight-add-start */}
-
-
-
- {/* highlight-add-end */}
-
- ;
-};
-```
-
-I know, you're curious to see how it looks, aren't you? See the image below:
+You can see the Report Issue addon in action when you highlight text in your documentation:

-By clicking the open new issue button, you will be redirected to the new issue page according to the source code provider you are using:
+By clicking the open new issue button, you are redirected to the new issue page according to the source code provider you are using:

-That's it! Now, we need the TechDocs Backend plugin for the frontend to work.
-
## Adding TechDocs Backend plugin
First we need to install the `@backstage/plugin-techdocs-backend` package.
@@ -115,13 +86,11 @@ backend.add(import('@backstage/plugin-techdocs-backend'));
backend.start();
```
-That's it! TechDocs frontend and backend have now been added to your Backstage
-app. Now let us tweak some configurations to suit your needs.
+That's it! TechDocs frontend and backend have now been added to your Backstage app. Now let us tweak some configurations to suit your needs.
## Setting the configuration
-**See [TechDocs Configuration Options](configuration.md) for complete
-configuration reference.**
+**See [TechDocs Configuration Options](configuration.md) for complete configuration reference.**
### Should TechDocs Backend generate docs?
@@ -130,28 +99,15 @@ techdocs:
builder: 'local'
```
-Note that we recommend generating docs on CI/CD instead. Read more in the
-"Basic" and "Recommended" sections of the
-[TechDocs Architecture](architecture.md). But if you want to get started quickly
-set `techdocs.builder` to `'local'` so that TechDocs Backend is responsible for
-generating documentation sites. If set to `'external'`, Backstage will assume
-that the sites are being generated on each entity's CI/CD pipeline, and are
-being stored in a storage somewhere.
+Note that we recommend generating docs on CI/CD instead. Read more in the "Basic" and "Recommended" sections of the [TechDocs Architecture](architecture.md). But if you want to get started quickly set `techdocs.builder` to `'local'` so that TechDocs Backend is responsible for generating documentation sites. If set to `'external'`, Backstage will assume that the sites are being generated on each entity's CI/CD pipeline, and are being stored in a storage somewhere.
-When `techdocs.builder` is set to `'external'`, TechDocs becomes more or less a
-read-only experience where it serves static files from a storage containing all
-the generated documentation.
+When `techdocs.builder` is set to `'external'`, TechDocs becomes more or less a read-only experience where it serves static files from a storage containing all the generated documentation.
### Choosing storage (publisher)
-TechDocs needs to know where to store generated documentation sites and where to
-fetch the sites from. This is managed by a
-[Publisher](./concepts.md#techdocs-publisher). Examples: Google Cloud Storage,
-Amazon S3, or local filesystem of Backstage server.
+TechDocs needs to know where to store generated documentation sites and where to fetch the sites from. This is managed by a [Publisher](./concepts.md#techdocs-publisher). Examples: Google Cloud Storage, Amazon S3, or local filesystem of Backstage server.
-It is okay to use the local filesystem in a "basic" setup when you are trying
-out Backstage for the first time. At a later time, review
-[Using Cloud Storage](./using-cloud-storage.md).
+It is okay to use the local filesystem in a "basic" setup when you are trying out Backstage for the first time. At a later time, review [Using Cloud Storage](./using-cloud-storage.md).
```yaml
techdocs:
@@ -164,15 +120,9 @@ techdocs:
You can skip this if your `techdocs.builder` is set to `'external'`.
-The TechDocs Backend plugin runs a docker container with mkdocs installed to
-generate the frontend of the docs from source files (Markdown). If you are
-deploying Backstage using Docker, this will mean that your Backstage Docker
-container will try to run another Docker container for TechDocs Backend.
+The TechDocs Backend plugin runs a docker container with mkdocs installed to generate the frontend of the docs from source files (Markdown). If you are deploying Backstage using Docker, this will mean that your Backstage Docker container will try to run another Docker container for TechDocs Backend.
-To avoid this problem, we have a configuration available. You can set a value in
-your `app-config.yaml` that tells the techdocs generator if it should run the
-`local` mkdocs or run it from `docker`. This defaults to running as `docker` if
-no config is provided.
+To avoid this problem, we have a configuration available. You can set a value in your `app-config.yaml` that tells the techdocs generator if it should run the `local` mkdocs or run it from `docker`. This defaults to running as `docker` if no config is provided.
```yaml
techdocs:
@@ -183,15 +133,11 @@ techdocs:
runIn: local
```
-Setting `generator.runIn` to `local` means you will have to make sure your
-environment is compatible with techdocs.
+Setting `generator.runIn` to `local` means you will have to make sure your environment is compatible with techdocs.
-You will have to install the `mkdocs` and `mkdocs-techdocs-core` package from
-pip, optionally also `graphviz` and `plantuml` from your OS package manager (e.g.
-apt).
+You will have to install the `mkdocs` and `mkdocs-techdocs-core` package from pip, optionally also `graphviz` and `plantuml` from your OS package manager (e.g. apt).
-You can do so by including the following lines right above `USER node` of your
-`Dockerfile`:
+You can do so by including the following lines right above `USER node` of your `Dockerfile`:
```Dockerfile
RUN apt-get update && \
@@ -205,20 +151,11 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH"
RUN pip3 install mkdocs-techdocs-core
```
-Please be aware that the version requirement could change, you need to check our
-[`Dockerfile`](https://github.com/backstage/techdocs-container/blob/main/Dockerfile)
-and make sure to match with it.
+Please be aware that the version requirement could change, you need to check our [`Dockerfile`](https://github.com/backstage/techdocs-container/blob/main/Dockerfile) and make sure to match with it.
-On a Debian-based Docker container, Python packages must be either installed using
-the OS package manager or within a virtual environment (see the
-[related PEP](https://peps.python.org/pep-0668/)). Alternative is to use e.g.
-[pipx](https://pypa.github.io/pipx/) for installing Python packages in an isolated
-environment.
+On a Debian-based Docker container, Python packages must be either installed using the OS package manager or within a virtual environment (see the [related PEP](https://peps.python.org/pep-0668/)). Alternative is to use e.g. [pipx](https://pypa.github.io/pipx/) for installing Python packages in an isolated environment.
-The above Dockerfile snippet installs the latest `mkdocs-techdoc-core` package.
-Version numbers can be found in the corresponding
-[changelog](https://github.com/backstage/mkdocs-techdocs-core#changelog). In
-case you want to pin the version, use the example below:
+The above Dockerfile snippet installs the latest `mkdocs-techdocs-core` package. Version numbers can be found in the corresponding [changelog](https://github.com/backstage/mkdocs-techdocs-core#changelog). In case you want to pin the version, use the example below:
```Dockerfile
RUN pip3 install mkdocs-techdocs-core==1.2.3
@@ -226,9 +163,7 @@ RUN pip3 install mkdocs-techdocs-core==1.2.3
Note: We recommend Python version 3.11 or higher.
-> Caveat: Please install the `mkdocs-techdocs-core` package after all other
-> Python packages. The order is important to make sure we get correct version of
-> some of the dependencies.
+> Caveat: Please install the `mkdocs-techdocs-core` package after all other Python packages. The order is important to make sure we get correct version of some of the dependencies.
## Additional reading
diff --git a/docs/features/techdocs/how-to-guides--old.md b/docs/features/techdocs/how-to-guides--old.md
new file mode 100644
index 0000000000..175a8e2ee8
--- /dev/null
+++ b/docs/features/techdocs/how-to-guides--old.md
@@ -0,0 +1,1022 @@
+---
+id: techdocs-how-to-guides--old
+title: TechDocs How-To guides (Old Frontend System)
+sidebar_label: How-To guides
+description: TechDocs How-To guides related to TechDocs
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./how-to-guides.md) instead.
+::::
+
+## How to migrate from TechDocs Basic to Recommended deployment approach?
+
+The main difference between TechDocs Basic and Recommended deployment approach
+is where the docs are generated and stored. In Basic or the out-of-the-box
+setup, docs are generated and stored at the server running your Backstage
+instance. But the recommended setup is to generate docs on CI/CD and store the
+generated sites to an external storage (e.g. AWS S3 or GCS). TechDocs in your
+Backstage instance should turn into read-only mode. Read more details and the
+benefits in the [TechDocs Architecture](architecture.md).
+
+Here are the steps needed to switch from the Basic to Recommended setup -
+
+### 1. Prepare a cloud storage
+
+Choose a cloud storage provider like AWS, Google Cloud or Microsoft Azure.
+Follow the detailed instructions for
+[using cloud storage](using-cloud-storage.md) in TechDocs.
+
+### 2. Publish to storage from CI/CD
+
+Start publishing your TechDocs sites from the CI/CD workflow of each repository
+containing the source markdown files. Read the detailed instructions for
+[configuring CI/CD](configuring-ci-cd.md).
+
+### 3. Switch TechDocs to read-only mode
+
+In your Backstage instance's `app-config.yaml`, set `techdocs.builder` from
+`'local'` to `'external'`. By doing this, TechDocs will not try to generate
+docs. Look at [TechDocs configuration](configuration.md) for reference.
+
+## How to understand techdocs-ref annotation values
+
+If TechDocs is configured to generate docs, it will first download source files
+based on the value of the `backstage.io/techdocs-ref` annotation defined in the
+Entity's `catalog-info.yaml` file. This is also called the
+[Prepare](./concepts.md#techdocs-preparer) step.
+
+We strongly recommend that the `backstage.io/techdocs-ref` annotation in each
+documented catalog entity's `catalog-info.yaml` be set to `dir:.` in almost all
+situations. This is because TechDocs is aligned with the "docs like code"
+philosophy, whereby documentation should be authored and managed alongside the
+source code of the underlying software itself.
+
+When you see `dir:.`, you can translate it to mean:
+
+- That the documentation source code lives in the same location as the
+ `catalog-info.yaml` file.
+- That, in particular, the `mkdocs.yml` file is a sibling of `catalog-info.yaml`
+ (meaning, it is in the same directory)
+- And that all of the source content of the documentation would be available if
+ one were to download the directory containing those two files (as well as all
+ sub-directories).
+
+The directory tree of the entity would look something like this:
+
+```
+├── catalog-info.yaml
+├── mkdocs.yml
+└── docs
+ └── index.md
+```
+
+If, for example, you wanted to keep a lean root directory, you could place your
+`mkdocs.yml` file in a subdirectory and update the `backstage.io/techdocs-ref`
+annotation value accordingly, e.g. to `dir:./sub-folder`:
+
+```
+├── catalog-info.yaml
+└── sub-folder
+ ├── mkdocs.yml
+ └── docs
+ └── index.md
+```
+
+In rare situations where your TechDocs source content is managed and stored in a
+location completely separate from your `catalog-info.yaml`, you can instead
+specify a URL location reference, the exact value of which will vary based on
+the source code hosting provider. Notice that instead of the `dir:` prefix, the
+`url:` prefix is used instead. For example:
+
+- **GitHub**: `url:https://githubhost.com/org/repo/tree/`
+- **GitLab**: `url:https://gitlabhost.com/org/repo/tree/`
+- **Bitbucket**: `url:https://bitbuckethost.com/project/repo/src/`
+- **Azure**: `url:https://azurehost.com/organization/project/_git/repository`
+
+Note, just as it's possible to specify a subdirectory with the `dir:` prefix,
+you can also provide a path to a non-root directory inside the repository which
+contains the `mkdocs.yml` file and `docs/` directory. It is important that it is
+suffixed with a '/' in order for relative path resolution to work consistently.
+
+e.g.
+`url:https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend/examples/documented-component/`
+
+### Why is URL Reader faster than a git clone?
+
+URL Reader uses the source code hosting provider to download a zip or tarball of
+the repository. The archive does not have any git history attached to it. Also
+it is a compressed file. Hence the file size is significantly smaller than how
+much data git clone has to transfer.
+
+## How to customize the TechDocs home page?
+
+TechDocs uses a composability pattern similar to the Search and Catalog plugins
+in Backstage. While a default table experience, similar to the one provided by
+the Catalog plugin, is made available for ease-of-use, it's possible for you to
+provide a completely custom experience, tailored to the needs of your
+organization. For example, TechDocs comes with an alternative grid based layout
+(``) and panel layout (`TechDocsCustomHome`).
+
+This is done in your `app` package. By default, you might see something like
+this in your `App.tsx`:
+
+```tsx
+const AppRoutes = () => {
+
+ }>
+
+
+ ;
+};
+```
+
+### Using TechDocsCustomHome
+
+You can easily customize the TechDocs home page using TechDocs panel layout
+(``).
+
+Modify your `App.tsx` as follows:
+
+```tsx
+import { Fragment, PropsWithChildren } from 'react';
+import { TechDocsCustomHome } from '@backstage/plugin-techdocs';
+//...
+
+const options = { emptyRowsWhenPaging: false };
+const linkDestination = (entity: Entity): string | undefined => {
+ return entity.metadata.annotations?.['external-docs'];
+};
+const techDocsTabsConfig = [
+ {
+ label: 'Recommended Documentation',
+ panels: [
+ {
+ title: 'Golden Path',
+ description: 'Documentation about standards to follow',
+ panelType: 'DocsCardGrid',
+ panelProps: { CustomHeader: () => },
+ filterPredicate: entity =>
+ entity?.metadata?.tags?.includes('golden-path') ?? false,
+ },
+ {
+ title: 'Recommended',
+ description: 'Useful documentation',
+ panelType: 'InfoCardGrid',
+ panelProps: {
+ CustomHeader: () =>
+ linkDestination: linkDestination,
+ },
+ filterPredicate: entity =>
+ entity?.metadata?.tags?.includes('recommended') ?? false,
+ },
+ ],
+ },
+ {
+ label: 'Browse All',
+ panels: [
+ {
+ description: 'Browse all docs',
+ filterPredicate: filterEntity,
+ panelType: 'TechDocsIndexPage',
+ title: 'All',
+ panelProps: { PageWrapper: Fragment, CustomHeader: Fragment, options: options },
+ },
+ ],
+ },
+];
+const docsFilter = {
+ kind: ['Location', 'Resource', 'Component'],
+ 'metadata.annotations.featured-docs': CATALOG_FILTER_EXISTS,
+}
+const customPageWrapper = ({ children }: PropsWithChildren<{}>) =>
+ ({children})
+const AppRoutes = () => {
+
+
+ }
+ />
+ ;
+};
+```
+
+### Building a Custom home page
+
+But you can replace `` with any React component, which
+will be rendered in its place. Most likely, you would want to create and
+maintain such a component in a new directory at
+`packages/app/src/components/techdocs`, and import and use it in `App.tsx`:
+
+For example, you can define the following Custom home page component:
+
+```tsx
+import { ReactNode } from 'react';
+
+import { Content } from '@backstage/core-components';
+import {
+ CatalogFilterLayout,
+ EntityOwnerPicker,
+ EntityTagPicker,
+ UserListPicker,
+ EntityListProvider,
+} from '@backstage/plugin-catalog-react';
+import {
+ TechDocsPageWrapper,
+ TechDocsPicker,
+} from '@backstage/plugin-techdocs';
+import { Entity } from '@backstage/catalog-model';
+
+import { EntityListDocsGrid } from '@backstage/plugin-techdocs';
+
+export type CustomTechDocsHomeProps = {
+ groups?: Array<{
+ title: ReactNode;
+ filterPredicate: ((entity: Entity) => boolean) | string;
+ }>;
+};
+
+export const CustomTechDocsHome = ({ groups }: CustomTechDocsHomeProps) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+```
+
+Then you can add the following to your `App.tsx`:
+
+```tsx
+import { CustomTechDocsHome } from './components/techdocs/CustomTechDocsHome';
+// ...
+const AppRoutes = () => {
+
+ }>
+
+ entity?.metadata?.tags?.includes('recommended') ?? false,
+ },
+ {
+ title: 'My Docs',
+ filterPredicate: 'ownedByUser',
+ },
+ ]}
+ />
+
+ ;
+};
+```
+
+## How to customize the TechDocs reader page?
+
+Similar to how it is possible to customize the TechDocs Home, it is also
+possible to customize the TechDocs Reader Page. It is done in your `app`
+package. By default, you might see something like this in your `App.tsx`:
+
+```tsx
+const AppRoutes = () => {
+ }>
+ {techDocsPage}
+ ;
+};
+```
+
+The `techDocsPage` is a default techdocs reader page which lives in
+`packages/app/src/components/techdocs`. It includes the following without you
+having to set anything up.
+
+```tsx
+
+
+
+
+
+```
+
+If you would like to compose your own `techDocsPage`, you can do so by replacing
+the children of TechDocsPage with something else. Maybe you are _just_
+interested in replacing the Header:
+
+```tsx
+
+
+
+
+```
+
+Or maybe you want to disable the in-context search
+
+```tsx
+
+
+
+
+```
+
+Or maybe you want to replace the entire TechDocs Page.
+
+```tsx
+
+
+
+