GitLab logo

Backstage GitLab Plugin

Created by ImmobiliareLabs

GitLab is a complete platform for the software lifecycle. It covers source code, merge requests, issues, pipelines, releases, and security in one place. Many teams use it to plan, build, test, and ship their services.

The GitLab plugin brings that context into Backstage. It adds a GitLab tab and optional cards on your entity pages, so you can see pipelines, merge requests, issues, recent releases, project languages, code owners, coverage, and the project readme. It works with personal tokens or project tokens. It supports multiple GitLab instances, including self managed and SaaS GitLab. It handles old and new GitLab APIs. Pagination keeps large tables fast.

This helps when you want build health, PR flow, and release notes next to your service docs. It gives platform teams a consistent view across repos. It is useful for organizations with many projects or a monorepo setup. The plugin can auto detect the related project from your catalog file. You can point it to a different project when needed. You can hide specific cards for services that do not use GitLab issues. There is optional OAuth support if you prefer user based access. If you already run Backstage, this plugin makes GitLab data part of the daily workflow without context switching.

Installation Instructions

These instructions apply to self-hosted Backstage only. To use this plugin on Roadie, visit the docs.

  1. Install the packages
Copy
# from your Backstage root
yarn --cwd packages/app add @immobiliarelabs/backstage-plugin-gitlab
yarn --cwd packages/backend add @immobiliarelabs/backstage-plugin-gitlab-backend
  1. Add your GitLab integration in app config
Copy
# app-config.yaml
integrations:
  gitlab:
    - host: gitlab.com
      token: ${GITLAB_TOKEN}
    # you can add more instances by adding more items
    # - host: gitlab.mycompany.internal
    #   token: ${GITLAB_INTERNAL_TOKEN}
  1. If you use the new Backstage backend system add the backend plugin this way
Copy
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
import {
  gitlabPlugin,
  catalogPluginGitlabFillerProcessorModule,
} from '@immobiliarelabs/backstage-plugin-gitlab-backend';

async function start() {
  const backend = createBackend();

  backend.add(gitlabPlugin);
  backend.add(catalogPluginGitlabFillerProcessorModule);

  await backend.start();
}

start().catch(err => {
  process.stderr.write(`${err}\n`);
  process.exit(1);
});

The filler processor auto fills project id and project slug annotations from your GitLab location

  1. If you use the classic backend system wire the backend plugin with an express router
Copy
// packages/backend/src/plugins/catalog.ts
import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
import { PluginEnvironment } from '../types';
import { Router } from 'express-serve-static-core';
import { GitlabFillerProcessor } from '@immobiliarelabs/backstage-plugin-gitlab-backend';

export default async function createPlugin(env: PluginEnvironment): Promise<Router> {
  const builder = await CatalogBuilder.create(env);

  // add the filler that enriches entities with GitLab annotations
  builder.addProcessor(new GitlabFillerProcessor(env.config));

  const { processingEngine, router } = await builder.build();
  await processingEngine.start();
  return router;
}
Copy
// packages/backend/src/plugins/gitlab.ts
import { PluginEnvironment } from '../types';
import { Router } from 'express-serve-static-core';
import { createRouter } from '@immobiliarelabs/backstage-plugin-gitlab-backend';

export default async function createPlugin(env: PluginEnvironment): Promise<Router> {
  return createRouter({
    logger: env.logger,
    config: env.config,
  });
}
Copy
// packages/backend/src/index.ts
import gitlab from './plugins/gitlab';
import { createEnv, useHotMemoize } from './lib'; // or your local helpers

async function main() {
  // other setup

  const gitlabEnv = useHotMemoize(module, () => createEnv('gitlab'));
  apiRouter.use('/gitlab', await gitlab(gitlabEnv));

  // other setup
}
  1. Add the frontend plugin to the classic Backstage frontend
Copy
// packages/app/src/components/catalog/EntityPage.tsx
import React from 'react';
import { EntityLayout, EntitySwitch } from '@backstage/plugin-catalog';
import { Grid } from '@backstage/core-components';

