PlatformEngr® Backstage plugins course

Cheat notes · Module 01 · The backend plugin

A backend plugin with an API

A five-minute review for when you have done this module and want it back in your head. Open the full module for the steps and the lab, or the full text to print.

The idea

A backend plugin is an id plus registerInit: declare the services you need, then mount a router. Every route is authenticated by default, and calling the catalog with the caller's credentials means you never show what the caller could not see.

Remember

  • createBackendPlugin: pluginId and register(env) → env.registerInit({ deps, init }). init runs once at startup.
  • httpRouter: mounts your Express router at /api/<pluginId>. The id alone decides the path.
  • Auth by default: every route needs credentials (a user, another plugin, a configured token). httpAuth.credentials(req) says who called.
  • Calling the catalog: catalogServiceRef is the catalog client as a service. Pass { credentials } and the catalog applies its own permissions.
  • Errors: throw NotFoundError or InputError from @backstage/errors; the backend answers 404 or 400 with a JSON body. Validate bodies with Zod.
  • Generate, don't hand-roll: yarn new --select backend-plugin creates the package and adds the backend.add line; plugin-common-library makes -common.

Pitfalls

  • Losing the backend.add line in a merge conflict: the backend starts without errors and your API answers 404 with an empty body. Smoke-test one route per plugin after deploy: 200 or 401, never 404.
  • Reading the catalog with the plugin's own credentials in a user request: you could leak entities the user may not see.

Commands

yarn new --select plugin-common-library --option pluginId=readinessThe shared types package
yarn new --select backend-plugin --option pluginId=readinessThe backend plugin, wired into the backend
curl -s -H "Authorization: Bearer $PORTAL_API_TOKEN" localhost:7007/api/readiness/checksCall your API as a script
yarn tscType-check everything

A backend plugin

export const readinessPlugin = createBackendPlugin({
  pluginId: 'readiness',                // -> /api/readiness
  register(env) {
    env.registerInit({
      deps: {
        httpAuth: coreServices.httpAuth,
        httpRouter: coreServices.httpRouter,
        catalog: catalogServiceRef,
      },
      async init({ httpAuth, httpRouter, catalog }) {
        httpRouter.use(await createRouter({ httpAuth, catalog, checks: defaultChecks }));
      },
    });
  },
});

Test yourself

Answer in your head first, then open the answer.

Why does /evaluate pass the caller's credentials to the catalog?

So the catalog applies its own permissions to what the caller may see.

Where does httpRouter mount the readiness routes?

Under /api/readiness, from the plugin id.

A route throws new NotFoundError(...). What does the caller get?

A 404 with a JSON body that carries the message.

Where module 01 leaves you: The backend serves /api/readiness. It lists five checks and scores checkout at 80 (it has no runbook link). Merged in your portal repo.