PlatformEngr® Backstage course

Cheat notes · Module 07 · Golden paths and docs

Your own scaffolder action

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

Company rules need your own code, and the smallest piece of backend code is a module: it plugs into one plugin (the scaffolder) through its extension point and gets services like the catalog injected.

Remember

  • Backend module: createBackendModule({ pluginId: 'scaffolder', moduleId }), added with backend.add. yarn new --select scaffolder-backend-module generates and wires it.
  • Extension point: what a plugin offers modules. scaffolderActionsExtensionPoint.addActions(...) registers actions.
  • Service refs in deps: catalogServiceRef, loggerServiceRef, databaseServiceRef; the backend injects them.
  • The action: createTemplateAction({ id, description, schema: { input, output }, handler }). Schemas are Zod; the Installed Actions page shows them. ctx.output(...) feeds later steps.
  • On whose behalf: read the catalog with ctx.getInitiatorCredentials(), so permissions apply to the user running the template.
  • Dry run: supportsDryRun: true only for read-only actions. An action that writes checks ctx.isDryRun and skips the write.

Pitfalls

  • Deleting supportsDryRun: true: tests and types still pass, but the dry run logs Skipping because acme:team:lookup does not support dry-run and the README says Owned by ****. Test for it.
  • Throwing a generic Error for bad input: use InputError so the user sees a clear message, and fail before anything is created (put the lookup step first).

Commands

yarn new --select scaffolder-backend-module --option moduleId=acmeGenerate the module and wire it into the backend
cd plugins/scaffolder-backend-module-acme && yarn test --watchAll=falseRun its tests
yarn tscType-check the whole repo

A module that adds an action

export const scaffolderModuleAcme = createBackendModule({
  pluginId: 'scaffolder',
  moduleId: 'acme',
  register({ registerInit }) {
    registerInit({
      deps: {
        scaffolderActions: scaffolderActionsExtensionPoint,
        catalog: catalogServiceRef,
      },
      async init({ scaffolderActions, catalog }) {
        scaffolderActions.addActions(createTeamLookupAction({ catalog }));
      },
    });
  },
});

Test yourself

Answer in your head first, then open the answer.

How does your module get the catalog client?

It lists catalogServiceRef in deps and the backend injects it.

Why is the action a module of the scaffolder and not its own plugin?

It extends the scaffolder through its extension point and has no API of its own.

Why read the catalog with ctx.getInitiatorCredentials()?

The read happens as the user who runs the template, so permissions apply to them.

Where module 07 leaves you: The scaffolder has your acme:team:lookup action with three passing tests. New services say "Owned by Team Checkout" in their README and docs.