import {
  isGitlabAvailable,
  EntityGitlabContent,
  EntityGitlabLanguageCard,
  EntityGitlabMergeRequestsTable,
  EntityGitlabMergeRequestStatsCard,
  EntityGitlabPeopleCard,
  EntityGitlabPipelinesTable,
  EntityGitlabReadmeCard,
  EntityGitlabReleasesCard,
} from '@immobiliarelabs/backstage-plugin-gitlab';

// add a GitLab tab on the service page
export const serviceEntityPage = (
  <EntityLayout>
    <EntityLayout.Route if={isGitlabAvailable} path="/gitlab" title="GitLab">
      <EntityGitlabContent />
    </EntityLayout.Route>
  </EntityLayout>
);

// add cards on the overview page
export const overviewContent = (
  <Grid container spacing={3} alignItems="stretch">
    <EntitySwitch>
      <EntitySwitch.Case if={isGitlabAvailable}>
        <Grid item md={12}>
          <EntityGitlabReadmeCard />
        </Grid>
        <Grid item sm={12} md={3} lg={3}>
          <EntityGitlabPeopleCard />
        </Grid>
        <Grid item sm={12} md={3} lg={3}>
          <EntityGitlabLanguageCard />
        </Grid>
        <Grid item sm={12} md={3} lg={3}>
          <EntityGitlabMergeRequestStatsCard />
        </Grid>
        <Grid item sm={12} md={3} lg={3}>
          <EntityGitlabReleasesCard />
        </Grid>
        <Grid item md={12}>
          <EntityGitlabPipelinesTable />
        </Grid>
        <Grid item md={12}>
          <EntityGitlabMergeRequestsTable />
        </Grid>
      </EntitySwitch.Case>
    </EntitySwitch>
  </Grid>
);

EntityGitlabContent does not load the README by default. Use EntityGitlabReadmeCard if you want the README on the overview

  1. If you use the new frontend system you can enable the alpha export
Copy
# from your Backstage root if not already installed
yarn --cwd packages/app add @immobiliarelabs/backstage-plugin-gitlab
Copy
// packages/app/src/App.tsx
import { createApp } from '@backstage/frontend-app-api';
import catalogPlugin from '@backstage/plugin-catalog/alpha';
import userSettingsPlugin from '@backstage/plugin-user-settings/alpha';
import gitlabPlugin from '@immobiliarelabs/backstage-plugin-gitlab/alpha';

export const app = createApp({
  features: [
    catalogPlugin,
    userSettingsPlugin,
    gitlabPlugin,
  ],
});

Then list the GitLab cards you want on the overview page using app extensions

Copy
# app-config.yaml
app:
  extensions:
    - entity-card:gitlab/people
    - entity-card:gitlab/languages
    - entity-card:gitlab/merge-requests-stats
    - entity-card:gitlab/releases
    - entity-card:gitlab/coverage
    - entity-card:gitlab/readme
  1. Add annotations in your entity files if you need to point to a different project or instance
Copy
# catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: my-service
  annotations:
    gitlab.com/project-id: '1234'            # wrap numeric ids in quotes
    # or use a slug
    gitlab.com/project-slug: 'group_name/project_name'
    # or pin a non default GitLab instance
    gitlab.com/instance: gitlab.internal.abcd
spec:
  type: service
  lifecycle: production
  owner: team-a

That is all you need to install the plugin on both frontend and backend. Add your token or enable OAuth or OIDC. Register backend with the new system or with the classic express router. Add the tab or the cards so users can see GitLab data in the entity pages

Things to Know

Authentication

The plugin can use a static token. It can also use OAuth or OIDC. The default is a static token from app config. OAuth or OIDC uses the user session from Backstage. That requires the GitLab auth backend provider. OAuth or OIDC works with one GitLab instance. Keep useOAuth false if you need more than one instance.

If you use a static token set it under integrations in app config. You can use a personal token or a project token. Give it read access for pipelines merge requests issues and releases. On newer GitLab versions read_api usually works. On older versions you may need api. Use the smallest scope that still loads the data you need.

Copy
# app-config.yaml
integrations:
  gitlab:
    - host: gitlab.com
      token: ${GITLAB_TOKEN}

  # add more instances if needed
  # - host: gitlab.mycompany.internal
  #   token: ${GITLAB_INTERNAL_TOKEN}

