Backstage Custom Field Extension: Dynamic Field Updates

Backstage makes building developer portals a breeze — especially with Software Templates for scaffolding services. But what if you want to build dynamic forms?

For example:

“Pick an AWS account, and auto-select the right IAM role or environment based on the account.”

Let’s walk through how I built an AwsAccountPicker — a custom field extension that listens to changes in another field and updates itself accordingly.

I am using FieldProps (from @rjsf/utils) instead of the Backstage-specific FieldExtensionComponentProps

Why FieldProps? While Backstage templates usually encourage using FieldExtensionComponentProps, using FieldProps gives you full access to the underlying JSON Schema Form engine — giving you more control, especially for dynamic behaviours like reacting to other fields.

Requirement

A custom field extension (AwsAccountPicker) that:

  • Fetches a list of AWS accounts.
  • Observes another form field (like serviceName) via formContext.formData.
  • Dynamically selects the appropriate account based on a pattern match.
Setting Up the Field Extension

In Backstage frontend app/src/components/scaffolder/customScaffolderExtensions.tsx:

import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react';
import { AwsAccountPicker } from './AwsAccountPicker';

export const awsAccountPickerExtension: FieldExtensionOptions = {
  name: 'AwsAccountPicker',
  component: AwsAccountPicker,
};

Inside app/src/components/scaffolder/AwsAccountPicker.tsx:

import React, { useEffect, useMemo } from 'react';
import { FieldProps } from '@rjsf/utils';
import { TextField, MenuItem } from '@material-ui/core';

export const AwsAccountPicker = ({
  formData,
  onChange,
  uiSchema,
  registry,
  schema,
}: FieldProps) => {
  const allFormData = registry.formContext?.formData ?? {};
  const uiOptions = uiSchema['ui:options'] || {};

  const accounts = [
    { id: '123456789012', name: 'dev-account' },
    { id: '210987654321', name: 'prod-account' },
  ]; # Use a hook to get this dynamically from an api

  const valueFrom = uiOptions.autofillFrom;
  const outputType = uiOptions.output || 'id';
  const serviceName = allFormData[valueFrom];

  useEffect(() => {
    if (!serviceName) return;
    const expectedAccount = `${serviceName}-account`;
    const match = accounts.find(acc =>
      expectedAccount.toLowerCase().includes(acc.name.toLowerCase())
    );
    if (match && formData !== match[outputType]) {
      onChange(match[outputType]);
    }
  }, [serviceName, formData, onChange, outputType]);

  return (
    <TextField
      select
      label={schema.title}
      value={formData ?? ''}
      onChange={e => onChange(e.target.value)}
    >
      {accounts.map(account => (
        <MenuItem key={account.id} value={account[outputType]}>
          {account.name}
        </MenuItem>
      ))}
    </TextField>
  );
};

Above is a simplified version of the component using FieldProps

How this Works
  • registry.formContext.formData gives you access to other form fields.
  • uiSchema['ui:options'].autofillFrom defines which field to observe.
  • useEffect triggers when the observed field changes and auto-updates the current field.

This approach is clean, does not require extra state management, and works naturally with Backstage scaffolder templates.

Using It in a Template

In template.yaml:

parameters:
  - title: Service Details
    required:
      - serviceName
      - awsAccount
    properties:
      serviceName:
        type: string
        title: Service Name
      awsAccount:
        title: AWS Account
        type: string
        ui:field: AwsAccountPicker
        ui:options:
          autofillFrom: serviceName
          output: id

With this setup, if serviceName = my-service, and your AWS accounts include my-service-account, the form will auto-select the correct account.

The same pattern can be extended to:

  • Conditionally fetch data based on other fields.
  • Support custom inputs with validation.
  • Handle grouped permissions, environments, or region selectors.
Design a site like this with WordPress.com
Get started