Backstage Search logo

Backstage Backstage Search Plugin

Created by Spotify

Backstage Search gives your developer portal a single place to find things. It plugs into the Backstage frontend with a search page and a quick search modal. It also adds a backend that crawls sources and serves results through a simple API. Together these parts let teams search code metadata, docs, and custom data from one box.

The plugin indexes content from the software catalog and TechDocs. You can add your own collators to bring in data from any system you run. The architecture supports multiple engines so teams can start small then scale. Elasticsearch is recommended for production due to better scale and filtering. Common result views are ready to use. You can also build custom result items that show the fields that matter to your teams.

If you run a self hosted Backstage, this plugin helps engineers find services, docs, templates, and APIs fast. It cuts the time spent jumping between tools and gives a consistent search experience across your portal.

Installation Instructions

These instructions apply to self-hosted Backstage only.

Install the backend on the new backend system

Add the packages

Copy
yarn --cwd packages/backend add @backstage/plugin-search-backend @backstage/plugin-search-backend-module-catalog @backstage/plugin-search-backend-module-techdocs

Register the backend plugins

Put this in packages/backend/src/index.ts

Copy
import { createBackend } from '@backstage/backend-defaults';

const backend = createBackend();

// Search core
backend.add(import('@backstage/plugin-search-backend'));

// Index Software Catalog entities
backend.add(import('@backstage/plugin-search-backend-module-catalog'));

// Index TechDocs content
backend.add(import('@backstage/plugin-search-backend-module-techdocs'));

backend.start();

Optional config for collators

You can tune schedule or filters for the collators in app-config.yaml

Copy
search:
  collators:
    catalog:
      schedule:
        frequency:
          minutes: 30
        timeout:
          minutes: 10
        initialDelay:
          seconds: 10
      # optional example filter
      filter:
        kind: [Component, API]
    techdocs:
      schedule:
        frequency:
          minutes: 30
        timeout:
          minutes: 15
        initialDelay:
          seconds: 10

Add the packages

Copy
yarn --cwd packages/app add @backstage/plugin-search @backstage/plugin-search-react

Add the Search page route

Edit packages/app/src/App.tsx

Copy
import React from 'react';
import { Route } from 'react-router';
import { FlatRoutes } from '@backstage/core-app-api';
import { SearchPage } from '@backstage/plugin-search';

// keep your other imports

export const App = () => (
  <FlatRoutes>
    {/* your other routes */}
    <Route path="/search" element={<SearchPage />} />
  </FlatRoutes>
);

Edit packages/app/src/components/Root/Root.tsx

Copy
import React from 'react';
import { Sidebar, SidebarItem } from '@backstage/core-components';
import SearchIcon from '@mui/icons-material/Search';

// keep your other imports

export const Root = ({ children }: { children?: React.ReactNode }) => (
  <>
    <Sidebar>
      {/* your other sidebar items */}
      <SidebarItem to="/search" icon={SearchIcon} text="Search" />
    </Sidebar>
    {children}
  </>
);

Old backend system setup

The current search backend dropped legacy support in major version two. If you still run the old backend system you need a one x release of the search backend.

Add the packages

Copy
yarn --cwd packages/backend add @backstage/plugin-search-backend@^1.8.2 @backstage/plugin-search-backend-node

If your backend does not already have the catalog backend

Copy
yarn --cwd packages/backend add @backstage/plugin-catalog-backend

Create the search plugin file

Create packages/backend/src/plugins/search.ts

Copy
import { useHotCleanup } from '@backstage/backend-common';
import { createRouter } from '@backstage/plugin-search-backend';
import {
  IndexBuilder,
  LunrSearchEngine,
} from '@backstage/plugin-search-backend-node';
import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend';
import { PluginEnvironment } from '../types';

export default async function createPlugin({
  logger,
  discovery,
}: PluginEnvironment) {
  const searchEngine = new LunrSearchEngine({ logger });
  const indexBuilder = new IndexBuilder({ logger, searchEngine });

  indexBuilder.addCollator({
    defaultRefreshIntervalSeconds: 600,
    collator: new DefaultCatalogCollator({ discovery }),
  });

  const { scheduler } = await indexBuilder.build();

  setTimeout(() => scheduler.start(), 3000);
  useHotCleanup(module, () => scheduler.stop());

  return await createRouter({
    engine: indexBuilder.getSearchEngine(),
    logger,
  });
}

Mount the search router

Edit packages/backend/src/index.ts

Copy
import search from './plugins/search';
import { useHotMemoize } from '@backstage/backend-common';

// inside your main bootstrap function
const searchEnv = useHotMemoize(module, () => createEnv('search'));
apiRouter.use('/search', await search(searchEnv));

Optional search config

You can tune some backend limits

Copy
search:
  maxTermLength: 100

That is it. Install the packages, wire the backend, add the Search page route, add a sidebar link. When you run the app the Search page is reachable at path slash search.

Set up Backstage in minutes with Roadie