When to use this skill
- Building a custom action that scaffolder templates can call (e.g.
acme:user:create,myorg:slack:notify) - Editing an existing
scaffolder-backend-module-*plugin
What you build
A scaffolder action is a backend module (createBackendModule({ pluginId: 'scaffolder', ... })) that registers a TemplateAction against the scaffolderActionsExtensionPoint. Two pieces:
- The action itself (created via
createTemplateAction) - The module that registers it
Choose the right pattern
| Building... | Reference |
|---|---|
| The action itself | references/template-action.md |
| The module that wires it in | references/module-registration.md |
Two invocation modes
- Standalone: editing an existing scaffolder-backend-module-*.
- Post-scaffold (called by
creating-backstage-pluginforscaffolder-backend-module): scaffold both files together — action definition + module registration.
Common gotchas
- Action
idfollows the<namespace>:<name>(or<namespace>:<group>:<name>) convention. The namespace is yours to choose; pick something stable that won't collide with other actions in the catalog of installed modules. - Schemas are factory functions, not raw Zod schemas:
input: { name: z => z.string() }(notinput: { name: z.string() }). Backstage uses this pattern to pin its Zod version internally — verify the form against../backstage/plugins/scaffolder-node/src/actions/createTemplateAction.ts. - Action handler context (
ctx) provides:ctx.logger,ctx.workspacePath,ctx.input(typed from the input schema),ctx.output(name, value)to emit outputs,ctx.getInitiatorCredentials()for the user identity,ctx.createTemporaryDirectory()for sandboxed work,ctx.signal(AbortSignal) for cancellation,ctx.isDryRunfor dry-run mode. - Long-running actions should call
ctx.logger.infoperiodically — the scaffolder UI streams logs to the user. - For dry-run support, set
supportsDryRun: trueand checkctx.isDryRunin the handler to skip side effects. - Templates can use
if: ${{ always() }}andif: ${{ failure() }}to control step execution after failures —always()runs unconditionally,failure()runs only when a prior step failed. These are built into the template engine; action authors don't need to do anything to support them.