If you switch to OAuth or OIDC set this. Then install and configure the GitLab auth provider on the backend.

Copy
# app-config.yaml
gitlab:
  useOAuth: true

Multiple GitLab instances and self managed

Add one entry per instance under integrations gitlab. The host must match the instance domain. For entities that live on a non default instance add an annotation to pin the instance.

Copy
# catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: my-service
  annotations:
    gitlab.com/instance: gitlab.mycompany.internal
spec:
  type: service
  owner: team-a

How the plugin finds the project

The backend filler processor reads the entity location. If that location points to a GitLab repo it fills project id and project slug automatically. You can override with annotations. Quote numeric ids.

Copy
# catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: my-service
  annotations:
    gitlab.com/project-id: '1234'
    # or
    gitlab.com/project-slug: 'group_name/project_name'
spec:
  type: service
  owner: team-a

You can turn off GitLab views for an entity by setting empty strings.

Copy
# catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: docs-site
  annotations:
    gitlab.com/instance: ''
    gitlab.com/project-slug: ''
spec:
  type: website
  owner: team-b

Backend settings

Tweak defaults in app config. Use proxySecure false if your GitLab uses a self signed cert. Enable cache to reduce API traffic. Set allowedKinds to control which kinds render GitLab data.

Copy
# app-config.yaml
gitlab:
  defaultCodeOwnersPath: .gitlab/CODEOWNERS
  defaultReadmePath: .gitlab/README.md
  allowedKinds: ['Component', 'Resource']
  proxySecure: true
  useOAuth: false
  cache:
    enabled: true
    ttl: 300

If your CODEOWNERS file lives in a different path you can set an annotation on the entity. The card will use the annotation. If missing it falls back to the default path in app config.

Copy
# catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: my-service
  annotations:
    gitlab.com/codeowners-path: 'somewhere/CODEOWNERS'
spec:
  type: service
  owner: team-a

Feature guide

The GitLab tab shows a combined view. It includes pipelines merge requests issues releases languages people and stats. The tables use pagination. Use the cards on the overview for a quick at a glance view. The releases card shows the latest releases. The merge requests stats card shows counts by state. The pipelines table shows the latest pipelines with status and duration. The people card reads CODEOWNERS and enriches users from GitLab.

Custom client for old or new GitLab versions

If your GitLab API returns a different shape extend the client on the app side. Override only what you need. Keep the setupAPI factory so the client is wired into discovery and uses your config paths.

Copy
// packages/app/src/api.ts
import { AnyApiFactory, createApiFactory } from '@backstage/core-plugin-api';
import { discoveryApiRef, configApiRef } from '@backstage/core-plugin-api';
import { GitlabCIApiRef } from '@immobiliarelabs/backstage-plugin-gitlab';
import { CustomGitlabCIClient } from './myCustomClient';

export const apis: AnyApiFactory[] = [
  createApiFactory({
    api: GitlabCIApiRef,
    deps: { configApi: configApiRef, discoveryApi: discoveryApiRef },
    factory: ({ configApi, discoveryApi }) =>
      CustomGitlabCIClient.setupAPI({
        discoveryApi,
        codeOwnersPath: configApi.getOptionalString('gitlab.defaultCodeOwnersPath'),
        readmePath: configApi.getOptionalString('gitlab.defaultReadmePath'),
      }),
  }),
];
Copy
// packages/app/src/myCustomClient.ts
import { GitlabCIClient } from '@immobiliarelabs/backstage-plugin-gitlab';

export class CustomGitlabCIClient extends GitlabCIClient {
  async getPipelineSummary(projectID: string | undefined) {
    return this.callApi(/* your custom call */);
  }
}

Common problems to avoid

Use quotes around numeric project ids in annotations. Without quotes YAML can cast them to numbers and that can break lookups.

Match the host value in integrations to the real instance hostname. If the host is wrong the backend cannot route requests.

Keep gitlab.useOAuth false if you configure more than one integration entry. OAuth or OIDC supports one instance only.

If you see cert errors against a private GitLab set proxySecure false in app config.

Set up Backstage in minutes with Roadie