Learn Backstage - Backend Plugin & Backend Framework
Episode 18 of 23

Learn Backstage - Backend Plugin & Backend Framework

Building backend plugins in Backstage with the New Backend System: createBackendPlugin and createBackendModule, service APIs, plugin options, and testing with backend-test-utils. Closing with how to integrate plugins with the proxy, auth, catalog, and scaffolder backends.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 17 you built plugins on the frontend side with the New Frontend System: creating extensions, wiring pages and components into the entity page. Episode 18 completes the story from the server side. Backstage isn't just a collection of UI — behind almost every frontend plugin there's usually a backend plugin providing data, HTTP endpoints, and a bridge into internal systems. Here you'll learn the New Backend System: defining plugins and modules, using service APIs, setting plugin options, testing everything, then wiring it into existing backends like proxy, auth, catalog, and scaffolder.

Understanding the New Backend System

The New Backend System is the architectural foundation of Backstage backend plugins. Previously every plugin was wired directly inside the backend code, registering itself by calling APIs from app-backend. Now plugins and modules are declarative: you write plugin definitions, then assemble them like Lego at a single entry point called backend.

There are three main building units to understand:

UnitPurposeWhen to use
createBackendPluginCreates a standalone backend pluginEvery time there's a new backend plugin
createBackendModuleInjects features into other pluginsCross-plugin integrations, extra custom logic
Service APIsShared services used across pluginsAccessing config, database, logger, auth, catalog

All three work together: plugins provide features, modules extend them, and services channel the same functions to all plugins.

Creating a Plugin with createBackendPlugin

createBackendPlugin is the function for defining a backend plugin. A minimal backend plugin has a unique pluginId and a register function that connects the plugin to the backend:

Plugin backend sederhana
import { createBackendPlugin, coreServices } from '@backstage/backend-plugin-api';
 
export const healthPlugin = createBackendPlugin({
  pluginId: 'health',
  register(env) {
    env.registerInit({
      deps: { logger: coreServices.logger, httpRouter: coreServices.httpRouter },
      async init({ logger, httpRouter }) {
        httpRouter.use('/healthz', (_req, res) => {
          logger.info('health check dipanggil');
          res.json({ status: 'ok' });
        });
      },
    });
  },
});

This plugin provides a new HTTP endpoint via httpRouter. Once installed at the backend entry point with backend.add, its router and logger are available immediately without extra setup.

createBackendModule for Extensibility

A module is a unit that extends another plugin. The common pattern is registering into an extension point owned by the target plugin:

Modul yang memperluas plugin scaffolder
import { createBackendModule } from '@backstage/backend-plugin-api';
 
export const customScaffolderModule = createBackendModule({
  pluginId: 'scaffolder',
  moduleId: 'custom-actions',
  register(env) {
    env.registerInit({
      deps: {
        scaffolder: scaffolderActionsExtensionPoint,
        logger: coreServices.logger,
      },
      async init({ scaffolder, logger }) {
        scaffolder.addActions(new MyCustomAction({ logger }));
      },
    });
  },
});

A module binds to the pluginId of the plugin it extends. The example above adds a new action to the scaffolder without touching the scaffolder plugin's own code. This is the form of extensibility that makes Backstage easy to customize without forking the repository.

Service APIs and Service Registration

Service APIs are the service contracts Backstage provides: config, logger, database, cache, auth, httpAuth, httpRouter, catalog, permissions, and many more. A plugin requests services through a deps declaration rather than importing an implementation directly — Backstage decides how that service is realized, including how it behaves under testing.

For your own services, register them with createServiceFactory:

Mendaftarkan custom service
import { createServiceRef, createServiceFactory } from '@backstage/backend-plugin-api';
 
export const reportingServiceRef = createServiceRef<ReportingService>({ id: 'acme.reporting' });
 
export const reportingServiceFactory = createServiceFactory({
  service: reportingServiceRef,
  deps: { logger: coreServices.logger, config: coreServices.rootConfig },
  async factory({ logger, config }) {
    return new ReportingService({ logger, endpoint: config.getString('reporting.url') });
  },
});

A custom service registered with createServiceFactory can be requested by other plugins via deps — a clean way to share database connections, HTTP clients, or internal helpers across plugins.

Plugin Options

Backend plugins often need specific settings. The New Backend System supports plugin options defined in the plugin factory function:

Plugin dengan opsi terdefinisi
import { createBackendPlugin } from '@backstage/backend-plugin-api';
import { z } from 'zod';
 
export const reportingPlugin = createBackendPlugin({
  pluginId: 'reporting',
  options: {
    endpoint: z.string().url(),
    retries: z.number().default(3),
  },
  async register(env, options) {
    env.registerInit({
      deps: { httpRouter: coreServices.httpRouter },
      async init({ httpRouter }) {
        httpRouter.use('/reports', new ReportsRouter(options));
      },
    });
  },
});

When installed, the option values are passed along with the backend.add call:

Mengaktifkan plugin dengan opsi
backend.add(reportingPlugin, { endpoint: 'https://reports.internal', retries: 5 });

With options, one plugin can be reused with different configurations without duplicating code.

Testing a Backend Plugin

@backstage/backend-test-utils provides mock services and a harness for running a test backend without a real server:

Test plugin dengan backend-test-utils
import { startTestBackendFromFeatures, mockServices } from '@backstage/backend-test-utils';
 
const backend = await startTestBackendFromFeatures({
  features: [
    reportingPlugin,
    mockServices.config.factory({
      data: { reporting: { url: 'https://test.internal' } },
    }),
  ],
});

This kind of harness also provides setupRequestHandlerContext for testing specific HTTP routes. With the test harness, a plugin is tested in realistic isolation: the services it needs are mocked, and only the plugin under test is active.

Extensibility with Existing Backends

Proxy, Auth, Catalog, and Scaffolder

The main advantage of the plugin architecture is easy integration. Common needs:

  • Proxy — routing requests to external services through proxy configuration, without writing your own authentication.
  • Auth — a plugin that needs user identity just asks for httpAuth or auth; Backstage manages the tokens, sessions, and sign-in providers.
  • Catalog — reading entities, relations, and metadata directly from the catalog service, or adding your own processors for custom data.
  • Scaffolder — adding custom actions and templates that leverage the scaffolder pipeline.

All of these integrations are expressed through service APIs and modules, so you don't need to hack into another plugin's code.

Tip

The most common pattern for backend integration: module + extension point. If the target plugin exposes an extension point, register through a module instead of changing global configuration. This keeps plugins declarative and makes upgrading Backstage versions easier later.

Conclusion

In this episode 18, you understood the New Backend System: building plugins with createBackendPlugin, extending other plugins with createBackendModule, using service APIs like config, logger, database, and catalog, setting plugin options, testing plugins with @backstage/backend-test-utils, and integrating with the proxy, auth, catalog, and scaffolder backends through custom service registration.

The key takeaways:

  • Backend plugins are declarative — define with createBackendPlugin, then install via backend.add.
  • Modules are the path to extensibility — extend other plugins through extension points without changing their code.
  • Service APIs separate use from implementation — a plugin just names its deps; Backstage provides the services.
  • Test plugins with backend-test-utils — harnesses and mock services make testing fast and deterministic.

In episode 19, you move up from one instance to many: scaling & performance. We'll set up Backstage to serve thousands of engineers — from horizontal scaling, load balancing, a shared PostgreSQL database, a task scheduler for pipelines, to bundling size optimization, memory management, and cache strategy.

Learn Backstage - Backend Plugin & Backend Framework | Learn Backstage