Search How-To guides
How to implement your own Search API
The Search plugin provides implementation of one primary API by default: the SearchApi, 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.
-
Implement the
SearchApi
interface according to your needs.export class SearchClient implements SearchApi {
// your implementation
} -
Override the API ref
searchApiRef
with your new implemented API in theApp.tsx
usingApiFactories
. Read more about App APIs.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
andlocation
cannot be modified via aentityTransformer
,location
can be modified only throughlocationTemplate
.
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,
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,
entityTransformer: techDocsEntityTransformer,
documentTransformer: techDocsDocumentTransformer,
}),
});
How to limit what can be searched in the Software Catalog
The Software Catalog includes a wealth of information about the components, systems, groups, users, and other aspects of your software ecosystem. However, you may not always want every aspect to appear when a user searches the catalog. Examples include:
- Entities of kind
Location
, which are often not useful to Backstage users. - Entities of kind
User
orGroup
, if you'd prefer that users and groups be exposed to search in a different way (or not at all).
It's possible to write your own Collator to control
exactly what's available to search, (or a Decorator
to filter things out here and there), but the DefaultCatalogCollator
that's
provided by @backstage/plugin-catalog-backend
offers some configuration too!
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: DefaultCatalogCollator.fromConfig(env.config, {
discovery: env.discovery,
tokenManager: env.tokenManager,
filter: {
kind: ['API', 'Component', 'Domain', 'Group', 'System', 'User'],
},
}),
});
As shown above, you can add a catalog entity filter to narrow down what catalog entities are indexed by the search engine.
How to customize search results highlighting styling
The default highlighting styling for matched terms in search results is your
browsers default styles for the <mark>
HTML tag. If you want to customize
how highlighted terms look you can follow Backstage's guide on how to
Customize the look-and-feel of your App
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:
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',
},
},
},
},
});
const app : BackstageApp = createApp({
...
themes: [{
id: 'my-light-theme',
title: 'Light Theme',
variant: 'light',
icon: <LightIcon />,
Provider: ({ children }) => (<UnifiedThemeProvider theme={myLightTheme} children={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<SearchResult />
component.
Using the example below, you can provide an extension to be used as a search result item:
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:
export const YourSearchResultListItemExtension: (
props: SearchResultListItemExtensionProps<YourSearchResultListItemProps>,
) => 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:
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:
export { YourSearchResultListItem } from './plugin.ts';
For more details, see the createSearchResultListItemExtension 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 <SearchResult>
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
:
import React from 'react';
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 = () => (
<Page themeId="home">
<Header title="Search" />
<Content>
<Grid container direction="row">
<Grid item xs={12}>
<Paper>
<SearchBar />
</Paper>
</Grid>
<Grid item xs={12}>
<SearchResult>
<YourSearchResultListItem />
<CatalogSearchResultListItem icon={<CatalogIcon />} />
<TechDocsSearchResultListItem icon={<DocsIcon />} />
<ToolSearchResultListItem icon={<BuildIcon />} />
</SearchResult>
</Grid>
</Grid>
</Content>
</Page>
);
export const searchPage = <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:
import { SidebarSearchModal } from '@backstage/plugin-search';
...
export const Root = ({ children }: PropsWithChildren<{}>) => {
const styles = useStyles();
return <SidebarPage>
<Sidebar>
...
<SidebarSearchModal resultItemComponents={[
/* Provide a custom Extension search item renderer */
<CustomSearchResultListItem icon={<CatalogIcon />} />,
/* Provide an existing search item renderer */
<TechDocsSearchResultListItem icon={<DocsIcon />} />
]} />
...
</Sidebar>
{children}
</SidebarPage>;
};
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:
import React from 'react';
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 }) => (
<>
<DialogTitle>
<Paper>
<SearchBar />
</Paper>
</DialogTitle>
<DialogContent>
<SearchResult onClick={toggleModal}>
<CatalogSearchResultListItem icon={<CatalogIcon />} />
<TechDocsSearchResultListItem icon={<DocsIcon />} />
<ToolSearchResultListItem icon={<BuildIcon />} />
{/* 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 */}
<YourSearchResultListItem />
</SearchResult>
</DialogContent>
</>
);
There are other more specific search results layout components that also accept result item extensions, check their documentation: SearchResultList and SearchResultGroup.