docs/releases/v1.51.0-next.3-changelog.md
Upgrade Helper: https://backstage.github.io/upgrade-helper/?to=1.51.0-next.3
formDecorators field
instead of EXPERIMENTAL_formDecorators. Templates that still declare
spec.EXPERIMENTAL_formDecorators are read transparently and surfaced under
the new field.8df06ec: Added webIdentityTokenFile to AwsIntegrationAccountConfig and
AwsIntegrationDefaultAccountConfig. When set along with a roleName,
DefaultAwsCredentialsManager retrieves credentials by calling
AssumeRoleWithWebIdentity (via fromTokenFile) using the file's
contents as the web identity token. The file is re-read on each
credential refresh.
The validator rejects combining webIdentityTokenFile with
accessKeyId/secretAccessKey, profile, or externalId, and
rejects setting it without a roleName.
c2de113: BREAKING: When paginating entities with an order field via /entities/by-query, entities that lack the order field are now excluded from both the result set and the totalItems count. Previously these entities appeared at the end of the sorted result via NULLS LAST, but cursor-based pagination could not actually reach them past the first page — the count over-reported the number of navigable entities. The new behavior aligns the count with what is actually returned.
This also removes the DISTINCT deduplication from the sort-field CTE, which is a prerequisite for the planner to use the (key, value, entity_id) index in sort order and short-circuit on LIMIT. Installations with duplicate search rows should land the search-table deduplication migration before adopting this change.
ccbad9d: Improved the performance of the catalog_entities_count metric.
The legacy Prometheus and OpenTelemetry observable gauges previously each ran their own copy of the per-kind count query against the search table on every metrics scrape. On large catalogs this could pile up faster than the queries completed, contending for buffers and stalling the database.
The two callbacks now share a single query result with a short in-process TTL cache, and the underlying query reads from final_entities instead of search, avoiding the bitmap heap scans that dominated the previous form. The emitted labels and values are unchanged.
add5d1a: Restructured the entity listing endpoint so that, when a sort field is specified, the search-by-key index drives the query rather than being side-joined onto final_entities. This lets PostgreSQL walk the (key, value, entity_id) index in already-sorted order and short-circuit on LIMIT, reducing typical broad-filter paginated list times from seconds to milliseconds. Entities that lack the sort field still appear at the end of sorted results (NULLS LAST semantics preserved), ordered by entity_id.
387ea7d: Simplified the entity facets aggregation from COUNT(DISTINCT entity_id) to COUNT(*). The unique constraint on (entity_id, key, value) guarantees each entity appears at most once per search row group, making the DISTINCT unnecessary. This allows the database to use a simpler aggregation plan.
3f55b73: Improved the performance of the entity facets endpoint when filters are applied. The filtered entity set is now combined with the search table through an inner join rather than a WHERE entity_id IN (subquery). Results are unchanged; on large catalogs the query planner is able to choose dramatically cheaper plans, with measured improvements ranging from roughly 1.2× on already-fast cases to 7× or more on high-cardinality facets.
cde3643: Added missing description to the type parameter on the unregister-entity MCP action.
7445f0f: Added a migration that removes duplicate rows from the search table, creates covering indices for improved query performance, and adds a UNIQUE constraint on (entity_id, key, value).
This is a long-running migration on large catalogs. On PostgreSQL with millions of search rows, the index creation may take 5-15 minutes per index. During this time, other pods running the previous version will continue to serve traffic normally — the index creation does not block reads or writes. However, if a Kubernetes liveness probe kills the pod before the index build completes, the build is lost and the next startup will start over. On large tables this can repeat indefinitely.
For large installations, it is recommended to run the following SQL commands against your PostgreSQL database before deploying this version. Each index build takes a few minutes but does not block reads or writes. If these have already completed, the migration will detect the existing indices and skip all work — startup will be instant.
-- Step 1: Remove duplicate search rows
WITH cte AS (
SELECT ctid, row_number() OVER (PARTITION BY entity_id, key, value) AS rn
FROM search
)
DELETE FROM search USING cte WHERE search.ctid = cte.ctid AND cte.rn > 1;
-- Step 2: Create new indices (run each separately)
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS
search_entity_key_value_idx ON search (entity_id, key, value);
CREATE INDEX CONCURRENTLY IF NOT EXISTS
search_key_value_entity_idx ON search (key, value, entity_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS
search_facets_covering_idx ON search (key, original_value, entity_id)
WHERE original_value IS NOT NULL;
-- Step 3: Drop old indices that are no longer needed
DROP INDEX CONCURRENTLY IF EXISTS search_key_value_idx;
DROP INDEX CONCURRENTLY IF EXISTS search_key_original_value_idx;
Also fixed buildEntitySearch to remove duplicate output for entities with duplicate array values, and added ON CONFLICT DO UPDATE to syncSearchRows so that concurrent stitching races are handled gracefully.
Updated dependencies
dbeb7aa: Added experimental BUI (Backstage UI) form theme for scaffolder forms. All default field extensions render BUI variants when enabled.
Extension config:
app:
extensions:
- sub-page:scaffolder/templates:
config:
enableBackstageUi: true
JSX props:
<ScaffolderPage formProps={{ EXPERIMENTAL_theme: 'bui' }} />
8006acf: Promoted formDecoratorsApiRef, ScaffolderFormDecoratorsApi,
DefaultScaffolderFormDecoratorsApi, and formDecoratorsApi from @alpha
to @public.
d09c21c: The sub-page:scaffolder/templates extension now accepts a groups config
field that lets you define template groups on the template list page. Each group
has a title and a filter predicate. Templates not matched by any
configured group fall into an automatically appended "Other Templates" group.
With no groups configured, the page renders a single "Templates" group as
before.
Example:
app:
extensions:
- sub-page:scaffolder/templates:
config:
groups:
- title: Recommended Services
filter:
spec.type: service
- title: Documentation
filter:
spec.type: documentation
.default()
are applied and invalid input is reported through the error API instead of
silently passing through.spec.formDecorators field on a template, falling back to the deprecated
spec.EXPERIMENTAL_formDecorators for templates that have not been migrated.formDecorators field on the Template spec out of experimental.
The previous EXPERIMENTAL_formDecorators field continues to work and is
kept as a deprecated alias.dbeb7aa: Added experimental BUI (Backstage UI) form theme for scaffolder forms. All default field extensions render BUI variants when enabled.
Extension config:
app:
extensions:
- sub-page:scaffolder/templates:
config:
enableBackstageUi: true
JSX props:
<ScaffolderPage formProps={{ EXPERIMENTAL_theme: 'bui' }} />
8006acf: Promoted FormDecoratorBlueprint and ScaffolderFormDecorator from @alpha
to @public.
d09c21c: The TemplateCard component is now a swappable component. Apps using the new
frontend system can replace it by registering a SwappableComponentBlueprint
that targets TemplateCard. Components used as the swappable implementation
receive TemplateCardComponentProps, where onSelected is a zero-argument
callback bound to the rendered template. Existing usage continues to work
unchanged.
TracingService to provide a unified interface for emitting trace spans across Backstage plugins.TracingService to provide a unified interface for emitting trace spans across Backstage plugins.~30.2.0 to prevent automatic upgrades to Jest 30.4.x, which requires Node.js v24.9+ and breaks tests on Node 22.4bb649d: Fixed Table with row selection creating phantom scroll height on ancestor elements by establishing a containing block for visually-hidden checkbox inputs.
Affected components: Table, TableRoot
d726bcd: Added new DatePicker component — combines a date field and a calendar popover for selecting a date, built on React Aria with full keyboard and screen reader accessibility. Uses BUI design tokens throughout, including auto-incremented backgrounds via the bg consumer pattern.
Affected components: DatePicker
a345820: The app/routes redirect config now supports path parameter substitution in the to target. Named params (:userId) and splat params (*) captured by the from path are replaced in the to string before navigating, making it possible to express redirects like:
app:
extensions:
- app/routes:
config:
redirects:
- from: /users/:userId
to: /profile/:userId
- from: /old-docs
to: /docs/*
Updated dependencies
/catalog/default/component/foo/blob) would silently render the first available route. Unknown paths now show the standard not-found page instead.