GraphQL Catalog gives you a GraphQL view of your Backstage Catalog. It runs inside your Backstage backend and exposes a single endpoint for queries. The core uses GraphQL Modules and Envelop so you can compose schema and middleware from many places into one server. You can start with the catalog module to read entities through GraphQL then grow the schema with your own types and resolvers.
With this plugin you can query services, resources, groups, owners, and their relations in one request. That makes it easy to build custom pages in Backstage that pull exactly the fields you need. It helps with automation too. You can power checks, reports, or dashboards that traverse ownership and dependencies without extra glue code. If you need to join in data from other systems you can add loaders and extend the context so resolvers can fetch from external sources when needed.
It fits the new Backstage backend system and keeps things modular. You can add middleware for tracing or error masking using Envelop. You can explore the API from inside Backstage using a GraphiQL plugin. You can surface the schema in API Docs so teams can see what is available. If you want a single consistent way to read catalog data across plugins and apps this gives you that foundation.
Installation Instructions
These instructions apply to self-hosted Backstage only.
Install the backend plugin and the catalog module with yarn. This gives you a GraphQL endpoint for the Catalog.
yarn --cwd packages/backend add @frontside/backstage-plugin-graphql-backend @frontside/backstage-plugin-graphql-backend-module-catalog
Wire the plugin into the new backend system. Edit packages/backend/src/index.ts. Import the plugin and the catalog module. Then register both with createBackend.
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
import { graphqlPlugin } from '@frontside/backstage-plugin-graphql-backend';
import { graphqlModuleCatalog } from '@frontside/backstage-plugin-graphql-backend-module-catalog';
const backend = createBackend();
backend.use(graphqlPlugin());
backend.use(graphqlModuleCatalog());
backend.start();
The plugin serves GraphQL at the path under api graphql.
Start your backend. Use the workspace name you use locally.
yarn workspace backend start
Optional middleware support for the new backend system. Create a small module that adds an Envelop plugin. Then register it in index.ts.
// packages/backend/src/modules/graphqlPlugins.ts
import { createBackendModule } from '@backstage/backend-plugin-api';
import { graphqlPluginsExtensionPoint } from '@frontside/backstage-plugin-graphql-backend-node';
import { useMaskedErrors } from '@envelop/core';
export const graphqlModulePlugins = createBackendModule({
pluginId: 'graphql',
moduleId: 'plugins',
register(env) {
env.registerInit({
deps: { plugins: graphqlPluginsExtensionPoint },
async init({ plugins }) {
plugins.addPlugins([useMaskedErrors()]);
},
});
},
});
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
import { graphqlPlugin } from '@frontside/backstage-plugin-graphql-backend';
import { graphqlModuleCatalog } from '@frontside/backstage-plugin-graphql-backend-module-catalog';
import { graphqlModulePlugins } from './modules/graphqlPlugins';
const backend = createBackend();
backend.use(graphqlPlugin());
backend.use(graphqlModuleCatalog());
backend.use(graphqlModulePlugins());
backend.start();
Add a basic custom GraphQL module if you want to extend the schema now. Create a schema file and a module file. Then register it with the backend.
# packages/backend/src/modules/my-module/my-module.graphql
extend type Query {
hello: String!
}
// packages/backend/src/modules/my-module/my-module.ts
import { resolvePackagePath } from '@backstage/backend-common';
import { loadFilesSync } from '@graphql-tools/load-files';
import { createModule } from 'graphql-modules';
export const MyModule = createModule({
id: 'my-module',
dirname: resolvePackagePath('backend', 'src/modules/my-module'),
typeDefs: loadFilesSync(
resolvePackagePath('backend', 'src/modules/my-module/my-module.graphql'),
),
resolvers: {
Query: {
hello: () => 'world',
},
},
});
// packages/backend/src/modules/graphqlMyModule.ts
import { createBackendModule } from '@backstage/backend-plugin-api';
import { graphqlModulesExtensionPoint } from '@frontside/backstage-plugin-graphql-backend-node';
import { MyModule } from './my-module/my-module';
export const graphqlModuleMyModule = createBackendModule({
pluginId: 'graphql',
moduleId: 'myModule',
register(env) {
env.registerInit({
deps: { modules: graphqlModulesExtensionPoint },
async init({ modules }) {
await modules.addModules([MyModule]);
},
});
},
});
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
import { graphqlPlugin } from '@frontside/backstage-plugin-graphql-backend';
import { graphqlModuleCatalog } from '@frontside/backstage-plugin-graphql-backend-module-catalog';
import { graphqlModuleMyModule } from './modules/graphqlMyModule';
const backend = createBackend();
backend.use(graphqlPlugin());
backend.use(graphqlModuleCatalog());
backend.use(graphqlModuleMyModule());
backend.start();
Add a GraphiQL page in the app so people can run queries from the portal UI. First add the plugin to the app package.
yarn --cwd packages/app add @backstage/plugin-graphiql
Register the GraphQL endpoint in the app API registry. Edit packages/app/src/apis.ts. Add an endpoint that points to your backend GraphQL path. The page will call discovery to resolve the url.
// packages/app/src/apis.ts
import { GraphQLEndpoints } from '@backstage/plugin-graphiql';
import { createApiFactory, discoveryApiRef, errorApiRef, githubAuthApiRef } from '@backstage/core-plugin-api';
export const apis = [
createApiFactory({
api: GraphQLEndpoints.apiRef,
deps: { errorApi: errorApiRef, githubAuthApi: githubAuthApiRef, discovery: discoveryApiRef },
factory: ({ errorApi, githubAuthApi, discovery }) =>
GraphQLEndpoints.from([
{
id: 'backstage-backend',
title: 'Backstage GraphQL API',
fetcher: async (params: any) => {
const graphqlURL = await discovery.getBaseUrl('graphql');
return fetch(graphqlURL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
}).then(res => res.json());
},
},
]),
}),
];
Expose the GraphiQL page in the router. Add a route so users can reach it. Then link it in the sidebar.
// packages/app/src/App.tsx
import React from 'react';
import { Route } from 'react-router';
import { GraphiQLPage } from '@backstage/plugin-graphiql';
import { FlatRoutes } from '@backstage/core-app-api';
import { apis } from './apis';
export const AppRoutes = () => (
<FlatRoutes>
{/* other routes */}
<Route path="/graphiql" element={<GraphiQLPage />} />
</FlatRoutes>
);
// packages/app/src/components/Root/Root.tsx
import React from 'react';
import CodeIcon from '@mui/icons-material/Code';
import { Sidebar, SidebarGroup, SidebarItem } from '@backstage/core-components';
export const Root = ({ children }: { children?: React.ReactNode }) => (
<Sidebar>
<SidebarGroup label="Menu" icon={<CodeIcon />}>
<SidebarItem icon={CodeIcon} to="/graphiql" text="GraphiQL" />
</SidebarGroup>
</Sidebar>
);
Show the GraphQL schema in API Docs. Create an API entity that reads the schema from the backend schema endpoint. Then allow the backend to read that url.
# catalog-info.yaml or a separate file you register
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: backstage-graphql-api
description: GraphQL API provided by GraphQL Plugin
spec:
type: graphql
owner: [email protected]
lifecycle: production
definition:
$text: http://localhost:7007/api/graphql/schema
# app-config.yaml
backend:
reading:
allow:
- host: localhost:7007
The steps above use the new backend system. If you still run the old backend system you can add the older GraphQL backend package that plugs in as an express router. Create a backend plugin file and mount it under the api path. This keeps the same idea for the Catalog through GraphQL. The core Catalog GraphQL backend has been deprecated in Backstage and the recommended path is the Frontside plugin.
yarn --cwd packages/backend add @frontside/backstage-plugin-graphql
// packages/backend/src/plugins/graphql.ts
import { createRouter } from '@frontside/backstage-plugin-graphql';
import { CatalogClient } from '@backstage/catalog-client';
import { Router } from 'express';
import { Logger } from 'winston';
interface PluginEnvironment {
logger: Logger;
catalog: CatalogClient;
}
export default async function createPlugin(env: PluginEnvironment): Promise<Router> {
return await createRouter({
logger: env.logger,
catalog: env.catalog,
});
}
Mount that router in the old backend index. Put it under the api path so the page stays the same for users.
// packages/backend/src/index.ts
import graphql from './plugins/graphql';
// inside your service builder setup
apiRouter.use('/api/graphql', await graphql({
logger: env.logger,
catalog: new CatalogClient({ discoveryApi: env.discovery }),
}));
That is all you need to serve GraphQL for the Catalog and to give users a simple GraphiQL page in your portal. The plugin code paths and app wiring above follow the plugin readme and show the exact imports and file locations you need.
Set up Backstage in minutes with Roadie
Focus on using Backstage, rather than building and maintaining it.