Q&A logo

Backstage Q&A Plugin

Created by drodil

Q&A brings a shared knowledge space into Backstage. It works like a light Stack Overflow for your org. People ask and answer questions. Teams write short articles. You can attach links to catalog entities. Tags and mentions route questions to experts. Votes surface helpful answers. Notifications reach followers of tags, entities, collections, and users. Search shows Q&A next to docs and services. You can group posts into collections. Templates guide repeat requests.

Optional AI can draft answers or suggest tags. The result is faster support for engineers and less noise in chat. Good for on call handoffs, service ownership, and onboarding.

Real teams use it today. The adopters list includes OP Financial Group and QuintoAndar. If your Backstage serves many teams, Q&A gives them a simple place to ask, answer, and share fixes where they already work.

Installation Instructions

These instructions apply to self-hosted Backstage only.

Install the backend

New backend system

  1. Add the backend package
Copy
yarn workspace backend add @drodil/backstage-plugin-qeta-backend
  1. Register the backend plugin
Copy
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';

const backend = createBackend();
// other plugins
backend.add(import('@drodil/backstage-plugin-qeta-backend'));
backend.start();

Old backend system

Version 2 or later needs the new backend system. If you still use the old system, pin version 1.24.5.

  1. Add the pinned backend package
Copy
yarn workspace backend add @drodil/[email protected]
  1. Create the router
Copy
// packages/backend/src/plugins/qeta.ts
import {
  createRouter,
  DatabaseQetaStore,
} from '@drodil/backstage-plugin-qeta-backend';
import { PluginEnvironment } from '../types';

export default async function createPlugin({
  logger,
  database,
  identity,
  config,
}: PluginEnvironment) {
  const db = await DatabaseQetaStore.create({ database });
  return await createRouter({
    logger,
    database: db,
    identity,
    config,
  });
}
  1. Mount the router
Copy
// packages/backend/src/index.ts
import qeta from './plugins/qeta';

// inside createEnv and apiRouter setup
const qetaEnv = useHotMemoize(module, () => createEnv('qeta'));
apiRouter.use('/qeta', await qeta(qetaEnv));

Install the frontend

  1. Add the frontend packages
Copy
yarn workspace app add @drodil/backstage-plugin-qeta @drodil/backstage-plugin-qeta-react
  1. Add the route so users can open Q and A
Copy
// packages/app/src/App.tsx
import React from 'react';
import { FlatRoutes, Route } from '@backstage/core-app-api';
import { QetaPage } from '@drodil/backstage-plugin-qeta';

export const AppRoutes = () => (
  <FlatRoutes>
    {/* other routes */}
    <Route path="/qeta" element={<QetaPage title="Questions" />} />
  </FlatRoutes>
);
  1. Add a sidebar item
Copy
// packages/app/src/components/Root/Root.tsx
import React, { PropsWithChildren } from 'react';
import LiveHelpIcon from '@material-ui/icons/LiveHelp';
import { SidebarItem, SidebarPage } from '@backstage/core-components';

export const Root = ({ children }: PropsWithChildren<{}>) => (
  <SidebarPage>
    {/* other items */}
    <SidebarItem icon={LiveHelpIcon} to="qeta" text="Q&A" />
    {children}
  </SidebarPage>
);

Add Q and A to catalog entity pages

  1. Create a content component
Copy
// packages/app/src/components/catalog/EntityPage/content/QetaContent.tsx
import React from 'react';
import { useEntity } from '@backstage/plugin-catalog-react';
import { Container } from '@material-ui/core';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { PostsContainer } from '@drodil/backstage-plugin-qeta-react';

export const QetaContent = () => {
  const { entity } = useEntity();

  return (
    <Container>
      <PostsContainer
        entity={stringifyEntityRef(entity)}
        showTitle
        showAskButton
        type="question" // or "article"
      />
    </Container>
  );
};
  1. Mount it as a tab on your entity page
Copy
// packages/app/src/components/catalog/EntityPage.tsx
<EntityLayout.Route path="/qeta" title="Q&A">
  <QetaContent />
</EntityLayout.Route>

Optional grid view

Copy
import { PostsGrid } from '@drodil/backstage-plugin-qeta-react';

// inside QetaContent return
<PostsGrid entity={stringifyEntityRef(entity)} showTitle showAskButton type="question" />

Add Q and A to the home page

Copy
// packages/app/src/components/home/HomePage.tsx
import React from 'react';
import { QuestionTableCard } from '@drodil/backstage-plugin-qeta';
import { CustomHomepageGrid } from '@backstage/plugin-home';
import { Content, Page } from '@backstage/core-components';

