Skip to main content

What's new?

Well then, here we are! 🚀

Backstage has had many forms of templating languages throughout different plugins and different systems. We've had cookiecutter syntax in templates, and we also had handlebars templating in the kind: Template. Then we wanted to remove the additional dependency on cookiecutter for Software Templates out of the box, so we introduced nunjucks as an alternative in fetch:template action which is based on the jinja2 syntax so they're pretty similar. In an effort to reduce confusion and unify on to one templating language, we're officially deprecating support for handlebars templating in the kind: Template entities with apiVersion scaffolder.backstage.io/v1beta3 and moving to using nunjucks instead.

This provides us a lot of built in filters (handlebars helpers), that as Template authors will give you much more flexibility out of the box, and also open up sharing of filters in the Entity and the actual skeleton too, and removing the slight differences between the two languages.

We've also removed a lot of the built in helpers that we shipped with handlebars, as they're now supported as first class citizens by either nunjucks or the new scaffolder when using scaffolder.backstage.io/v1beta3 apiVersion

The migration path is pretty simple, and we've removed some of the pain points from writing the handlebars templates too. Let's go through what's new and how to upgrade.

Add the Processor to the plugin-catalog-backend

An important change is to add the required processor to your packages/backend/src/plugins/catalog.ts

packages/backend/src/plugins/catalog.ts
import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';

export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
builder.addProcessor(new ScaffolderEntitiesProcessor());
const { processingEngine, router } = await builder.build();

// ..
}

backstage.io/v1beta2 -> scaffolder.backstage.io/v1beta3

The most important change is that you'll need to switch over the apiVersion in your templates to the new one.

  kind: Template
apiVersion: backstage.io/v1beta2
apiVersion: scaffolder.backstage.io/v1beta3

${{ }} instead of "{{ }}"

One really big readability issue and cause for confusion was the fact that with handlebars and yaml you always had to wrap your templating strings in quotes in yaml so that it didn't try to parse it as a json object and fail. This was pretty annoying, as it also meant that all things look like strings. Now that's no longer the case, you can now remove the "" and take advantage of writing nice yaml files that just work.

spec:
steps:
input:
allowedHosts: ['github.com']
description: 'This is {{ parameters.name }}'
description: This is ${{ parameters.name }}
repoUrl: '{{ parameters.repoUrl }}'
repoUrl: ${{ parameters.repoUrl }}

No more eq or not helpers

These helpers are no longer needed with the more expressive api that nunjucks provides. You can simply use the built-in nunjucks and jinja2 style operators.

spec:
steps:
input:
if: '{{ eq parameters.value "backstage" }}'
if: ${{ parameters.value === "backstage" }}

And then for the not

spec:
steps:
input:
if: '{{ not parameters.value "backstage" }}'
if: ${{ parameters.value !== "backstage" }}

Much better right? ✨

No more json helper

This helper is no longer needed, as we've added support for complex values and supporting the additional primitive values now rather than everything being a string. This means that now that you can pass around parameters and it should all work as expected and keep the type that has been declared in the input schema.

spec:
parameters:
test:
type: number
name: Test Number
address:
type: object
required:
- line1
properties:
line1:
type: string
name: Line 1
line2:
type: string
name: Line 2

steps:
- id: test step
action: run:something
input:
address: '{{ json parameters.address }}'
address: ${{ parameters.address }}
test: '{{ parameters.test }}'
test: ${{ parameters.test }} # this will now make sure that the type of test is a number 🙏

parseRepoUrl is now a filter

All calls to parseRepoUrl are now a jinja2 filter, which means you'll need to update the syntax.

spec:
steps:
input:
repoUrl: '{{ parseRepoUrl parameters.repoUrl }}'
repoUrl: ${{ parameters.repoUrl | parseRepoUrl }}

Now we have complex value support here too, expect that this filter will go away in future versions and the RepoUrlPicker will return an object so parameters.repoUrl will already be a { host: string; owner: string; repo: string } 🚀

Previously, it was possible to provide links to the frontend using the named output entityRef and remoteUrl. These should be moved to links under the output object instead.

output:
remoteUrl: {{ steps['publish'].output.remoteUrl }}
entityRef: {{ steps['register'].output.entityRef }}
links:
- title: Repository
url: ${{ steps['publish'].output.remoteUrl }}
- title: Open in catalog
icon: catalog
entityRef: ${{ steps['register'].output.entityRef }}

Watch out for dash-case

The nunjucks compiler can run into issues if the id fields in your template steps use dash characters, since these IDs translate directly to JavaScript object properties when accessed as output. One possible migration path is to use camelCase for your action IDs.

  steps:
id: my-custom-action
...

id: publish-pull-request
input:
repoUrl: {{ steps.my-custom-action.output.repoUrl }} # Will not recognize 'my-custom-action' as a JS property since it contains dashes!

steps:
id: myCustomAction
...

id: publishPullRequest
input:
repoUrl: ${{ steps.myCustomAction.output.repoUrl }}

Alternatively, it's possible to keep the dash-case syntax and use brackets for property access as you would in JavaScript:

input:
repoUrl: ${{ steps['my-custom-action'].output.repoUrl }}

Summary

Of course, we're always available on discord if you're stuck or something's not working as expected. You can also raise an issue with feedback or bugs!