Scaffolder Template Design

Scaffolder Template Design serves as the foundational architecture for standardizing service creation across modern developer portals. By defining reusable, parameterized workflows, platform teams can enforce architectural guardrails while accelerating developer onboarding. Effective template design integrates seamlessly with the broader Plugin Ecosystem & Custom Extensions, ensuring that automated provisioning aligns with organizational compliance, infrastructure standards, and team-specific operational requirements.

Prerequisites

Before architecting a new template, verify that your environment meets core operational requirements. Platform engineers must establish a secure, auditable baseline for template execution and entity registration.

  • Repository Access & Credentials: Configure provider-specific tokens via environment variables (GITHUB_TOKEN, GITLAB_TOKEN, BITBUCKET_TOKEN). Store secrets in a centralized vault (e.g., HashiCorp Vault, AWS Secrets Manager) and inject them into the Backstage backend via app-config.yaml.
  • RBAC & Template Visibility: Implement role-based access control using the @backstage/plugin-permission-backend. Define policies that restrict template visibility to authorized groups (catalog:read, scaffolder:template:use).
  • Software Catalog Schema Alignment: Ensure your catalog-info.yaml schema matches the target entity types (Component, API, System). Validate against the backstage.io/v1alpha1 specification.
  • CI/CD Runner Permissions: Provision OIDC workload identity or IAM roles for execution runners. Runners require repo:write, actions:write, and infrastructure provisioning permissions (e.g., AWS sts:AssumeRole, GCP iam.serviceAccountTokenCreator) to bootstrap pipelines without manual credential rotation.
  • CLI Toolchain: Install @backstage/cli@^0.26.0 and ensure yarn or npm is available for local dry-runs and schema validation.

Step-by-Step Configuration

The configuration phase focuses on structuring the YAML definition, defining input parameters, and chaining execution actions. Templates must be deterministic, idempotent, and strictly validated before deployment.

Core YAML Architecture & Parameterization

Start by declaring the apiVersion and kind: Template, followed by a spec block containing parameters and steps. Use JSON Schema validation (required, type, pattern, ui:widget) to enforce type safety and required fields for user inputs.

apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
 name: standardized-service-template
 title: Standardized Service Template
 description: Bootstraps a new service with CI/CD and catalog registration
 tags:
 - platform
 - scaffolder
spec:
 owner: platform-engineering
 type: service
 parameters:
 - title: Service Configuration
 required:
 - repoName
 - owner
 - targetEnvironment
 properties:
 repoName:
 title: Repository Name
 type: string
 pattern: "^[a-z0-9-]+$"
 ui:autofocus: true
 owner:
 title: Team Owner (Group)
 type: string
 ui:field: EntityPicker
 ui:options:
 allowedKinds:
 - Group
 targetEnvironment:
 title: Deployment Environment
 type: string
 enum:
 - staging
 - production
 ui:widget: radio
 steps:
 - id: fetch-base
 name: Fetch Base Template
 action: fetch:template
 input:
 url: ./template
 values:
 repoName: ${{ parameters.repoName }}
 environment: ${{ parameters.targetEnvironment }}
 githubOrg: ${GITHUB_ORG}

Action Sequencing & Custom Extensions

Chain actions sequentially to handle repository creation, file templating, and initial CI/CD pipeline generation. Each step must output artifacts required by subsequent actions using the output field. When extending default capabilities, consider Building Custom Backstage Plugins to inject proprietary provisioning logic directly into the scaffolder pipeline.

 - id: create-repo
 name: Create Repository
 action: publish:github
 input:
 repoUrl: ${GITHUB_HOST}?owner=${GITHUB_ORG}&repo=${{ parameters.repoName }}
 defaultBranch: main
 description: "Auto-provisioned service for ${{ parameters.owner }}"
 topics:
 - backstage-scaffolded
 - ${{ parameters.targetEnvironment }}
 protectDefaultBranch: true

 - id: register-catalog
 name: Register Component
 action: catalog:register
 input:
 repoContentsUrl: ${{ steps['create-repo'].output.repoContentsUrl }}
 catalogInfoPath: /catalog-info.yaml

Validation

Rigorous validation prevents deployment failures and ensures catalog consistency. Implement automated checks at both the developer workstation and CI pipeline levels.

Dry-Run Execution & Schema Linting

Implement dry-run execution to verify parameter resolution, action sequencing, and file generation before publishing. Cross-reference the generated catalog-info.yaml against established Catalog Integration Patterns to guarantee seamless entity registration, ownership mapping, and metadata propagation.

# catalog-info.yaml (Injected during scaffolding)
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
 name: ${{ parameters.repoName }}
 description: Auto-generated service component
 tags:
 - scaffolder
 - automated
 - ${{ parameters.targetEnvironment }}
