Skip to main content

Creating and Managing Software Templates

After understanding the Software Catalog and Entity model in Backstage Core Concepts, this document explains how to create, manage, and publish software templates with the Scaffolder. Templates codify "the right way" to do things in your organization as a Golden Path, letting developers spin up a fully wired new project in minutes.


1. What are Software Templates

Software Templates are the new-project automation provided by the Backstage Scaffolder plugin. A developer fills in a few form fields, and the Scaffolder creates the repository, generates the initial code, wires up CI/CD, and registers the project in the catalog — all in one flow.

Positioning as a Golden Path

A Golden Path is "the organization's recommended, low-friction route for development." Templates make that path concrete.

  • Reduced cognitive load — no need to research boilerplate or initial setup.
  • Guidance, not enforcement — make the right choice the easy choice.
  • Best practices built in — security settings, naming conventions, and ownership are correct from the start.

Typical use cases

Use caseWhat the template generates
New microserviceService skeleton + Dockerfile + CI/CD + catalog-info.yaml
Shared libraryPackage skeleton + publish config + docs
IaC moduleTerraform module skeleton + review settings
Documentation siteTechDocs setup (mkdocs.yml + docs/)

2. Template structure

A template is defined in template.yaml (kind: Template) and is itself registered in the catalog as an Entity.

apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: dotnet-microservice
title: .NET Microservice
description: Generates a standard .NET service
tags:
- dotnet
- recommended
spec:
owner: group:platform-team
type: service

# (1) Input form
parameters:
- title: Basic info
required:
- name
properties:
name:
title: Service name
type: string
pattern: '^[a-z][a-z0-9-]*$'
- title: Repository
required:
- repoUrl
properties:
repoUrl:
title: Repository location
type: string
ui:field: RepoUrlPicker
ui:options:
allowedHosts:
- github.com

# (2) Execution steps
steps:
- id: fetch
name: Expand template
action: fetch:template
input:
url: ./skeleton
values:
name: ${{ parameters.name }}

- id: publish
name: Create repository
action: publish:github
input:
repoUrl: ${{ parameters.repoUrl }}
description: ${{ parameters.name }} service

- id: register
name: Register in catalog
action: catalog:register
input:
repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
catalogInfoPath: '/catalog-info.yaml'

# (3) Output shown on completion
output:
links:
- title: Open repository
url: ${{ steps.publish.output.remoteUrl }}
- title: Open in catalog
icon: catalog
entityRef: ${{ steps.register.output.entityRef }}
BlockRole
parametersDefines the input form shown to developers using JSON Schema
stepsA sequence of actions executed using the input values
outputLinks and entry points to generated artifacts shown on the completion screen
The skeleton directory

The files that get expanded live under skeleton/ (any name). Inside files you embed input values with nunjucks syntax such as ${{ values.name }}. File names themselves can be templated too (e.g., ${{ values.name }}.csproj).


3. Parameter design

parameters are turned into a form UI by react-jsonschema-form.

Form UI and validation

  • Multiple pages — making parameters an array turns each element into a wizard step.
  • Validation — constrain input with standard JSON Schema features like pattern, maxLength, and enum.
  • Input assistance — refine UX with ui:help, ui:placeholder, and ui:autofocus.

Custom fields

Backstage provides Backstage-specific custom fields.

FieldPurpose
RepoUrlPickerSelect the destination repository (host, org, repo name)
EntityPickerSelect an entity from the catalog (e.g., the owner Group)
OwnerPickerSelect from Groups / Users that can be owners
MultiEntityPickerSelect multiple entities
owner:
title: Owner
type: string
ui:field: OwnerPicker
ui:options:
catalogFilter:
kind: Group
Use ui:field: Secret for secrets

When accepting tokens or passwords, use ui:field: Secret (not ui:widget: password). The value is held in memory only during step execution and never persisted to logs or the catalog.


4. Built-in actions

The action used in each step ships with a set of common built-ins.

ActionRole
fetch:templateExpand the skeleton with nunjucks into the workspace
fetch:plainCopy files as-is, without template processing
publish:github / publish:gitlab / publish:azureCreate a new repository on the SCM and push
publish:github:pull-requestCreate a PR against an existing repo instead of a new repo
catalog:registerRegister the generated catalog-info.yaml in the catalog
github:actions:dispatchTrigger a GitHub Actions workflow after generation
debug:logEmit log output for debugging

Approach to custom actions

When the built-ins fall short (calling internal APIs, custom SCM integrations, etc.), create a custom action.

  • Implement it in TypeScript with createTemplateAction and register it in the backend.
  • Type inputs/outputs with a Zod schema so they are validated like the form.
  • First check whether you can compose existing actions, and keep custom actions minimal.

5. Managing and operating templates

Template repository layout

Templates are commonly grouped in a dedicated repository (e.g., software-templates), one directory per template.

software-templates/
├── dotnet-microservice/
│ ├── template.yaml
│ └── skeleton/ # the files that get expanded
│ ├── catalog-info.yaml
│ ├── Dockerfile
│ └── src/...
└── react-frontend/
├── template.yaml
└── skeleton/...

Each template.yaml is registered automatically via catalog.locations in app-config.yaml or via GitHub Discovery.

Versioning and change management

  • Change templates like code: PR review → merge.
  • For breaking changes, rename metadata.name (e.g., -v2) or signal generations with tags.
  • Note that changes do not propagate to repositories already generated — generation happens once.

Testing / CI

  • Add template.yaml schema validation (backstage-cli) to CI.
  • Periodically confirm that key templates actually generate and build (drift detection).

Visibility / permissions

Combined with the Permission Framework, you can control "who may run which template." Restrict sensitive templates (e.g., creating production infrastructure) to specific Groups.


6. Integration with organizational standards

The real value of templates comes from embedding organizational standards into the skeleton.

What you embedEffect
Default CI/CD pipelineEvery service uses the same build/test/deploy foundation
Security settingsInclude SAST, dependency scanning, and SBOM generation in the initial setup
catalog-info.yamlEliminate missing catalog registrations and auto-populate annotations
mkdocs.yml + docs/Enable TechDocs from day one
Automatic owner assignmentFeed the OwnerPicker value into spec.owner to prevent ownerless entities
Prevent missing annotations

As noted in core concepts, annotations are the integration keys for each plugin. Auto-populating them in the template means services are born with integrations for Kubernetes, CI, and Dependency-Track already wired up.


Best Practices

  • Keep templates "few and high quality" — a proliferation of similar templates causes choice paralysis and breaks maintenance. Absorb commonalities into parameters.
  • Minimal parameters with strong defaults — reduce what developers must decide; pre-fill what can be defaulted.
  • Clear maintenance ownership — set spec.owner on the template itself so someone is responsible for keeping it fresh.
  • Verify output freshness in CI — periodically check "generate → build succeeds" to catch dependency rot.
  • Avoid anti-patterns — one huge template that does everything, hardcoded hostnames or tokens, and passing secrets without Secret.

References