ワンクリックで
backstage-backend-plugin
Build and maintain Backstage backend plugins following best practices.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Build and maintain Backstage backend plugins following best practices.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | backstage-backend-plugin |
| description | Build and maintain Backstage backend plugins following best practices. |
| version | 0.1.0 |
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.
Use this skill when creating server-side functionality for Backstage: REST/HTTP APIs, background jobs, data processing, or integrations.
Before building a backend plugin, clearly understand:
Load reference files as needed based on the plugin requirements:
For Core Services:
For MCP Actions:
actionsRegistryServiceRef)For Testing:
Follow the workflow below for implementation, referring to the reference files as needed.
Important Decisions:
⚠️ MCP Actions Key Point:
credentials in their handler; use these with permissions.authorize() for sensitive operations. Unlike HTTP routes, MCP actions don't have route-level auth policies, so explicit permission checks are critical.After implementing the plugin:
startTestBackendTestDatabasesyarn backstage-cli package test --coverage
Before publishing:
yarn new → backend‑plugin; the package lives in plugins/<id>-backend/.createBackendPlugin, declare dependencies via deps, and initialize in register(env).registerInit.coreServices.httpRouter. Backstage prefixes plugin routers with /api/<pluginId>.httpRouter.addAuthPolicy({ path, allow }) to allow unauthenticated endpoints like /health.coreServices.actionsRegistryServiceRef from @backstage/backend-plugin-api/alpha.permissions.authorize() with the credentials passed to the action handler.permissionsRegistry.addPermissions() at plugin init.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); }
});
Add a terminal error handler to your router and prefer structured logs with context:
import { errorHandler } from '@backstage/backend-common';
router.use(errorHandler());
Keep backends secure-by-default
Open only explicit paths with addAuthPolicy
For protected routes, extract credentials with httpAuth
Derive user/entity identity via userInfo when required
// Inside a route handler
const creds = await httpAuth.credentials(req, { allow: ['user', 'service'] });
const { userEntityRef } = await userInfo.getUserInfo(creds);
logger.info('request', { userEntityRef });
const knex = await database.getClient(); to get a database client.js) and export them in package.json (see the Core Services Reference for details)logger.child({ plugin: 'example' }) for traceabilitypermissions.authorize() with the credentials from the action handlerauth.getPluginRequestToken({ onBehalfOf: credentials }) for downstream API calls to preserve user identityactions.ts file for MCP action registrationget_example_items, create_example_itemInputError, NotAllowedError# 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.
src/plugin.ts — plugin + DI + routerimport { 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)
src/service/router.ts — minimal Express routerimport 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;
}
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)
coreServices.database to get a Knex client; create your own migrations and run them via your chosen process. (Backstage)coreServices.httpAuth + coreServices.userInfo to obtain the calling user and their entity refs when an endpoint needs identity. (Backstage)packages/backend/src/index.ts via backend.add(import('@internal/plugin-<id>-backend')).yarn start at the root). Then check:
{ "status": "ok" }/health with addAuthPolicy.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).
| 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 |
Load these resources as needed during development:
actionsRegistryServiceRef from @backstage/backend-plugin-api/alpha)credentials and auth.getPluginRequestToken()permissions.authorize() for sensitive operationsstartTestBackendTestDatabases