spec:
 type: service
 lifecycle: experimental
 owner: ${{ parameters.owner }}
 system: platform-core
 dependsOn:
 - resource:platform-db-cluster

CI Pipeline Integration

Integrate automated schema linting and template parsing checks into your CI pipeline to catch structural regressions early.

#!/usr/bin/env bash
set -euo pipefail

# 1. Validate template syntax and JSON schema compliance
backstage-cli template validate --path ./templates/service-template.yaml

# 2. Run dry-run simulation with mock parameters
backstage-cli scaffolder dry-run \
 --template ./templates/service-template.yaml \
 --output ./dry-run-output \
 --values '{"repoName":"test-svc-01","owner":"group:default/platform-team","targetEnvironment":"staging"}'

# 3. Verify generated catalog entity
cat ./dry-run-output/catalog-info.yaml | yq '.spec.type' | grep -q "service" && echo "Catalog schema valid"

Debugging & Deployment Verification

  • Log Inspection: Enable scaffolder debug logging via LOG_LEVEL=debug in the Backstage backend. Monitor action:fetch:template and action:publish:* execution traces.
  • Artifact Inspection: After dry-run, verify ./dry-run-output/ contains all expected files. Check for unrendered ${{ parameters.* }} placeholders.
  • Deployment: Commit validated templates to a dedicated Git repository (git@github.com:org/backstage-templates.git). Configure Backstage catalog.providers.scaffolder to poll this repository.

Maintenance

Template maintenance requires a structured versioning strategy and backward compatibility planning. Use semantic versioning for template releases and maintain a clear deprecation policy for legacy schemas. Monitor usage metrics to identify underutilized parameters and refactor complex workflows into modular sub-templates. For language-specific implementations, refer to Writing custom scaffolder templates for Node.js services as a reference for structuring dependency resolution, runtime configuration, and automated testing hooks.

Versioning & Rollback Procedures

  • Version Control: Tag template directories using Git tags (v1.2.0). Reference specific versions in Backstage via templateRef: github.com?owner=org&repo=backstage-templates&path=templates/service-template&ref=v1.2.0.
  • Rollback Strategy: If a template update causes pipeline failures:
  1. Revert the Git tag to the last stable commit.
  2. Update app-config.yaml to point catalog.providers.scaffolder to the stable ref.
  3. Restart Backstage backend pods to clear cached template definitions.
  4. Manually clean up orphaned repositories using gh repo delete <org>/<repo> and remove stale catalog entities via the Backstage API (DELETE /api/catalog/entities/by-uid/<uid>).

Monitoring & Refactoring

Track template execution success rates, average completion time, and parameter usage distribution. Decompose monolithic templates into reusable fetch:template sub-modules. Implement conditional steps using if: ${{ parameters.targetEnvironment === 'production' }} to reduce branching complexity.

Common Pitfalls

Pitfall Mitigation Strategy
Overloading templates with environment-specific logic Leverage parameterization, conditional steps, and dynamic file templating (fetch:template with values mapping).
Failing to validate catalog-info.yaml generation Implement CI linting (@backstage/cli template validate) and dry-run assertions to prevent orphaned entities.
Hardcoding repository URLs or branch names Always use ${{ parameters.* }} interpolation and ${ENV_VAR} placeholders for host/org resolution.
Neglecting RBAC scoping Enforce catalog:read and scaffolder:template:use permissions via CASL policies; audit template visibility quarterly.
Skipping dry-run validation Mandate backstage-cli scaffolder dry-run in PR checks before merging template updates.

FAQ

How do I enforce mandatory security checks within a scaffolder template? Integrate a fetch:template or custom action:run step that executes security linting (e.g., Trivy, Snyk, npm audit) before the repository is published. Use conditional parameters to block execution if critical vulnerabilities are detected. Example:

- id: security-scan
 name: Security Linting
 action: run:command
 input:
 command: npx trivy fs . --severity CRITICAL --exit-code 1
 workingDirectory: ${{ steps['fetch-base'].output.workspacePath }}

Can scaffolder templates be versioned independently of the Backstage core? Yes. Templates are stored as YAML files in a Git repository and can be tagged using semantic versioning. The Backstage catalog can reference specific template versions via templateRef, allowing teams to roll out updates without breaking existing workflows. Use catalog.providers.scaffolder polling intervals to sync new tags automatically.

What is the recommended approach for handling multi-environment deployments in templates? Define environment-specific parameters (e.g., targetEnvironment, region, clusterName) and use conditional action steps or dynamic file templating to inject environment-appropriate configurations. Avoid hardcoding infrastructure endpoints; instead, resolve them via a centralized configuration service or parameter lookup. Utilize fetch:template with environment-specific directories (./template/staging/, ./template/production/) selected via if conditions in the steps array.