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 case | What the template generates |
|---|---|
| New microservice | Service skeleton + Dockerfile + CI/CD + catalog-info.yaml |
| Shared library | Package skeleton + publish config + docs |
| IaC module | Terraform module skeleton + review settings |
| Documentation site | TechDocs 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 }}
| Block | Role |
|---|---|
parameters | Defines the input form shown to developers using JSON Schema |
steps | A sequence of actions executed using the input values |
output | Links and entry points to generated artifacts shown on the completion screen |
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
parametersan array turns each element into a wizard step. - Validation — constrain input with standard JSON Schema features like
pattern,maxLength, andenum. - Input assistance — refine UX with
ui:help,ui:placeholder, andui:autofocus.
Custom fields
Backstage provides Backstage-specific custom fields.
| Field | Purpose |
|---|---|
RepoUrlPicker | Select the destination repository (host, org, repo name) |
EntityPicker | Select an entity from the catalog (e.g., the owner Group) |
OwnerPicker | Select from Groups / Users that can be owners |
MultiEntityPicker | Select multiple entities |
owner:
title: Owner
type: string
ui:field: OwnerPicker
ui:options:
catalogFilter:
kind: Group
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.
| Action | Role |
|---|---|
fetch:template | Expand the skeleton with nunjucks into the workspace |
fetch:plain | Copy files as-is, without template processing |
publish:github / publish:gitlab / publish:azure | Create a new repository on the SCM and push |
publish:github:pull-request | Create a PR against an existing repo instead of a new repo |
catalog:register | Register the generated catalog-info.yaml in the catalog |
github:actions:dispatch | Trigger a GitHub Actions workflow after generation |
debug:log | Emit 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
createTemplateActionand 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 withtags. - Note that changes do not propagate to repositories already generated — generation happens once.
Testing / CI
- Add
template.yamlschema 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 embed | Effect |
|---|---|
| Default CI/CD pipeline | Every service uses the same build/test/deploy foundation |
| Security settings | Include SAST, dependency scanning, and SBOM generation in the initial setup |
catalog-info.yaml | Eliminate missing catalog registrations and auto-populate annotations |
mkdocs.yml + docs/ | Enable TechDocs from day one |
| Automatic owner assignment | Feed the OwnerPicker value into spec.owner to prevent ownerless entities |
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.owneron 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.