export const HomePage = (
  <Page themeId="home">
    <Content>
      <CustomHomepageGrid>
        <QuestionTableCard />
      </CustomHomepageGrid>
    </Content>
  </Page>
);
  1. Render the result item
Copy
// packages/app/src/components/search/SearchPage.tsx
import React from 'react';
import { Page } from '@backstage/core-components';
import { SearchBar, SearchContextProvider, SearchResult } from '@backstage/plugin-search-react';
import { QetaSearchResultListItem } from '@drodil/backstage-plugin-qeta';

export const SearchPage = (
  <Page themeId="home">
    <SearchContextProvider>
      <SearchBar />
      <SearchResult>
        <QetaSearchResultListItem />
      </SearchResult>
    </SearchContextProvider>
  </Page>
);
  1. Add the search indexing module on the backend
Copy
yarn workspace backend add @drodil/backstage-plugin-search-backend-module-qeta
Copy
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
import { searchPlugin } from '@backstage/plugin-search-backend/alpha';

const backend = createBackend();
backend.add(searchPlugin());
backend.add(import('@drodil/backstage-plugin-search-backend-module-qeta'));
backend.start();

Configure the plugin

Add minimal config. Defaults work if you do not set these.

Copy
# app-config.yaml
qeta:
  permissions: false
  notifications: true
  storage:
    type: database

You can tune limits and features

Copy
qeta:
  allowAnonymous: false
  entities:
    kinds: [Component]
    max: 3
  tags:
    allowCreation: true
    max: 5
  mentions:
    supportedKinds:
      - User
      - Group
  storage:
    disabled: false
    allowedMimeTypes:
      - png
      - jpg
      - jpeg
      - gif

Optional AI features

Use the OpenAI backend module if you want AI answers or summaries.

  1. Install the module
Copy
yarn workspace backend add @drodil/backstage-plugin-qeta-backend-module-openai
  1. Register it
Copy
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
// other plugins
backend.add(import('@drodil/backstage-plugin-qeta-backend-module-openai'));
backend.start();
  1. Configure the key and options
Copy
# app-config.yaml
qeta:
  openai:
    apiKey: ${OPENAI_API_KEY}
    model: gpt-4o-mini
    temperature: 0.5
    maxTokens: 100
    answer:
      newQuestions: true
      existingQuestions: true
      articleSummary: false
    post:
      tagSuggestions: false

You can also build your own AI handler with the qetaAIExtensionPoint in a custom backend module.

Copy
// example backend module
import { createBackendModule } from '@backstage/backend-plugin-api';
import { qetaAIExtensionPoint } from '@drodil/backstage-plugin-qeta-node';

export const qetaAiModule = createBackendModule({
  pluginId: 'qeta',
  moduleId: 'custom-ai',
  register(reg) {
    reg.registerInit({
      deps: { ai: qetaAIExtensionPoint },
      async init({ ai }) {
        ai.setAIHandler({
          async answerExistingQuestion(question) {
            return { answer: 'Answer' };
          },
          async answerNewQuestion(title, content) {
            return { answer: 'Answer' };
          },
          async summarizeArticle(article) {
            return { answer: 'Summary' };
          },
          async suggestTags(title, content) {
            return { tags: ['example'] };
          },
        });
      },
    });
  },
});

Then add your module to the backend

Copy
backend.add(import('./path/to/qetaAiModule'));

Notes on databases

The plugin works with Postgres and sqlite. It uses your Backstage database connection. No extra setup needed for a basic install.

Summary of what you added

  • Backend plugin registered so the API runs under your backend
  • Frontend page mounted at path qeta with a sidebar link
  • Optional entity tab, home card, and search result item so users see Q and A in common places
  • Optional search indexing module for better search
  • Optional AI module for answers and summaries

Changelog

This changelog is produced from commits made to the Q&A plugin since 14 days ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.

Features

  • Improve icon fetching. Backend fetches the favicon binary and sends it to the UI. New or edited links store the favicon. Favicons use data URLs. Adds a favicon field to post data. #339 10 days ago

Bug fixes

  • Fix link page title. #343 4 days ago

Improvements

  • Improve error logging for link metadata extraction. Add logger warnings. Use the logger in helper functions. #340 6 days ago

Migrations

Run database migrations after upgrade.

  • Update url column in posts table to use the text type. #344 4 days ago
  • Store favicons in the database. Maximum length is 12 000 characters. #339 10 days ago

Set up Backstage in minutes with Roadie