Backstage Developer Portal: Building a Service Catalog with Custom Plugins

Backstage Developer Portal Setup Guide

Backstage developer portal has become the de facto standard for internal developer platforms. Originally created at Spotify, this open-source platform now powers developer experience at thousands of organizations. It provides a unified service catalog, documentation hub, and extensible plugin ecosystem that eliminates the fragmentation of internal tooling.

This guide walks you through building a production-ready Backstage instance with a populated service catalog, custom plugins, and software templates. Whether you are starting a platform engineering initiative or consolidating existing tools, this guide covers the practical steps to deliver value quickly.

Architecture Overview

Backstage consists of three core components: the frontend app (React), the backend (Node.js), and a PostgreSQL database for persistent storage. The plugin architecture allows teams to extend functionality without modifying the core platform.

Backstage developer portal architecture diagram
The Backstage architecture connects services, documentation, and tooling in a unified portal
# Create a new Backstage app
npx @backstage/create-app@latest my-portal
cd my-portal

# Project structure
# ├── app-config.yaml          # Main configuration
# ├── app-config.production.yaml
# ├── packages/
# │   ├── app/                 # Frontend (React)
# │   └── backend/             # Backend (Node.js)
# └── plugins/                 # Custom plugins

Configuring the Service Catalog

The service catalog is the heart of any Backstage developer portal. It provides a centralized registry of all software components, APIs, resources, and their ownership. Moreover, it automatically discovers and ingests catalog entities from your Git repositories.

# app-config.yaml
catalog:
  import:
    entityFilename: catalog-info.yaml
    pullRequestBranchName: backstage-integration
  rules:
    - allow: [Component, System, API, Resource, Location, Group, User]
  locations:
    # GitHub org discovery
    - type: github-discovery
      target: https://github.com/my-org/*/blob/main/catalog-info.yaml
    # Static locations
    - type: url
      target: https://github.com/my-org/backstage-catalog/blob/main/all-systems.yaml
  processors:
    githubOrg:
      providers:
        - target: https://github.com
          apiBaseUrl: https://api.github.com
          orgs: ['my-org']

Each service registers itself with a catalog-info.yaml file in its repository root:

# catalog-info.yaml in each service repo
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payment-service
  description: Handles payment processing and billing
  annotations:
    github.com/project-slug: my-org/payment-service
    backstage.io/techdocs-ref: dir:.
    pagerduty.com/service-id: PABCDEF
    grafana/dashboard-selector: "payment-*"
  tags:
    - java
    - spring-boot
    - payments
  links:
    - url: https://grafana.internal/d/payment
      title: Grafana Dashboard
      icon: dashboard
spec:
  type: service
  lifecycle: production
  owner: team-payments
  system: billing-platform
  providesApis:
    - payment-api
  consumesApis:
    - user-api
    - notification-api
  dependsOn:
    - resource:default/payments-db
    - resource:default/payments-redis

Building Custom Plugins

Backstage plugins extend the portal with new features. Furthermore, the plugin SDK provides a structured approach to building both frontend and backend extensions:

# Generate a new plugin
cd my-portal
npx @backstage/cli new --select plugin
# Enter plugin ID: cost-insights
// plugins/cost-insights/src/components/CostDashboard.tsx
import React from 'react';
import { useApi, configApiRef } from '@backstage/core-plugin-api';
import { Table, TableColumn } from '@backstage/core-components';

interface ServiceCost {
  name: string;
  monthlyCost: number;
  trend: number;
  owner: string;
}

export const CostDashboard = () => {
  const [costs, setCosts] = React.useState<ServiceCost[]>([]);
  const config = useApi(configApiRef);

  React.useEffect(() => {
    fetch('/api/cost-insights/services')
      .then(res => res.json())
      .then(data => setCosts(data));
  }, []);

  const columns: TableColumn<ServiceCost>[] = [
    { title: 'Service', field: 'name' },
    { title: 'Monthly Cost', field: 'monthlyCost',
      render: row => `$${row.monthlyCost.toFixed(2)}` },
    { title: 'Trend', field: 'trend',
      render: row => row.trend > 0
        ? `+${row.trend}%`
        : `${row.trend}%` },
    { title: 'Owner', field: 'owner' },
  ];

  return (
    <Table
      title="Service Cost Overview"
      columns={columns}
      data={costs}
      options={{ pageSize: 20, search: true }}
    />
  );
};
Backstage plugin dashboard for cost insights
Custom plugins provide team-specific views and integrations within the portal

Software Templates for Golden Paths

Software templates codify best practices into self-service project scaffolding. As a result, developers can create new services, libraries, or infrastructure components that automatically follow organizational standards:

# templates/spring-service/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: spring-boot-service
  title: Spring Boot Microservice
  description: Create a production-ready Spring Boot microservice
spec:
  owner: team-platform
  type: service
  parameters:
    - title: Service Details
      required: [name, owner, description]
      properties:
        name:
          title: Service Name
          type: string
          pattern: '^[a-z][a-z0-9-]*$'
        owner:
          title: Owner Team
          type: string
          ui:field: OwnerPicker
        description:
          title: Description
          type: string
        javaVersion:
          title: Java Version
          type: string
          enum: ['17', '21']
          default: '21'
    - title: Infrastructure
      properties:
        database:
          title: Database
          type: string
          enum: ['none', 'postgresql', 'mysql']
          default: 'postgresql'
        messaging:
          title: Messaging
          type: string
          enum: ['none', 'kafka', 'rabbitmq']
  steps:
    - id: fetch
      name: Fetch Template
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: '{{ parameters.name }}'
          owner: '{{ parameters.owner }}'
          java_version: '{{ parameters.javaVersion }}'
    - id: publish
      name: Create Repository
      action: publish:github
      input:
        repoUrl: 'github.com?repo={{ parameters.name }}&owner=my-org'
        description: '{{ parameters.description }}'
    - id: register
      name: Register in Catalog
      action: catalog:register
      input:
        repoContentsUrl: '{{ steps.publish.output.repoContentsUrl }}'
        catalogInfoPath: /catalog-info.yaml

TechDocs Integration

TechDocs transforms Markdown documentation into a searchable knowledge base. Consequently, every service has documentation accessible from the same portal:

# app-config.yaml
techdocs:
  builder: 'external'
  generator:
    runIn: 'local'
  publisher:
    type: 'awsS3'
    awsS3:
      bucketName: 'my-techdocs-bucket'
      region: 'us-east-1'
      credentials:
        roleArn: 'arn:aws:iam::123456789:role/techdocs-publisher'

When NOT to Use Backstage

Backstage requires significant investment in setup and maintenance. Small teams with fewer than 20 developers may find the overhead unjustified when simpler wiki-based solutions suffice. Additionally, if your organization lacks dedicated platform engineering resources, the portal may become stale and unused. Backstage is also not a monitoring solution — it aggregates links to monitoring tools but does not replace Grafana or Datadog.

Platform engineering team building developer portal
Successful Backstage adoption requires dedicated platform engineering investment

Key Takeaways

  • A Backstage developer portal centralizes service discovery, documentation, and tooling in a single platform
  • The service catalog with automatic GitHub discovery eliminates manual registry maintenance
  • Custom plugins extend the portal with team-specific features like cost insights and deployment tracking
  • Software templates enforce organizational standards while enabling developer self-service
  • Plan for dedicated platform engineering resources — Backstage is not a set-and-forget solution

Related Reading

External Resources

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top