Generate a scaffolder module plugin with the Backstage CLI: yarn backstage-cli new --select scaffolder-module, which scaffolds the boilerplate directory structure.
In the module's src directory, call createTemplateAction({ id: 'namespace:entity:verb', description: '...', schema: { input: z.object({...}), output: z.object({...}) }, handler: async (ctx) => {...} }) from @backstage/plugin-scaffolder-node.
Use ctx.input to read validated input values and ctx.output to set named output values accessible by downstream steps via ${{ steps.stepId.output.fieldName }}.
Register the custom action by adding it to the list passed to createBuiltinActions in the scaffolder backend plugin configuration.
Write unit tests for the handler using the createMockActionContext helper from @backstage/plugin-scaffolder-node-test-utils.
Reference the custom action in templates by its id string in a step's action field; redeploy or hot-reload the Backstage backend to pick up new action registrations.
Known gotchas
The action id must follow the provider:entity:verb convention and must be unique across all registered actions; duplicate ids cause the last-registered action to silently shadow earlier ones.
Zod schemas are converted to JSON Schema for the UI form automatically, but complex Zod types like z.union may not render the expected UI widget; test the form in the Backstage UI, not just in unit tests.
Custom actions run inside the scaffolder backend process; avoid blocking the event loop with synchronous heavy computation—use await and streams for long-running operations.
Give your agent this knowledge — and 200+ more routes
One MCP install gives any agent live access to the full route map, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp