Learn Backstage - Advanced Scaffolder & Custom Actions
Episode 10 of 23

Learn Backstage - Advanced Scaffolder & Custom Actions

Bringing the Scaffolder to production level: designing multi-step workflows with if and when conditional logic, taking advantage of output as links, entities, and entityRef, restricting templates with permissions, and building custom actions with createTemplateAction to provision cloud resources from internal tooling.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 6, you assembled a three-step template: fetch, publish, register. In episode 9, you tidied up the configuration surrounding it. Episode 10 combines both at a higher level: Advanced Scaffolder & Custom Actions. Templates are no longer just about copying folders — they can branch based on user answers, produce entities and links, be restricted by permissions, and even call internal tooling to provision cloud resources. This is where the Scaffolder changes from a repository creator into a platform engineering orchestrator.

Basic Templates vs Advanced Templates

Before diving into details, compare how the concepts evolve from episode 6 to episode 10:

ConceptBasic (episode 6)Advanced (episode 10)
StepsFew and linearMany, branching with conditions
Outputlinks and textlinks, entities, and entityRef
AccessAll users can useRestricted by per-template permissions
ActionsBuilt-in onlyBuilt-in plus internal custom actions

Multi-Step Workflows

An advanced workflow can consist of many steps: fetch a template, create a repository, run an audit, call an internal API, then register the entity. Each step still runs sequentially and can consume the previous step's output. Pipeline length isn't the issue — what matters is that every step has a clear id so its output can be referenced by the next step and by the template output. In practice, the common order is fetch:template first, then publish:github, with validation or provisioning steps inserted in between that use the previous step's output through the steps.<id>.output.<field> pattern.

Conditional Logic

Not every step has to run every time. The Scaffolder supports conditional logic through when on a step: the step only executes if the condition is met. Conditions usually use parameter values or previous step output — very useful for templates serving more than one kind of component.

Langkah kondisional dengan when
steps:
  - id: fetch-base
    name: Ambil Base Template
    action: fetch:template
    input:
      url: ./templates/service
      values:
        name: ${{ parameters.name }}
 
  - id: provision-db
    name: Provisi Database
    action: acme:cloud:provision-database
    input:
      instance: ${{ parameters.name }}-db
    when:
      condition: ${{ parameters.includeDatabase }}
 
  - id: publish
    name: Publikasikan ke GitHub
    action: publish:github
    input:
      repoUrl: ${{ parameters.repoUrl }}

The provision-db step only runs if the user checks includeDatabase in the form. For those who don't need a database, the pipeline skips that step without interruption. Conditions can be built more complex with operators like not, allOf, and anyOf for trickier needs.

Template output is no longer just links. There are three main forms:

Output typeContentsUsage
linksA list of links with titles and iconsPointing to a repository, pipeline, or dashboard
entitiesFull entities or entity referencesRegistering new entities produced by scaffolding
entityRefAn entity reference in catalog formatLinking to an existing entity

entities output lets a template register components, APIs, or resources all at once. An entityRef in catalog format like component:default/payment-api keeps references between entities consistent and linkable by the catalog — a theme we'll dive deep into in episode 11.

Template Permissions

Not every template deserves to be used by everyone. A template for internal infrastructure, for instance, should probably only be run by the platform team. Template permissions bind the Scaffolder to Backstage's permission framework: before a template executes, the permission policy is checked, and users who aren't allowed are rejected. Policies can be ownership-based — for example only owners of a certain group may run a template — or custom rules through permission policies. This keeps the golden path open for many teams while protecting sensitive paths.

Custom Actions: createTemplateAction

Built-in actions are enough for standard flows, but internal tooling almost always has unique needs: creating tickets, provisioning databases, calling internal pipelines, or sending notifications. All of that can be wrapped into a custom action using createTemplateAction from the scaffolder backend.

Membuat custom action dengan createTemplateAction
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
 
export const createJiraTicket = createTemplateAction({
  id: 'acme:jira:create-ticket',
  description: 'Membuat tiket di Jira internal',
  schema: {
    input: {
      type: 'object',
      required: ['title'],
      properties: {
        title: { type: 'string', title: 'Judul tiket' },
      },
    },
  },
  async handler(ctx) {
    const ticket = await jiraClient.createTicket({
      summary: ctx.input.title,
    });
    ctx.logger.info(`Tiket dibuat untuk ${ctx.input.title}`);
    ctx.output('ticketUrl', ticket.url);
  },
});

The handler receives ctx with ctx.input holding the values from the template, ctx.output for writing results, and ctx.logger for logging. The id used becomes the action name templates call via action: acme:jira:create-ticket.

The Action Registry

A custom action you've built needs to be registered into the scaffolder's action registry — the list of actions the backend knows. Through the scaffolder extension point, you add one or more actions to the registry when the backend starts.

Mendaftarkan custom action di backend
import { createBackendModule } from '@backstage/backend-plugin-api';
import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha';
import { createJiraTicket } from './actions/jira';
 
export default createBackendModule({
  pluginId: 'scaffolder',
  moduleId: 'acme-actions',
  register(reg) {
    reg.registerInit({
      deps: { scaffolder: scaffolderActionsExtensionPoint },
      async init({ scaffolder }) {
        scaffolder.addActions(createJiraTicket);
      },
    });
  },
});

Once this module loads, the acme:jira:create-ticket action is available to all templates. The action registry becomes the catalog of actions templates are allowed to use — managing it carefully keeps your platform's integration surface controlled.

Integration with Internal Tooling

The real strategic value of custom actions is connecting the Scaffolder to your organization's internal tooling: cloud providers, orchestration platforms, ticketing systems, or data pipelines. A service creation template could, for example, call an internal action that provisions a VPC and a database in the cloud before the repository is published. The data flows in a chain: the VPC provisioning action's vpcId output becomes the input of the database provisioning action, and its result is used by the next action. With this pattern, the entire service creation process — from code to infrastructure — runs through one golden path, and cloud credentials are pulled from ctx.secrets or the environment, never written into the template.

Tip

Start writing custom actions small: wrap a single internal API call with createTemplateAction, test it through a simple template, then grow the complexity. A clear action id, a required input schema, and using ctx.secrets for credentials will keep the action safe to use from any template.

Conclusion

In this episode 10, you brought the Scaffolder to production level: multi-step workflows with data flowing between steps, conditional logic through when with the not, allOf, and anyOf operators, output as links, entities, and entityRef, template permissions to restrict access, and custom actions using createTemplateAction registered through the action registry to provision cloud resources from internal tooling.

The key takeaways:

  • An advanced workflow is a branching pipelinewhen makes a step run only if its condition is met.
  • Output is more than linksentities and entityRef connect scaffolding results to the catalog.
  • Custom actions wrap internal toolingcreateTemplateAction turns internal APIs into actions any template can use.
  • The action registry keeps control — only registered actions can be called, and credentials always go through ctx.secrets or the environment.

In the next episode, episode 11, we look at the data side behind all the entities you scaffold: Advanced Catalog & Data Model — relations between entities like ownership, partOf, dependsOn, and providesApi, modeling systems, domains, and resources, catalog filters, and keeping catalog quality in CI.

Learn Backstage - Advanced Scaffolder & Custom Actions | Learn Backstage