Backstage Backend Plugin
About This Skill
This skill provides specialized knowledge and workflows for building Backstage backend plugins. It guides the development of server-side functionality including REST/HTTP APIs, background jobs, data processing, and integrations.
What This Skill Provides
- •Specialized workflows - Step-by-step procedures for creating and configuring backend plugins
- •Best practices - Production-ready patterns for auth, validation, error handling, and database usage
- •Golden path templates - Copy/paste code snippets for common backend patterns
- •Domain expertise - Backstage specific knowledge about core services, and architecture best practices
When to Use This Skill
Use this skill when creating server-side functionality for Backstage: REST/HTTP APIs, background jobs, data processing, or integrations.
Development Workflow
Phase 1: Planning and Research
1.1 Understand the Requirements
Before building a backend plugin, clearly understand:
- •What endpoints or APIs are needed (REST, GraphQL, webhooks)
- •What data storage requirements exist (database, cache)
- •Whether background jobs or scheduled tasks are needed
- •Authentication and authorization requirements
- •Integration points with other services or plugins
- •What MCP actions make sense to expose if any
1.2 Load Reference Documentation
Load reference files as needed based on the plugin requirements:
For Core Services:
- •⚙️ Core Services Reference - Comprehensive guide to all core backend services including:
- •httpRouter, logger, database, httpAuth, userInfo
- •cache, scheduler, urlReader, discovery, permissions
- •Usage patterns and best practices for each service
For MCP Actions:
- •🤖 MCP Actions Reference - Complete guide to exposing plugin functionality to AI interfaces:
- •Actions Registry Service (
actionsRegistryServiceRef) - •Action registration patterns and schema definitions
- •Permission integration with MCP actions
- •Authentication and credential handling
- •Actions Registry Service (
For Testing:
- •✅ Testing Reference - Comprehensive testing guide for backend plugins
Phase 2: Implementation
Follow the workflow below for implementation, referring to the reference files as needed.
Important Decisions:
- •Determine which core services are needed (load ⚙️ Core Services Reference)
- •Plan database schema and migrations if using database with knex
- •Design authentication policies (which endpoints need auth?)
- •Design needed permissions for integrating with the permission framework
- •Plan error handling and validation strategies
- •Plan what MCP actions need to be exposed supporting reuse of functions from the rest of the plugin (load 🤖 MCP Actions Reference)
⚠️ MCP Actions Key Point:
- •Always add permission checks at the MCP action level - Actions receive
credentialsin their handler; use these withpermissions.authorize()for sensitive operations. Unlike HTTP routes, MCP actions don't have route-level auth policies, so explicit permission checks are critical.
Phase 3: Testing
After implementing the plugin:
- •Load the ✅ Testing Reference
- •Write comprehensive tests for:
- •Router endpoints using
startTestBackend - •Database operations with
TestDatabases - •External service calls with MSW
- •Authentication flows
- •Router endpoints using
- •Run tests and achieve good coverage:
bash
yarn backstage-cli package test --coverage
Phase 4: Review and Polish
Before publishing:
- •Run linting and structure checks
- •Ensure proper error handling throughout
- •Verify authentication policies are correct
- •Check database migrations are production-ready
- •Review the Common Pitfalls section below
Quick Notes
- •Scaffold with
yarn new→ backend‑plugin; the package lives inplugins/<id>-backend/. - •Define the plugin with
createBackendPlugin, declare dependencies viadeps, and initialize inregister(env).registerInit. - •Attach routes through
coreServices.httpRouter. Backstage prefixes plugin routers with/api/<pluginId>. - •Backends are secure by default; use
httpRouter.addAuthPolicy({ path, allow })to allow unauthenticated endpoints like/health. - •Core services (logger, database, httpRouter, httpAuth, userInfo, urlReader, scheduler, etc.) are available via
coreServices. - •MCP actions use
actionsRegistryServiceReffrom@backstage/backend-plugin-api/alpha. - •Always check permissions in MCP actions - use
permissions.authorize()with thecredentialspassed to the action handler. - •Register plugin permissions with
permissionsRegistry.addPermissions()at plugin init.
Best Practices
Request Validation
Validate inputs at the edge using a schema (e.g., zod) before hitting DBs or external services:
import { z } from 'zod';
const querySchema = z.object({ q: z.string().min(1) });
router.get('/search', async (req, res, next) => {
const parsed = querySchema.safeParse(req.query);
if (!parsed.success) return res.status(400).json({ error: 'invalid query' });
try {
// ... perform work with parsed.data.q
res.json({ items: [] });
} catch (e) { next(e); }
});
Error Handling
Add a terminal error handler to your router and prefer structured logs with context:
import { errorHandler } from '@backstage/backend-common';
router.use(errorHandler());
Auth and Identity
- •
Keep backends secure-by-default
- •
Open only explicit paths with
addAuthPolicy - •
For protected routes, extract credentials with
httpAuth - •
Derive user/entity identity via
userInfowhen requiredts// Inside a route handler const creds = await httpAuth.credentials(req, { allow: ['user', 'service'] }); const { userEntityRef } = await userInfo.getUserInfo(creds); logger.info('request', { userEntityRef });
Database Usage
- •Use
const knex = await database.getClient();to get a database client - •Keep queries in small repo/service modules
- •Write migrations in JavaScript (
.js) and export them in package.json (see the Core Services Reference for details)
Observability & Scalability
- •Avoid in-memory state
- •Make handlers idempotent
- •Log with
logger.child({ plugin: 'example' })for traceability
MCP Actions
- •Always check permissions at the action level - Use
permissions.authorize()with thecredentialsfrom the action handler - •Use
auth.getPluginRequestToken({ onBehalfOf: credentials })for downstream API calls to preserve user identity - •Create a separate
actions.tsfile for MCP action registration - •Reuse service classes from your plugin - don't duplicate business logic in actions
- •Write clear, descriptive action descriptions so AI understands when to use them
- •Use snake_case for action names with verb prefix:
get_example_items,create_example_item - •Handle errors with Backstage error types:
InputError,NotAllowedError
Golden Path (Copy/Paste Workflow)
1) Scaffold
# From the repository root # Non-interactive (for AI agents/automation) yarn new --select backend-plugin --option pluginId=example --option owner=""
This creates plugins/example-backend/ using the New Backend System with createBackendPlugin.
2) src/plugin.ts — plugin + DI + router
import { createBackendPlugin, coreServices } from '@backstage/backend-plugin-api';
import { createRouter } from './service/router';
export const examplePlugin = createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
httpRouter: coreServices.httpRouter,
logger: coreServices.logger,
database: coreServices.database, // optional
httpAuth: coreServices.httpAuth, // optional
userInfo: coreServices.userInfo, // optional
},
async init({ httpRouter, logger, database, httpAuth, userInfo }) {
const router = await createRouter({ logger, database, httpAuth, userInfo });
httpRouter.use(router);
// Secure-by-default: open /health only
httpRouter.addAuthPolicy({ path: '/health', allow: 'unauthenticated' });
},
});
},
});
export { examplePlugin as default } from './plugin';
Key points: DI via deps, register routes with the plugin’s httpRouter, and export the plugin as the default. (Backstage)
3) src/service/router.ts — minimal Express router
import express from 'express';
import type {
LoggerService,
DatabaseService,
HttpAuthService,
UserInfoService,
} from '@backstage/backend-plugin-api';
export interface RouterOptions {
logger: LoggerService;
database?: DatabaseService;
httpAuth?: HttpAuthService;
userInfo?: UserInfoService;
}
export async function createRouter(options: RouterOptions): Promise<express.Router> {
const { logger } = options;
const router = express.Router();
router.get('/health', (_req, res) => {
logger.info('health check');
res.json({ status: 'ok' });
});
return router;
}
4) Add to your backend
In packages/backend/src/index.ts:
const backend = createBackend();
backend.add(import('@internal/plugin-example-backend'));
backend.start();
Now GET http://localhost:7007/api/example/health returns { "status": "ok" }. (Backstage)
5) Database, auth, identity (when needed)
- •Database: depend on
coreServices.databaseto get a Knex client; create your own migrations and run them via your chosen process. (Backstage) - •Identity: use
coreServices.httpAuth+coreServices.userInfoto obtain the calling user and their entity refs when an endpoint needs identity. (Backstage) - •Core services catalog: consult the Backstage Core Backend Service APIs for the full list (cache, scheduler, urlReader, etc.). (Backstage)
Verify in a Backstage backend
- •Add the plugin to
packages/backend/src/index.tsviabackend.add(import('@internal/plugin-<id>-backend')). - •Start the repo (e.g.,
yarn startat the root). Then check:- •GET http://localhost:7007/api/example/health →
{ "status": "ok" } - •If 401 occurs, ensure you opened
/healthwithaddAuthPolicy.
- •GET http://localhost:7007/api/example/health →
Testing, linting & structure checks
Use Backstage's CLI for tests and lints:
yarn backstage-cli package test yarn backstage-cli package lint yarn backstage-cli repo lint
Keep routers small (/service/router.ts), inject dependencies (DB, auth, clients) from plugin.ts, and avoid in-memory state (horizontally scalable).
Common Pitfalls (and Fixes)
| Problem | Solution | Reference |
|---|---|---|
404s under /api | Remember Backstage prefixes plugin routers with /api/<pluginId> | Backstage |
| Auth unexpectedly required | Backends are secure by default; open endpoints explicitly via httpRouter.addAuthPolicy | Backstage |
| Tight coupling | Never call other backend code directly; communicate over the network or through well-defined services | Backstage |
Reference Files
📚 Documentation Library
Load these resources as needed during development:
Core Services
- •⚙️ Core Services Reference - Complete guide to all core backend services including:
- •HTTP Router Service for route registration
- •Logger Service for structured logging
- •Database Service for Knex-based data access
- •HTTP Auth Service and User Info Service for authentication
- •Cache Service for key-value storage
- •Scheduler Service for background tasks
- •Discovery Service for inter-plugin communication
- •URL Reader Service for reading external content
- •Permissions Service for authorization
- •Configuration and health check services
- •Service composition examples and best practices
MCP Actions
- •🤖 MCP Actions Reference - Complete guide to MCP action integration:
- •Actions Registry Service (
actionsRegistryServiceReffrom@backstage/backend-plugin-api/alpha) - •Action registration with schema definitions (input/output using zod)
- •Authentication handling with
credentialsandauth.getPluginRequestToken() - •Permission integration with
permissions.authorize()for sensitive operations - •Reusing service classes in actions
- •Error handling patterns with Backstage error types
- •Complete examples from existing plugins
- •Actions Registry Service (
Testing
- •✅ Testing Reference - Comprehensive testing guide including:
- •Testing backend plugins with
startTestBackend - •Mock services for all core services
- •Testing routers with supertest
- •Testing authentication and permissions
- •External service mocking with MSW
- •Database testing with
TestDatabases - •Service factory testing
- •Integration testing patterns
- •Best practices and common patterns
- •Testing backend plugins with