Linguist is the library GitHub uses to detect languages in a codebase. It scans files and reports a breakdown by language. That insight is useful when you want a quick read on what makes up a repo.The Linguist plugin brings that view into Backstage. It adds a card to entity pages that shows the language mix for the code linked to an entity in your catalog. The plugin reads source through Backstage UrlReader, so it can pull from GitHub, GitLab, Bitbucket, Azure Repos, Google Cloud Storage, and AWS S3. You see the results right where your teams work.
You can use it to spot tech drift, plan migrations, or track language adoption across services. It helps new folks understand a service at a glance. It also supports a tags processor that can write language tags onto entities. That makes it simple to filter the catalog by language and to build simple governance checks. For example, you can find all Python services in a group, or ignore tiny shell scripts by setting a minimum byte count.
The plugin is maintained in the community plugins repo. It has a front end card and a back end that generates and refreshes the data on a schedule, with options to tune batch size and refresh age.
Installation Instructions
These instructions apply to self-hosted Backstage only.
Add the entity annotation
Add this annotation to each entity you want to analyze. Use a source URL that your Backstage UrlReader can read.
# Example catalog-info.yaml entity definition file
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: my-service
annotations:
backstage.io/linguist: https://github.com/backstage/backstage
spec:
type: service
Linguist uses the UrlReader to fetch files then scans them to find language usage. It works with common providers like GitHub, GitLab, Bitbucket, Azure, Google GCS, and AWS S3.
Install the frontend plugin for the classic frontend
-
Add the package to your app.
# From your Backstage root directory yarn --cwd packages/app add @backstage-community/plugin-linguist
-
Show the card on the entity page.
Edit packages/app/src/components/catalog/EntityPage.tsx. Import the card and the helper. Add the card to the overview section.
// packages/app/src/components/catalog/EntityPage.tsx import React from 'react'; import Grid from '@material-ui/core/Grid'; import { EntitySwitch } from '@backstage/plugin-catalog'; import { isLinguistAvailable, EntityLinguistCard } from '@backstage-community/plugin-linguist'; // inside your overview content const overviewContent = ( <Grid container spacing={3} alignItems="stretch"> {/* other cards */} <EntitySwitch> <EntitySwitch.Case if={isLinguistAvailable}> <Grid item md={6}> <EntityLinguistCard /> </Grid> </EntitySwitch.Case> </EntitySwitch> {/* other cards */} </Grid> );
Notes
- You can remove the if prop if you want the card to always render.
Install the frontend plugin for the new frontend system
-
Add the package to your app.
# From your Backstage root directory yarn --cwd packages/app add @backstage-community/plugin-linguist
-
Enable the plugin feature in packages/app or packages/app-next.
// packages/app/src/App.tsx or packages/app-next/src/App.tsx import { createApp } from '@backstage/frontend-app-api'; import catalogPlugin from '@backstage/plugin-catalog/alpha'; import catalogImportPlugin from '@backstage/plugin-catalog-import/alpha'; import userSettingsPlugin from '@backstage/plugin-user-settings/alpha'; import linguistPlugin from '@backstage-community/plugin-linguist'; export const app = createApp({ features: [ catalogPlugin, catalogImportPlugin, userSettingsPlugin, linguistPlugin, ], });
Or turn on feature discovery in app-config.yaml.
app:
packages: all
Install the backend plugin for the classic backend
-
Add the backend package to your backend.
# From your Backstage root directory yarn --cwd packages/backend add @backstage-community/plugin-linguist-backend
-
Create the backend plugin wiring.
Create a new file packages/backend/src/plugins/linguist.ts.
// packages/backend/src/plugins/linguist.ts import { Router } from 'express'; import { PluginEnvironment } from '../types'; import { createRouter } from '@backstage-community/plugin-linguist-backend'; export default async function createPlugin( env: PluginEnvironment, ): Promise<Router> { return await createRouter({ logger: env.logger, config: env.config, reader: env.reader, discovery: env.discovery, cache: env.cache, }); }
-
Mount the router in the backend.
Edit packages/backend/src/index.ts. Create the plugin environment then mount the router under the api path.
// packages/backend/src/index.ts import { createServiceBuilder } from '@backstage/backend-common'; import express from 'express'; import http from 'http'; import { useHotCleanup } from '@backstage/backend-common'; import { createEnv } from './lib/createEnv'; import linguist from './plugins/linguist'; async function main() { const apiRouter = express.Router(); const linguistEnv = useHotCleanup(module, () => createEnv('linguist')); apiRouter.use('/linguist', await linguist(linguistEnv)); const service = createServiceBuilder(module) .setPort(Number(process.env.PORT) || 7007) .addRouter('/api', apiRouter); await service.start().catch(err => { process.exit(1); }); } main().catch(err => { process.exit(1); });
This exposes the plugin at the api linguist path. The frontend card will call it through the Backstage proxy.
Install the backend plugin for the new backend system
-
Add the backend package to your backend.
# From your Backstage root directory yarn --cwd packages/backend add @backstage-community/plugin-linguist-backend
-
Register the module in packages/backend src index.ts.
// packages/backend/src/index.ts import { createBackend } from '@backstage/backend-defaults'; import { catalogModule } from '@backstage/plugin-catalog-backend/alpha'; import { scaffolderModule } from '@backstage/plugin-scaffolder-backend/alpha'; // The linguist backend module export name may differ depending on version // If this import fails, open the package in node_modules and use the exported module function import { linguistModule } from '@backstage-community/plugin-linguist-backend'; const backend = createBackend(); backend.add(catalogModule()); backend.add(scaffolderModule()); backend.add(linguistModule()); backend.start();
If the package exports a default module function, use this instead.
import linguistModule from '@backstage-community/plugin-linguist-backend';
backend.add(linguistModule());
Summary of what you should see in your app
- Entities that have the backstage.io linguist annotation will show the Linguist card on the overview page
- The card will be empty until the backend has scanned the source for that entity
- After the scan the card will show the language breakdown
Changelog
This changelog is produced from commits made to the Linguist plugin since a year 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.
Breaking changes
- None
Features
- Add filter to show or hide the Language card based on
isLinguistAvailable
#3490 5 months ago
Bug fixes
- Replace deprecated
id
withpluginId
increateFrontendPlugin
#4268 3 months ago
Documentation
- Update docs with guidance for plugin dev environments #3134 6 months ago
Chore
Set up Backstage in minutes with Roadie
Focus on using Backstage, rather than building and maintaining it.