diff --git a/.changeset/README.md b/.changeset/README.md
index 6c68425e59..423552ee75 100644
--- a/.changeset/README.md
+++ b/.changeset/README.md
@@ -26,7 +26,7 @@ Optional description with code examples.
Migration instructions (breaking changes only).
-**Affected components:** button, card
+**Affected components:** Button, Card
```
**Required:**
@@ -40,7 +40,7 @@ Migration instructions (breaking changes only).
```markdown
Fixed button hover state
-**Affected components:** button
+**Affected components:** Button
```
````markdown
@@ -56,7 +56,7 @@ Update imports:
```
````
-**Affected components:** table
+**Affected components:** Table
```
diff --git a/.changeset/add-actions-registry-examples.md b/.changeset/add-actions-registry-examples.md
new file mode 100644
index 0000000000..5174bcc68d
--- /dev/null
+++ b/.changeset/add-actions-registry-examples.md
@@ -0,0 +1,7 @@
+---
+'@backstage/backend-plugin-api': minor
+'@backstage/backend-defaults': patch
+'@backstage/backend-test-utils': patch
+---
+
+Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions.
diff --git a/.changeset/add-masked-and-hidden-option.md b/.changeset/add-masked-and-hidden-option.md
deleted file mode 100644
index ad5c810a8e..0000000000
--- a/.changeset/add-masked-and-hidden-option.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend-module-gitlab': patch
----
-
-Added `maskedAndHidden` option to `gitlab:projectVariable:create` and `publish:gitlab` action to support creating GitLab project variables that are both masked and hidden. Updated gitbeaker to version 43.8.0 for proper type support.
diff --git a/.changeset/angry-clouds-tell.md b/.changeset/angry-clouds-tell.md
new file mode 100644
index 0000000000..a01306d84a
--- /dev/null
+++ b/.changeset/angry-clouds-tell.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli-module-new': patch
+---
+
+Rename the legacy `frontend-plugin` to `frontend-plugin-legacy`
diff --git a/.changeset/app-defaults-clarify-failures.md b/.changeset/app-defaults-clarify-failures.md
new file mode 100644
index 0000000000..c6321a3a3a
--- /dev/null
+++ b/.changeset/app-defaults-clarify-failures.md
@@ -0,0 +1,5 @@
+---
+'@backstage/app-defaults': patch
+---
+
+Added `FetchMiddlewares.clarifyFailures()` to the default fetch API middleware stack.
diff --git a/.changeset/app-routes-redirect-config.md b/.changeset/app-routes-redirect-config.md
new file mode 100644
index 0000000000..ce43e3c159
--- /dev/null
+++ b/.changeset/app-routes-redirect-config.md
@@ -0,0 +1,17 @@
+---
+'@backstage/plugin-app': patch
+---
+
+Added support for configuring URL redirects on the `app/routes` extension. Redirects can be configured through `app-config` as an array of `{from, to}` path pairs, which will cause navigation to the `from` path to be redirected to the `to` path.
+
+For example:
+
+```yaml
+app:
+ extensions:
+ - app/routes:
+ config:
+ redirects:
+ - from: /old-path
+ to: /new-path
+```
diff --git a/.changeset/auditor-zod-v4-refactor.md b/.changeset/auditor-zod-v4-refactor.md
new file mode 100644
index 0000000000..c1ac7ce0aa
--- /dev/null
+++ b/.changeset/auditor-zod-v4-refactor.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-defaults': patch
+---
+
+Refactored auditor severity log level mappings to use `zod/v4` with schema-driven defaults and type inference.
diff --git a/.changeset/auth-node-logout-result.md b/.changeset/auth-node-logout-result.md
new file mode 100644
index 0000000000..9eaf842f14
--- /dev/null
+++ b/.changeset/auth-node-logout-result.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-node': patch
+---
+
+Added `OAuthAuthenticatorLogoutResult` type. The `logout` method on `OAuthAuthenticator` can now optionally return `{ logoutUrl }` to trigger a browser redirect after sign-out. This allows providers like Auth0 to clear their session cookies by redirecting to their logout endpoint.
diff --git a/.changeset/auth0-federated-logout.md b/.changeset/auth0-federated-logout.md
new file mode 100644
index 0000000000..83dda17a71
--- /dev/null
+++ b/.changeset/auth0-federated-logout.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-backend-module-auth0-provider': minor
+---
+
+Added federated logout support. Set `federatedLogout: true` in the Auth0 provider config to clear both the Auth0 session and any upstream IdP session on sign-out. The authenticator returns a logout URL that redirects the browser to Auth0's `/v2/logout?federated` endpoint, ensuring users must fully re-authenticate after signing out.
diff --git a/.changeset/azure-scm-events-layer.md b/.changeset/azure-scm-events-layer.md
new file mode 100644
index 0000000000..22ca304f96
--- /dev/null
+++ b/.changeset/azure-scm-events-layer.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-azure': patch
+---
+
+Add Azure DevOps SCM event translation layer for instant catalog reprocessing.
diff --git a/.changeset/blue-moons-crash.md b/.changeset/blue-moons-crash.md
deleted file mode 100644
index bb5ae15c2e..0000000000
--- a/.changeset/blue-moons-crash.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/ui': patch
----
-
-Made Accordion a `bg` provider so nested components like Button auto-increment their background level. Updated `useDefinition` to resolve `bg` `propDef` defaults for provider components.
diff --git a/.changeset/bright-moons-open.md b/.changeset/bright-moons-open.md
deleted file mode 100644
index 2668dfb2e6..0000000000
--- a/.changeset/bright-moons-open.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/plugin-search-react': patch
-'@backstage/plugin-search': patch
----
-
-Fixes the search component not registering the first search on navigate to the search page.
diff --git a/.changeset/brown-towns-find.md b/.changeset/brown-towns-find.md
deleted file mode 100644
index c3244d9427..0000000000
--- a/.changeset/brown-towns-find.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-api-docs': patch
----
-
-Fixed invisible text in parameter input fields when using dark mode in OpenAPI definition pages
diff --git a/.changeset/bump-bfj-v9.md b/.changeset/bump-bfj-v9.md
deleted file mode 100644
index 63309e4657..0000000000
--- a/.changeset/bump-bfj-v9.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli': patch
----
-
-Updated dependency `bfj` to `^9.0.2`.
diff --git a/.changeset/bump-glob-rollup-security.md b/.changeset/bump-glob-rollup-security.md
new file mode 100644
index 0000000000..1738169fd1
--- /dev/null
+++ b/.changeset/bump-glob-rollup-security.md
@@ -0,0 +1,11 @@
+---
+'@backstage/plugin-catalog-backend': patch
+'@backstage/cli-module-auth': patch
+'@backstage/cli-module-build': patch
+'@backstage/cli-module-test-jest': patch
+'@backstage/cli': patch
+'@backstage/ui': patch
+'@backstage/repo-tools': patch
+---
+
+Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc).
diff --git a/.changeset/catalog-entity-page-no-header.md b/.changeset/catalog-entity-page-no-header.md
new file mode 100644
index 0000000000..51cda8f087
--- /dev/null
+++ b/.changeset/catalog-entity-page-no-header.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog': patch
+---
+
+Disabled the default page layout header for the catalog entity page in the new frontend system. The entity page already renders its own header through the `EntityHeader` extension, so the page layout header was redundant.
diff --git a/.changeset/catalog-exists-query-optimization.md b/.changeset/catalog-exists-query-optimization.md
new file mode 100644
index 0000000000..e0eb2cbb31
--- /dev/null
+++ b/.changeset/catalog-exists-query-optimization.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Improved catalog entity filter query performance by switching from `IN (subquery)` to `EXISTS (correlated subquery)` patterns. This enables PostgreSQL semi-join optimizations and fixes `NOT IN` NULL-semantics pitfalls by using `NOT EXISTS` instead.
diff --git a/.changeset/catalog-graph-nfs-page.md b/.changeset/catalog-graph-nfs-page.md
new file mode 100644
index 0000000000..fac437c641
--- /dev/null
+++ b/.changeset/catalog-graph-nfs-page.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-graph': patch
+---
+
+Added `title` and `icon` to the new frontend system plugin definition.
diff --git a/.changeset/catalog-import-nfs-page.md b/.changeset/catalog-import-nfs-page.md
new file mode 100644
index 0000000000..2944565205
--- /dev/null
+++ b/.changeset/catalog-import-nfs-page.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-import': patch
+---
+
+Added `title` and `icon` to the new frontend system plugin definition.
diff --git a/.changeset/catalog-nfs-page-header.md b/.changeset/catalog-nfs-page-header.md
new file mode 100644
index 0000000000..7f94a28cc8
--- /dev/null
+++ b/.changeset/catalog-nfs-page-header.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog': patch
+---
+
+Exported the NFS variant of the catalog index page as `CatalogIndexPage` from the `./alpha` entry point, along with supporting types `CatalogIndexPageProps`, `CatalogTableRow`, and `CatalogTableColumnsFunc`. This allows adopters to use and customize the catalog index page within a `PageBlueprint` in the new frontend system.
diff --git a/.changeset/catalog-search-table-sync.md b/.changeset/catalog-search-table-sync.md
new file mode 100644
index 0000000000..3a3bcea96a
--- /dev/null
+++ b/.changeset/catalog-search-table-sync.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Reduced search table write churn during stitching by syncing only changed rows instead of doing a full delete and re-insert. On Postgres this uses a single writable CTE, on MySQL a temporary table merge with deadlock retry, and on SQLite the previous bulk replace.
diff --git a/.changeset/chatty-cups-create.md b/.changeset/chatty-cups-create.md
new file mode 100644
index 0000000000..0fa05b14f3
--- /dev/null
+++ b/.changeset/chatty-cups-create.md
@@ -0,0 +1,24 @@
+---
+'@backstage/ui': minor
+---
+
+**BREAKING**: The `Header` component's `tabs` prop now uses `HeaderNavTabItem[]` instead of `HeaderTab[]`. Tabs render as a `` element with links and optional dropdown menus instead of `role="tablist"`. A new `activeTabId` prop controls which tab is highlighted.
+
+**Migration:**
+
+```diff
+- import { Header, type HeaderTab } from '@backstage/ui';
++ import { Header, type HeaderNavTabItem } from '@backstage/ui';
+
+ // Tabs no longer support matchStrategy — active state is controlled via activeTabId
+- const tabs: HeaderTab[] = [
+- { id: 'overview', label: 'Overview', href: '/overview', matchStrategy: 'prefix' },
++ const tabs: HeaderNavTabItem[] = [
++ { id: 'overview', label: 'Overview', href: '/overview' },
+ ];
+
+-
++
+```
+
+**Affected components:** Header
diff --git a/.changeset/clarify-fetch-failures.md b/.changeset/clarify-fetch-failures.md
new file mode 100644
index 0000000000..245f790f64
--- /dev/null
+++ b/.changeset/clarify-fetch-failures.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-app-api': minor
+---
+
+Added `FetchMiddlewares.clarifyFailures()` which replaces the uninformative "TypeError: Failed to fetch" with a message that includes the request method and URL.
diff --git a/.changeset/cli-common-cached-paths.md b/.changeset/cli-common-cached-paths.md
deleted file mode 100644
index 6d9ec14297..0000000000
--- a/.changeset/cli-common-cached-paths.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-'@backstage/cli-common': minor
----
-
-Added `targetPaths` and `findOwnPaths` as replacements for `findPaths`, with a cleaner separation between target project paths and package-relative paths.
-
-To migrate existing `findPaths` usage:
-
-```ts
-// Before
-import { findPaths } from '@backstage/cli-common';
-const paths = findPaths(__dirname);
-
-// After — for target project paths (cwd-based):
-import { targetPaths } from '@backstage/cli-common';
-// paths.targetDir → targetPaths.dir
-// paths.targetRoot → targetPaths.rootDir
-// paths.resolveTarget('src') → targetPaths.resolve('src')
-// paths.resolveTargetRoot('yarn.lock') → targetPaths.resolveRoot('yarn.lock')
-
-// After — for package-relative paths:
-import { findOwnPaths } from '@backstage/cli-common';
-const own = findOwnPaths(__dirname);
-// paths.ownDir → own.dir
-// paths.ownRoot → own.rootDir
-// paths.resolveOwn('config/jest.js') → own.resolve('config/jest.js')
-// paths.resolveOwnRoot('tsconfig.json') → own.resolveRoot('tsconfig.json')
-```
diff --git a/.changeset/cli-internal-refactor.md b/.changeset/cli-internal-refactor.md
deleted file mode 100644
index c3f8f38540..0000000000
--- a/.changeset/cli-internal-refactor.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli': patch
----
-
-Internal refactor to use new concurrency utilities from `@backstage/cli-node`.
diff --git a/.changeset/cli-node-parallel-helpers.md b/.changeset/cli-node-parallel-helpers.md
deleted file mode 100644
index 5ad7ad7f4d..0000000000
--- a/.changeset/cli-node-parallel-helpers.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli-node': patch
----
-
-Added `runConcurrentTasks` and `runWorkerQueueThreads` utilities, moved from the `@backstage/cli` internal code.
diff --git a/.changeset/cli-translations-export-import.md b/.changeset/cli-translations-export-import.md
deleted file mode 100644
index 6631eebdfd..0000000000
--- a/.changeset/cli-translations-export-import.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-'@backstage/cli': patch
----
-
-Added `translations export` and `translations import` commands for managing translation files.
-
-The `translations export` command discovers all `TranslationRef` definitions across frontend plugin dependencies and exports their default messages as JSON files. The `translations import` command generates `TranslationResource` wiring code from translated JSON files, ready to be plugged into the app.
-
-Both commands support a `--pattern` option for controlling the message file layout, for example `--pattern '{lang}/{id}.json'` for language-based directory grouping.
diff --git a/.changeset/cold-bikes-beam.md b/.changeset/cold-bikes-beam.md
new file mode 100644
index 0000000000..5a1c5ae7c5
--- /dev/null
+++ b/.changeset/cold-bikes-beam.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': minor
+---
+
+Added a new `execute-template` actions registry action that executes a scaffolder template with provided input values and returns a task ID for tracking progress.
diff --git a/.changeset/cool-shoes-hide.md b/.changeset/cool-shoes-hide.md
new file mode 100644
index 0000000000..ef7da1597c
--- /dev/null
+++ b/.changeset/cool-shoes-hide.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Added RangeSlider component for selecting numeric ranges.
+
+**Affected components:** RangeSlider
diff --git a/.changeset/core-app-api-logout-redirect.md b/.changeset/core-app-api-logout-redirect.md
new file mode 100644
index 0000000000..63d9b6fa0c
--- /dev/null
+++ b/.changeset/core-app-api-logout-redirect.md
@@ -0,0 +1,6 @@
+---
+'@backstage/core-app-api': patch
+'@backstage/plugin-app': patch
+---
+
+The default auth implementation now checks for a `logoutUrl` in the logout response body. If the auth provider returns one (e.g. Auth0 federated logout), the browser is redirected to that URL to clear the provider's session cookies. This is backward compatible — providers that return an empty response are unaffected.
diff --git a/.changeset/create-app-1774970958.md b/.changeset/create-app-1774970958.md
new file mode 100644
index 0000000000..b50d431d4b
--- /dev/null
+++ b/.changeset/create-app-1774970958.md
@@ -0,0 +1,5 @@
+---
+'@backstage/create-app': patch
+---
+
+Bumped create-app version.
diff --git a/.changeset/dependabot-03b295a.md b/.changeset/dependabot-03b295a.md
new file mode 100644
index 0000000000..5012e22728
--- /dev/null
+++ b/.changeset/dependabot-03b295a.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-react': patch
+---
+
+build(deps): bump `flatted` from 3.4.1 to 3.4.2
diff --git a/.changeset/dependabot-2a735a0.md b/.changeset/dependabot-2a735a0.md
new file mode 100644
index 0000000000..87264f89ec
--- /dev/null
+++ b/.changeset/dependabot-2a735a0.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-notifications-backend-module-email': patch
+---
+
+build(deps): bump `nodemailer` from 7.0.13 to 8.0.4
diff --git a/.changeset/dependabot-cc35625.md b/.changeset/dependabot-cc35625.md
new file mode 100644
index 0000000000..b6dd72c5ee
--- /dev/null
+++ b/.changeset/dependabot-cc35625.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli-common': patch
+---
+
+chore(deps): bump `undici` from 7.22.0 to 7.24.0
diff --git a/.changeset/dependabot-d2ec7e9.md b/.changeset/dependabot-d2ec7e9.md
deleted file mode 100644
index efa4ec5510..0000000000
--- a/.changeset/dependabot-d2ec7e9.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-'@backstage/backend-defaults': patch
-'@backstage/cli': patch
-'@backstage/eslint-plugin': patch
-'@backstage/repo-tools': patch
-'@backstage/plugin-auth-backend': patch
-'@backstage/plugin-catalog-backend-module-github': patch
-'@backstage/plugin-catalog-backend': patch
----
-
-build(deps): bump `minimatch` from 9.0.5 to 10.2.1
diff --git a/.changeset/drop-react-17-packages-ui.md b/.changeset/drop-react-17-packages-ui.md
new file mode 100644
index 0000000000..710f6e5b42
--- /dev/null
+++ b/.changeset/drop-react-17-packages-ui.md
@@ -0,0 +1,5 @@
+---
+'@backstage/ui': minor
+---
+
+**BREAKING**: Dropped support for React 17. The minimum supported React version is now 18.
diff --git a/.changeset/easy-pens-judge.md b/.changeset/easy-pens-judge.md
new file mode 100644
index 0000000000..fe9dde7665
--- /dev/null
+++ b/.changeset/easy-pens-judge.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Made `SearchAutocomplete` background-aware. The input now adapts its background color based on its parent container's background level.
+
+**Affected components:** SearchAutocomplete
diff --git a/.changeset/evil-seals-smell.md b/.changeset/evil-seals-smell.md
new file mode 100644
index 0000000000..a533720434
--- /dev/null
+++ b/.changeset/evil-seals-smell.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Migrated OpenTelemetry metrics to use the `MetricsService` from `@backstage/backend-plugin-api/alpha` instead of the raw `@opentelemetry/api` meter.
diff --git a/.changeset/fancy-ends-turn.md b/.changeset/fancy-ends-turn.md
deleted file mode 100644
index 7787b47971..0000000000
--- a/.changeset/fancy-ends-turn.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-notifications-backend-module-slack': minor
----
-
-Add an extension for custom Slack message layouts
diff --git a/.changeset/fix-avatar-flex-shrink.md b/.changeset/fix-avatar-flex-shrink.md
new file mode 100644
index 0000000000..161a2327b6
--- /dev/null
+++ b/.changeset/fix-avatar-flex-shrink.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Fixed `Avatar` becoming elliptical in flex layouts by preventing it from shrinking.
+
+**Affected components:** Avatar
diff --git a/.changeset/fix-aws-s3-url-reader-custom-endpoints.md b/.changeset/fix-aws-s3-url-reader-custom-endpoints.md
new file mode 100644
index 0000000000..c9fe3923ba
--- /dev/null
+++ b/.changeset/fix-aws-s3-url-reader-custom-endpoints.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-defaults': patch
+---
+
+Fixed `AwsS3UrlReader` failing to read files from S3 buckets configured with custom endpoint hosts. When an integration was configured with a specific endpoint like `https://bucket-1.s3.eu-central-1.amazonaws.com`, the URL parser incorrectly fell through to the non-AWS code path, always defaulting the region to `us-east-1` instead of extracting it from the hostname.
diff --git a/.changeset/fix-bui-relative-href-resolution.md b/.changeset/fix-bui-relative-href-resolution.md
new file mode 100644
index 0000000000..62d5b4ada2
--- /dev/null
+++ b/.changeset/fix-bui-relative-href-resolution.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Fixed relative `href` resolution for BUI link components. Relative paths like `../other` are now correctly turned into absolute paths before reaching the React Aria layer, ensuring client-side navigation goes to the right place.
+
+**Affected components:** ButtonLink, Card, CellProfile, CellText, Link, ListRow, MenuItem, MenuListBoxItem, Row, SearchAutocompleteItem, Tab, Tag
diff --git a/.changeset/fix-catalog-filter-flicker.md b/.changeset/fix-catalog-filter-flicker.md
new file mode 100644
index 0000000000..5964f7ada9
--- /dev/null
+++ b/.changeset/fix-catalog-filter-flicker.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-react': patch
+---
+
+Fixed a UI flicker in the catalog entity list where changing a filter would briefly flash stale data before showing the new results.
diff --git a/.changeset/fix-catalog-refresh-state-deadlock.md b/.changeset/fix-catalog-refresh-state-deadlock.md
new file mode 100644
index 0000000000..867bafce49
--- /dev/null
+++ b/.changeset/fix-catalog-refresh-state-deadlock.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Fixed a deadlock in the catalog processing loop that occurred when running multiple replicas. The `getProcessableEntities` method used `SELECT ... FOR UPDATE SKIP LOCKED` to prevent concurrent processors from claiming the same rows, but the call was not wrapped in a transaction, so the row locks were released before the subsequent `UPDATE` executed. This allowed multiple replicas to select and update overlapping rows, causing PostgreSQL deadlock errors (code 40P01).
diff --git a/.changeset/fix-catalog-table-loading-flash.md b/.changeset/fix-catalog-table-loading-flash.md
new file mode 100644
index 0000000000..1e75a6d7af
--- /dev/null
+++ b/.changeset/fix-catalog-table-loading-flash.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog': patch
+---
+
+Fixed the catalog table briefly showing an empty loading state when changing filters. The table now keeps displaying stale results until new data arrives.
diff --git a/.changeset/fix-entity-info-card-header-overflow.md b/.changeset/fix-entity-info-card-header-overflow.md
new file mode 100644
index 0000000000..e40a6149fa
--- /dev/null
+++ b/.changeset/fix-entity-info-card-header-overflow.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-react': patch
+---
+
+Fixed `EntityInfoCard` header overflowing on narrow screens.
diff --git a/.changeset/fix-frontend-feature-compat.md b/.changeset/fix-frontend-feature-compat.md
deleted file mode 100644
index ced9237285..0000000000
--- a/.changeset/fix-frontend-feature-compat.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/frontend-plugin-api': patch
----
-
-Made the `pluginId` property optional in the `FrontendFeature` type, allowing plugins published against older versions of the framework to be used without type errors.
diff --git a/.changeset/fix-org-profile-card-overflow.md b/.changeset/fix-org-profile-card-overflow.md
new file mode 100644
index 0000000000..21ec67b428
--- /dev/null
+++ b/.changeset/fix-org-profile-card-overflow.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-org': patch
+---
+
+Fixed `GroupProfileCard` and `UserProfileCard` content overflowing on narrow screens.
diff --git a/.changeset/fix-prettier-existence-check.md b/.changeset/fix-prettier-existence-check.md
deleted file mode 100644
index 96e8b79082..0000000000
--- a/.changeset/fix-prettier-existence-check.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/repo-tools': patch
----
-
-Fixed prettier existence checks in OpenAPI commands to use `fs.pathExists` instead of checking the resolved path string, which was always truthy.
diff --git a/.changeset/fix-sidebar-settings-nav.md b/.changeset/fix-sidebar-settings-nav.md
new file mode 100644
index 0000000000..ee8223e5f4
--- /dev/null
+++ b/.changeset/fix-sidebar-settings-nav.md
@@ -0,0 +1,5 @@
+---
+'@backstage/create-app': patch
+---
+
+Various fixes for the `create-app` template: reorganizing sidebar items, removing redundant config and code, and adding a documentation example.
diff --git a/.changeset/fix-translation-ref-ts6.md b/.changeset/fix-translation-ref-ts6.md
new file mode 100644
index 0000000000..1662d6c7ea
--- /dev/null
+++ b/.changeset/fix-translation-ref-ts6.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-plugin-api': patch
+---
+
+Fixed `FlattenedMessages` type to avoid excessive type instantiation depth in TypeScript 6 when using `createTranslationRef` with the `translations` option.
diff --git a/.changeset/fix-zod-generic-auth-node.md b/.changeset/fix-zod-generic-auth-node.md
new file mode 100644
index 0000000000..d8ef7e4931
--- /dev/null
+++ b/.changeset/fix-zod-generic-auth-node.md
@@ -0,0 +1,26 @@
+---
+'@backstage/plugin-auth-node': minor
+---
+
+**BREAKING**: Refactored `SignInResolverFactoryOptions` to use a schema-first generic pattern, following Zod's [recommended approach](https://zod.dev/library-authors?id=how-to-accept-user-defined-schemas#how-to-accept-user-defined-schemas) for writing generic functions. The type parameters changed from `` to ``.
+
+This fixes "Type instantiation is excessively deep and possibly infinite" errors that occurred when the Zod version in a user's project did not align with the one in Backstage core.
+
+If you use `createSignInResolverFactory` without explicit type parameters (the typical usage), no changes are needed:
+
+```ts
+// This usage is unchanged
+createSignInResolverFactory({
+ optionsSchema: z.object({ domain: z.string() }).optional(),
+ create(options = {}) {
+ /* ... */
+ },
+});
+```
+
+If you reference `SignInResolverFactoryOptions` with explicit type parameters, update as follows:
+
+```diff
+- SignInResolverFactoryOptions
++ SignInResolverFactoryOptions
+```
diff --git a/.changeset/fix-zod-generic-frontend-plugin-api.md b/.changeset/fix-zod-generic-frontend-plugin-api.md
new file mode 100644
index 0000000000..8950ed70c9
--- /dev/null
+++ b/.changeset/fix-zod-generic-frontend-plugin-api.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-plugin-api': patch
+---
+
+Refactored the internal `createSchemaFromZod` helper to use a schema-first generic pattern, replacing the `ZodSchema` constraint with `TSchema extends ZodType`. This avoids "excessively deep" type inference errors when multiple Zod copies are resolved.
diff --git a/.changeset/fluffy-owls-act.md b/.changeset/fluffy-owls-act.md
deleted file mode 100644
index 03ae42fd99..0000000000
--- a/.changeset/fluffy-owls-act.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/backend-defaults': patch
-'@backstage/integration': minor
-'@backstage/plugin-catalog-backend-module-gitlab': patch
----
-
-Add configurable throttling and retry mechanism for GitLab integration.
diff --git a/.changeset/frontend-app-api-permission-error.md b/.changeset/frontend-app-api-permission-error.md
new file mode 100644
index 0000000000..851304fcf2
--- /dev/null
+++ b/.changeset/frontend-app-api-permission-error.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-app-api': patch
+---
+
+Wrapped extension permission authorization in a try/catch to surface errors as `ForwardedError` with a clear message.
diff --git a/.changeset/funny-items-wink.md b/.changeset/funny-items-wink.md
new file mode 100644
index 0000000000..3c6c2b1644
--- /dev/null
+++ b/.changeset/funny-items-wink.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli-module-test-jest': patch
+---
+
+Minor error message update
diff --git a/.changeset/fuzzy-mangos-design.md b/.changeset/fuzzy-mangos-design.md
new file mode 100644
index 0000000000..be4a33566f
--- /dev/null
+++ b/.changeset/fuzzy-mangos-design.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs': patch
+---
+
+Migrated the TechDocs alpha plugin pages to use BUI components. The index page and reader page now use BUI `Header` and `Container` instead of legacy `Page`/`Content`/`ContentHeader` wrappers. Added a `SupportButton` as a plugin header action. Changed plugin title to "Documentation" and icon to `RiArticleLine`.
diff --git a/.changeset/gitlab-scm-events-layer.md b/.changeset/gitlab-scm-events-layer.md
new file mode 100644
index 0000000000..24349d01b1
--- /dev/null
+++ b/.changeset/gitlab-scm-events-layer.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-gitlab': patch
+---
+
+Add GitLab SCM event translation layer for instant catalog reprocessing.
diff --git a/.changeset/happy-masks-allow.md b/.changeset/happy-masks-allow.md
new file mode 100644
index 0000000000..56d9b04e91
--- /dev/null
+++ b/.changeset/happy-masks-allow.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Added `aria-hidden` to the `PluginHeader` icon to prevent screen readers from announcing decorative plugin icons.
+
+**Affected components:** PluginHeader
diff --git a/.changeset/hide-compat-wrapper-headers.md b/.changeset/hide-compat-wrapper-headers.md
new file mode 100644
index 0000000000..78b8f869d9
--- /dev/null
+++ b/.changeset/hide-compat-wrapper-headers.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-compat-api': patch
+---
+
+Hide the default page header for pages created through the compatibility wrappers, since legacy plugins already render their own headers.
diff --git a/.changeset/hip-parents-fall.md b/.changeset/hip-parents-fall.md
new file mode 100644
index 0000000000..2beddba466
--- /dev/null
+++ b/.changeset/hip-parents-fall.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Added support for disabling pagination in `useTable` complete mode by setting `paginationOptions: { type: 'none' }`. This skips data slicing and produces `pagination: { type: 'none' }` in `tableProps`, removing the need for consumers to manually override the pagination prop on `Table`. Also fixed complete mode not reacting to dynamic changes in `paginationOptions.pageSize`.
+
+**Affected components:** `useTable`
diff --git a/.changeset/hot-yaks-crash.md b/.changeset/hot-yaks-crash.md
new file mode 100644
index 0000000000..eb56752374
--- /dev/null
+++ b/.changeset/hot-yaks-crash.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-app': patch
+---
+
+Updated the `PageLayout` swap to pass a clickable `titleLink` on the `PluginHeader`, resolved from the plugin's root route ref.
diff --git a/.changeset/kubernetes-nfs-metadata.md b/.changeset/kubernetes-nfs-metadata.md
new file mode 100644
index 0000000000..fb80bdb1a1
--- /dev/null
+++ b/.changeset/kubernetes-nfs-metadata.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-kubernetes': patch
+---
+
+Added `title` and `icon` to the new frontend system plugin definition.
diff --git a/.changeset/lazy-jars-shake.md b/.changeset/lazy-jars-shake.md
new file mode 100644
index 0000000000..f63191f1bf
--- /dev/null
+++ b/.changeset/lazy-jars-shake.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Resolved DOM nesting warning in OAuthRequestDialog by rendering secondary text as block-level spans.
diff --git a/.changeset/long-hairs-throw.md b/.changeset/long-hairs-throw.md
deleted file mode 100644
index a00b24fda3..0000000000
--- a/.changeset/long-hairs-throw.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder': patch
----
-
-Added a new `ui:autoSelect` option to the EntityPicker field that controls whether an entity is automatically selected when the field loses focus. When set to `false`, the field will remain empty if the user closes it without explicitly selecting an entity, preventing unintentional selections. Defaults to `true` for backward compatibility.
diff --git a/.changeset/loopback-redirect-uri-port.md b/.changeset/loopback-redirect-uri-port.md
new file mode 100644
index 0000000000..e2f4d1b725
--- /dev/null
+++ b/.changeset/loopback-redirect-uri-port.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-backend': patch
+---
+
+Fixed CIMD redirect URI matching to allow any port for localhost addresses per RFC 8252 Section 7.3. Native CLI clients use ephemeral ports for OAuth callbacks, which are now accepted when the registered redirect URI uses a localhost address.
diff --git a/.changeset/lucky-adults-talk.md b/.changeset/lucky-adults-talk.md
new file mode 100644
index 0000000000..66e30af2dc
--- /dev/null
+++ b/.changeset/lucky-adults-talk.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog': patch
+---
+
+Added Kind field to the About Card. Tags moved before Type and Lifecycle, Kind placed after them. A new `aboutCard.kindField.label` translation key was added.
diff --git a/.changeset/lucky-things-write.md b/.changeset/lucky-things-write.md
new file mode 100644
index 0000000000..36df2d7308
--- /dev/null
+++ b/.changeset/lucky-things-write.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-plugin-api': patch
+---
+
+Added `titleLink` prop to `PageLayoutProps` so the plugin header title can link back to the plugin root.
diff --git a/.changeset/mean-fans-decide.md b/.changeset/mean-fans-decide.md
deleted file mode 100644
index 5fe04beb40..0000000000
--- a/.changeset/mean-fans-decide.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/ui': patch
----
-
-Fixed focus-visible outline styles for Menu and Select components.
-
-**Affected components:** Menu, Select
diff --git a/.changeset/metal-humans-move.md b/.changeset/metal-humans-move.md
deleted file mode 100644
index 21881246fc..0000000000
--- a/.changeset/metal-humans-move.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-catalog-backend': patch
----
-
-Minor internal optimisation
diff --git a/.changeset/migrate-to-target-paths.md b/.changeset/migrate-to-target-paths.md
deleted file mode 100644
index efc03ba289..0000000000
--- a/.changeset/migrate-to-target-paths.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-'@backstage/cli': patch
-'@backstage/cli-node': patch
-'@backstage/backend-dynamic-feature-service': patch
-'@backstage/codemods': patch
-'@backstage/config-loader': patch
-'@backstage/create-app': patch
-'@backstage/repo-tools': patch
-'@techdocs/cli': patch
----
-
-Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
diff --git a/.changeset/nine-signs-end.md b/.changeset/nine-signs-end.md
new file mode 100644
index 0000000000..544b0bb231
--- /dev/null
+++ b/.changeset/nine-signs-end.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-graph': patch
+---
+
+Support configuring `showArrowHeads` on `page:catalog-graph` and `entity-card:catalog-graph/relations`.
diff --git a/.changeset/ninety-corners-flash.md b/.changeset/ninety-corners-flash.md
deleted file mode 100644
index f858ec0593..0000000000
--- a/.changeset/ninety-corners-flash.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-catalog-backend': patch
----
-
-Migrates existing catalog metrics to use the alpha MetricsService. This release is a 1:1 migration with no breaking changes.
diff --git a/.changeset/notifications-nfs-metadata.md b/.changeset/notifications-nfs-metadata.md
new file mode 100644
index 0000000000..10ee3bbd8c
--- /dev/null
+++ b/.changeset/notifications-nfs-metadata.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-notifications': patch
+---
+
+Added `title` and `icon` to the new frontend system plugin definition.
diff --git a/.changeset/olive-peaches-fly.md b/.changeset/olive-peaches-fly.md
new file mode 100644
index 0000000000..58dd3250df
--- /dev/null
+++ b/.changeset/olive-peaches-fly.md
@@ -0,0 +1,8 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Fix the display of the description in `GitlabRepoPicker`:
+
+- Move `owner.description` helper text outside the `allowedOwners` conditional so it renders for both `Select` and `Autocomplete` modes.
+- Update the `Autocomplete` label to use `fields.gitlabRepoPicker.owner.inputTitle` instead of `fields.gitlabRepoPicker.owner.title`.
diff --git a/.changeset/olive-ravens-smell.md b/.changeset/olive-ravens-smell.md
new file mode 100644
index 0000000000..4ae0626d46
--- /dev/null
+++ b/.changeset/olive-ravens-smell.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Added `prefers-reduced-motion` support to Tab indicator animations. Users with reduced motion preferences will no longer see sliding transitions on the active and hover indicators.
+
+**Affected components:** Tabs
diff --git a/.changeset/orange-friends-march.md b/.changeset/orange-friends-march.md
new file mode 100644
index 0000000000..d3813063e2
--- /dev/null
+++ b/.changeset/orange-friends-march.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-search': patch
+---
+
+Fixed the `SearchModal` leaving the page in a broken state by not restoring body overflow and aria-hidden attributes when closing.
diff --git a/.changeset/orange-mugs-post-1.md b/.changeset/orange-mugs-post-1.md
deleted file mode 100644
index bbf2b51082..0000000000
--- a/.changeset/orange-mugs-post-1.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/plugin-catalog-backend': minor
----
-
-Added opentelemetry metrics for SCM events:
-
-- `catalog.events.scm.messages` with attribute `eventType`: Counter for the number of SCM events actually received by the catalog backend. The `eventType` is currently either `location` or `repository`.
diff --git a/.changeset/orange-mugs-post-2.md b/.changeset/orange-mugs-post-2.md
deleted file mode 100644
index ed78bbc175..0000000000
--- a/.changeset/orange-mugs-post-2.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/plugin-catalog-node': minor
----
-
-Added the ability for SCM events subscribers to mark the fact that they have taken actions based on events, which produces output metrics:
-
-- `catalog.events.scm.actions` with attribute `action`: Counter for the number of actions actually taken by catalog internals or other subscribers, based on SCM events. The `action` is currently either `create`, `delete`, `refresh`, or `move`.
diff --git a/.changeset/org-nfs-metadata.md b/.changeset/org-nfs-metadata.md
new file mode 100644
index 0000000000..759cffbd30
--- /dev/null
+++ b/.changeset/org-nfs-metadata.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-org': patch
+---
+
+Added `title` and `icon` to the new frontend system plugin definition.
diff --git a/.changeset/parallelize-cli-reports.md b/.changeset/parallelize-cli-reports.md
new file mode 100644
index 0000000000..aa3363ff38
--- /dev/null
+++ b/.changeset/parallelize-cli-reports.md
@@ -0,0 +1,5 @@
+---
+'@backstage/repo-tools': patch
+---
+
+Parallelized CLI report generation, reducing wall-clock time by ~4x.
diff --git a/.changeset/pink-terms-know.md b/.changeset/pink-terms-know.md
deleted file mode 100644
index ca64a76f39..0000000000
--- a/.changeset/pink-terms-know.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-'@backstage/cli': patch
-'@backstage/codemods': patch
-'@backstage/create-app': patch
-'@backstage/repo-tools': patch
-'@techdocs/cli': patch
----
-
-Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages.
diff --git a/.changeset/plugin-header-remove-toolbar-wrapper.md b/.changeset/plugin-header-remove-toolbar-wrapper.md
new file mode 100644
index 0000000000..a68bb1dfdd
--- /dev/null
+++ b/.changeset/plugin-header-remove-toolbar-wrapper.md
@@ -0,0 +1,9 @@
+---
+'@backstage/ui': minor
+---
+
+**BREAKING**: Removed the `toolbarWrapper` element from `PluginHeader` and dropped `toolbarWrapper` from `PluginHeaderDefinition.classNames`. Toolbar layout styles now live on `toolbar` (`.bui-PluginHeaderToolbar`).
+
+**Migration:** Update custom CSS that targeted `.bui-PluginHeaderToolbarWrapper` to use `.bui-PluginHeaderToolbar` instead.
+
+**Affected components:** PluginHeader
diff --git a/.changeset/polite-singers-lead.md b/.changeset/polite-singers-lead.md
deleted file mode 100644
index 0aa775e4e2..0000000000
--- a/.changeset/polite-singers-lead.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend': patch
----
-
-Removed unused dependencies
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 00dbb0f757..c87c88cf1b 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -2,213 +2,307 @@
"mode": "pre",
"tag": "next",
"initialVersions": {
- "example-app": "0.0.32",
- "@backstage/app-defaults": "1.7.5",
- "app-example-plugin": "0.0.32",
- "example-app-legacy": "0.2.118",
- "example-backend": "0.0.47",
- "@backstage/backend-app-api": "1.5.0",
- "@backstage/backend-defaults": "0.15.2",
+ "example-app": "0.0.33",
+ "@backstage/app-defaults": "1.7.6",
+ "app-example-plugin": "0.0.33",
+ "example-app-legacy": "0.2.119",
+ "example-backend": "0.0.48",
+ "@backstage/backend-app-api": "1.6.0",
+ "@backstage/backend-defaults": "0.16.0",
"@backstage/backend-dev-utils": "0.1.7",
- "@backstage/backend-dynamic-feature-service": "0.7.9",
- "@backstage/backend-openapi-utils": "0.6.6",
- "@backstage/backend-plugin-api": "1.7.0",
- "@backstage/backend-test-utils": "1.11.0",
- "@backstage/catalog-client": "1.13.0",
- "@backstage/catalog-model": "1.7.6",
- "@backstage/cli": "0.35.4",
- "@backstage/cli-common": "0.1.18",
- "@backstage/cli-node": "0.2.18",
- "@backstage/codemods": "0.1.54",
+ "@backstage/backend-dynamic-feature-service": "0.8.0",
+ "@backstage/backend-openapi-utils": "0.6.7",
+ "@backstage/backend-plugin-api": "1.8.0",
+ "@backstage/backend-test-utils": "1.11.1",
+ "@backstage/catalog-client": "1.14.0",
+ "@backstage/catalog-model": "1.7.7",
+ "@backstage/cli": "0.36.0",
+ "@backstage/cli-common": "0.2.0",
+ "@backstage/cli-defaults": "0.1.0",
+ "@internal/cli": "0.0.2",
+ "@backstage/cli-module-actions": "0.0.1",
+ "@backstage/cli-module-auth": "0.1.0",
+ "@backstage/cli-module-build": "0.1.0",
+ "@backstage/cli-module-config": "0.1.0",
+ "@backstage/cli-module-github": "0.1.0",
+ "@backstage/cli-module-info": "0.1.0",
+ "@backstage/cli-module-lint": "0.1.0",
+ "@backstage/cli-module-maintenance": "0.1.0",
+ "@backstage/cli-module-migrate": "0.1.0",
+ "@backstage/cli-module-new": "0.1.0",
+ "@backstage/cli-module-test-jest": "0.1.0",
+ "@backstage/cli-module-translations": "0.1.0",
+ "@backstage/cli-node": "0.3.0",
+ "@backstage/codemods": "0.1.55",
"@backstage/config": "1.3.6",
- "@backstage/config-loader": "1.10.8",
- "@backstage/core-app-api": "1.19.5",
- "@backstage/core-compat-api": "0.5.8",
- "@backstage/core-components": "0.18.7",
- "@backstage/core-plugin-api": "1.12.3",
- "@backstage/create-app": "0.7.9",
- "@backstage/dev-utils": "1.1.20",
- "e2e-test": "0.2.37",
+ "@backstage/config-loader": "1.10.9",
+ "@backstage/core-app-api": "1.19.6",
+ "@backstage/core-compat-api": "0.5.9",
+ "@backstage/core-components": "0.18.8",
+ "@backstage/core-plugin-api": "1.12.4",
+ "@backstage/create-app": "0.8.0",
+ "@backstage/dev-utils": "1.1.21",
+ "e2e-test": "0.2.38",
"@backstage/e2e-test-utils": "0.1.2",
"@backstage/errors": "1.2.7",
- "@backstage/eslint-plugin": "0.2.1",
- "@backstage/filter-predicates": "0.1.0",
- "@backstage/frontend-app-api": "0.15.0",
- "@backstage/frontend-defaults": "0.4.0",
- "@backstage/frontend-dynamic-feature-loader": "0.1.9",
- "@internal/frontend": "0.0.17",
- "@backstage/frontend-plugin-api": "0.14.0",
- "@backstage/frontend-test-utils": "0.5.0",
- "@backstage/integration": "1.20.0",
+ "@backstage/eslint-plugin": "0.2.2",
+ "@backstage/filter-predicates": "0.1.1",
+ "@backstage/frontend-app-api": "0.16.0",
+ "@backstage/frontend-defaults": "0.5.0",
+ "@backstage/frontend-dev-utils": "0.1.0",
+ "@backstage/frontend-dynamic-feature-loader": "0.1.10",
+ "@internal/frontend": "0.0.18",
+ "@backstage/frontend-plugin-api": "0.15.0",
+ "@backstage/frontend-test-utils": "0.5.1",
+ "@backstage/integration": "2.0.0",
"@backstage/integration-aws-node": "0.1.20",
- "@backstage/integration-react": "1.2.15",
- "@backstage/module-federation-common": "0.1.0",
+ "@backstage/integration-react": "1.2.16",
+ "@backstage/module-federation-common": "0.1.2",
"@internal/opaque": "0.0.1",
"@backstage/release-manifests": "0.0.13",
- "@backstage/repo-tools": "0.16.4",
- "@internal/scaffolder": "0.0.18",
- "@techdocs/cli": "1.10.5",
- "techdocs-cli-embedded-app": "0.2.117",
- "@backstage/test-utils": "1.7.15",
+ "@backstage/repo-tools": "0.17.0",
+ "@internal/scaffolder": "0.0.19",
+ "@techdocs/cli": "1.10.6",
+ "techdocs-cli-embedded-app": "0.2.118",
+ "@backstage/test-utils": "1.7.16",
"@backstage/theme": "0.7.2",
"@backstage/types": "1.2.2",
- "@backstage/ui": "0.12.0",
+ "@backstage/ui": "0.13.0",
"@backstage/version-bridge": "1.0.12",
- "yarn-plugin-backstage": "0.0.9",
- "@backstage/plugin-api-docs": "0.13.4",
+ "yarn-plugin-backstage": "0.0.10",
+ "@backstage/plugin-api-docs": "0.13.5",
"@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.11",
- "@backstage/plugin-app": "0.4.0",
- "@backstage/plugin-app-backend": "0.5.11",
- "@backstage/plugin-app-node": "0.1.42",
- "@backstage/plugin-app-react": "0.2.0",
- "@backstage/plugin-app-visualizer": "0.2.0",
- "@backstage/plugin-auth": "0.1.5",
- "@backstage/plugin-auth-backend": "0.27.0",
- "@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.12",
- "@backstage/plugin-auth-backend-module-auth0-provider": "0.3.0",
- "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.13",
- "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.17",
- "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.12",
- "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.12",
- "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.12",
- "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.12",
- "@backstage/plugin-auth-backend-module-github-provider": "0.5.0",
- "@backstage/plugin-auth-backend-module-gitlab-provider": "0.4.0",
- "@backstage/plugin-auth-backend-module-google-provider": "0.3.12",
- "@backstage/plugin-auth-backend-module-guest-provider": "0.2.16",
- "@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.12",
- "@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.12",
- "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.17",
- "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.13",
- "@backstage/plugin-auth-backend-module-okta-provider": "0.2.12",
- "@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.12",
- "@backstage/plugin-auth-backend-module-openshift-provider": "0.1.4",
- "@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.11",
- "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.11",
- "@backstage/plugin-auth-node": "0.6.13",
- "@backstage/plugin-auth-react": "0.1.24",
- "@backstage/plugin-bitbucket-cloud-common": "0.3.7",
- "@backstage/plugin-catalog": "1.33.0",
- "@backstage/plugin-catalog-backend": "3.4.0",
- "@backstage/plugin-catalog-backend-module-aws": "0.4.20",
- "@backstage/plugin-catalog-backend-module-azure": "0.3.14",
- "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.11",
- "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.8",
- "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.8",
- "@backstage/plugin-catalog-backend-module-gcp": "0.3.16",
- "@backstage/plugin-catalog-backend-module-gerrit": "0.3.11",
- "@backstage/plugin-catalog-backend-module-gitea": "0.1.9",
- "@backstage/plugin-catalog-backend-module-github": "0.12.2",
- "@backstage/plugin-catalog-backend-module-github-org": "0.3.19",
- "@backstage/plugin-catalog-backend-module-gitlab": "0.8.0",
- "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.18",
- "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.9",
- "@backstage/plugin-catalog-backend-module-ldap": "0.12.2",
- "@backstage/plugin-catalog-backend-module-logs": "0.1.19",
- "@backstage/plugin-catalog-backend-module-msgraph": "0.9.0",
- "@backstage/plugin-catalog-backend-module-openapi": "0.2.19",
- "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.19",
- "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.17",
- "@backstage/plugin-catalog-backend-module-unprocessed": "0.6.8",
+ "@backstage/plugin-app": "0.4.1",
+ "@backstage/plugin-app-backend": "0.5.12",
+ "@backstage/plugin-app-node": "0.1.43",
+ "@backstage/plugin-app-react": "0.2.1",
+ "@backstage/plugin-app-visualizer": "0.2.1",
+ "@backstage/plugin-auth": "0.1.6",
+ "@backstage/plugin-auth-backend": "0.27.2",
+ "@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.13",
+ "@backstage/plugin-auth-backend-module-auth0-provider": "0.3.1",
+ "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.14",
+ "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.18",
+ "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.13",
+ "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.13",
+ "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.13",
+ "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.13",
+ "@backstage/plugin-auth-backend-module-github-provider": "0.5.1",
+ "@backstage/plugin-auth-backend-module-gitlab-provider": "0.4.1",
+ "@backstage/plugin-auth-backend-module-google-provider": "0.3.13",
+ "@backstage/plugin-auth-backend-module-guest-provider": "0.2.17",
+ "@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.13",
+ "@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.13",
+ "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.18",
+ "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.14",
+ "@backstage/plugin-auth-backend-module-okta-provider": "0.2.13",
+ "@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.13",
+ "@backstage/plugin-auth-backend-module-openshift-provider": "0.1.5",
+ "@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.12",
+ "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.12",
+ "@backstage/plugin-auth-node": "0.6.14",
+ "@backstage/plugin-auth-react": "0.1.25",
+ "@backstage/plugin-bitbucket-cloud-common": "0.3.8",
+ "@backstage/plugin-catalog": "2.0.0",
+ "@backstage/plugin-catalog-backend": "3.5.0",
+ "@backstage/plugin-catalog-backend-module-aws": "0.4.21",
+ "@backstage/plugin-catalog-backend-module-azure": "0.3.15",
+ "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.12",
+ "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.9",
+ "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.9",
+ "@backstage/plugin-catalog-backend-module-gcp": "0.3.17",
+ "@backstage/plugin-catalog-backend-module-gerrit": "0.3.12",
+ "@backstage/plugin-catalog-backend-module-gitea": "0.1.10",
+ "@backstage/plugin-catalog-backend-module-github": "0.13.0",
+ "@backstage/plugin-catalog-backend-module-github-org": "0.3.20",
+ "@backstage/plugin-catalog-backend-module-gitlab": "0.8.1",
+ "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.19",
+ "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.10",
+ "@backstage/plugin-catalog-backend-module-ldap": "0.12.3",
+ "@backstage/plugin-catalog-backend-module-logs": "0.1.20",
+ "@backstage/plugin-catalog-backend-module-msgraph": "0.9.1",
+ "@backstage/plugin-catalog-backend-module-openapi": "0.2.20",
+ "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.20",
+ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.18",
+ "@backstage/plugin-catalog-backend-module-unprocessed": "0.6.9",
"@backstage/plugin-catalog-common": "1.1.8",
- "@backstage/plugin-catalog-graph": "0.5.7",
- "@backstage/plugin-catalog-import": "0.13.10",
- "@backstage/plugin-catalog-node": "2.0.0",
- "@backstage/plugin-catalog-react": "2.0.0",
- "@backstage/plugin-catalog-unprocessed-entities": "0.2.26",
+ "@backstage/plugin-catalog-graph": "0.6.0",
+ "@backstage/plugin-catalog-import": "0.13.11",
+ "@backstage/plugin-catalog-node": "2.1.0",
+ "@backstage/plugin-catalog-react": "2.1.0",
+ "@backstage/plugin-catalog-unprocessed-entities": "0.2.27",
"@backstage/plugin-catalog-unprocessed-entities-common": "0.0.13",
- "@backstage/plugin-config-schema": "0.1.77",
- "@backstage/plugin-devtools": "0.1.36",
- "@backstage/plugin-devtools-backend": "0.5.14",
- "@backstage/plugin-devtools-common": "0.1.22",
- "@backstage/plugin-devtools-react": "0.1.1",
- "@backstage/plugin-events-backend": "0.5.11",
- "@backstage/plugin-events-backend-module-aws-sqs": "0.4.19",
- "@backstage/plugin-events-backend-module-azure": "0.2.28",
- "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.28",
- "@backstage/plugin-events-backend-module-bitbucket-server": "0.1.9",
- "@backstage/plugin-events-backend-module-gerrit": "0.2.28",
- "@backstage/plugin-events-backend-module-github": "0.4.9",
- "@backstage/plugin-events-backend-module-gitlab": "0.3.9",
- "@backstage/plugin-events-backend-module-google-pubsub": "0.2.0",
- "@backstage/plugin-events-backend-module-kafka": "0.3.1",
- "@backstage/plugin-events-backend-test-utils": "0.1.52",
- "@backstage/plugin-events-node": "0.4.19",
- "@internal/plugin-todo-list": "1.0.48",
- "@internal/plugin-todo-list-backend": "1.0.47",
+ "@backstage/plugin-config-schema": "0.1.78",
+ "@backstage/plugin-devtools": "0.1.37",
+ "@backstage/plugin-devtools-backend": "0.5.15",
+ "@backstage/plugin-devtools-common": "0.1.23",
+ "@backstage/plugin-devtools-react": "0.2.0",
+ "@backstage/plugin-events-backend": "0.6.0",
+ "@backstage/plugin-events-backend-module-aws-sqs": "0.4.20",
+ "@backstage/plugin-events-backend-module-azure": "0.2.29",
+ "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.29",
+ "@backstage/plugin-events-backend-module-bitbucket-server": "0.1.10",
+ "@backstage/plugin-events-backend-module-gerrit": "0.2.29",
+ "@backstage/plugin-events-backend-module-github": "0.4.10",
+ "@backstage/plugin-events-backend-module-gitlab": "0.3.10",
+ "@backstage/plugin-events-backend-module-google-pubsub": "0.2.1",
+ "@backstage/plugin-events-backend-module-kafka": "0.3.2",
+ "@backstage/plugin-events-backend-test-utils": "0.1.53",
+ "@backstage/plugin-events-node": "0.4.20",
+ "@internal/plugin-todo-list": "1.0.49",
+ "@internal/plugin-todo-list-backend": "1.0.48",
"@internal/plugin-todo-list-common": "1.0.29",
- "@backstage/plugin-gateway-backend": "1.1.2",
- "@backstage/plugin-home": "0.9.2",
- "@backstage/plugin-home-react": "0.1.35",
- "@backstage/plugin-kubernetes": "0.12.16",
- "@backstage/plugin-kubernetes-backend": "0.21.1",
- "@backstage/plugin-kubernetes-cluster": "0.0.34",
+ "@backstage/plugin-gateway-backend": "1.1.3",
+ "@backstage/plugin-home": "0.9.3",
+ "@backstage/plugin-home-react": "0.1.36",
+ "@backstage/plugin-kubernetes": "0.12.17",
+ "@backstage/plugin-kubernetes-backend": "0.21.2",
+ "@backstage/plugin-kubernetes-cluster": "0.0.35",
"@backstage/plugin-kubernetes-common": "0.9.10",
- "@backstage/plugin-kubernetes-node": "0.4.1",
- "@backstage/plugin-kubernetes-react": "0.5.16",
- "@backstage/plugin-mcp-actions-backend": "0.1.9",
- "@backstage/plugin-mui-to-bui": "0.2.4",
- "@backstage/plugin-notifications": "0.5.14",
- "@backstage/plugin-notifications-backend": "0.6.2",
- "@backstage/plugin-notifications-backend-module-email": "0.3.18",
- "@backstage/plugin-notifications-backend-module-slack": "0.3.1",
+ "@backstage/plugin-kubernetes-node": "0.4.2",
+ "@backstage/plugin-kubernetes-react": "0.5.17",
+ "@backstage/plugin-mcp-actions-backend": "0.1.10",
+ "@backstage/plugin-mui-to-bui": "0.2.5",
+ "@backstage/plugin-notifications": "0.5.15",
+ "@backstage/plugin-notifications-backend": "0.6.3",
+ "@backstage/plugin-notifications-backend-module-email": "0.3.19",
+ "@backstage/plugin-notifications-backend-module-slack": "0.4.0",
"@backstage/plugin-notifications-common": "0.2.1",
- "@backstage/plugin-notifications-node": "0.2.23",
- "@backstage/plugin-org": "0.6.49",
- "@backstage/plugin-org-react": "0.1.47",
- "@backstage/plugin-permission-backend": "0.7.9",
- "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.16",
- "@backstage/plugin-permission-common": "0.9.6",
- "@backstage/plugin-permission-node": "0.10.10",
- "@backstage/plugin-permission-react": "0.4.40",
- "@backstage/plugin-proxy-backend": "0.6.10",
- "@backstage/plugin-proxy-node": "0.1.12",
- "@backstage/plugin-scaffolder": "1.35.3",
- "@backstage/plugin-scaffolder-backend": "3.1.3",
- "@backstage/plugin-scaffolder-backend-module-azure": "0.2.18",
- "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.3.19",
- "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.3.3",
- "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.18",
- "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.18",
- "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.20",
- "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.18",
- "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.18",
- "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.18",
- "@backstage/plugin-scaffolder-backend-module-github": "0.9.6",
- "@backstage/plugin-scaffolder-backend-module-gitlab": "0.11.3",
- "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.19",
- "@backstage/plugin-scaffolder-backend-module-rails": "0.5.18",
- "@backstage/plugin-scaffolder-backend-module-sentry": "0.3.1",
- "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.19",
- "@backstage/plugin-scaffolder-common": "1.7.6",
- "@backstage/plugin-scaffolder-node": "0.12.5",
- "@backstage/plugin-scaffolder-node-test-utils": "0.3.8",
- "@backstage/plugin-scaffolder-react": "1.19.7",
- "@backstage/plugin-search": "1.6.0",
- "@backstage/plugin-search-backend": "2.0.12",
- "@backstage/plugin-search-backend-module-catalog": "0.3.12",
- "@backstage/plugin-search-backend-module-elasticsearch": "1.8.0",
- "@backstage/plugin-search-backend-module-explore": "0.3.11",
- "@backstage/plugin-search-backend-module-pg": "0.5.52",
- "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.17",
- "@backstage/plugin-search-backend-module-techdocs": "0.4.11",
- "@backstage/plugin-search-backend-node": "1.4.1",
+ "@backstage/plugin-notifications-node": "0.2.24",
+ "@backstage/plugin-org": "0.7.0",
+ "@backstage/plugin-org-react": "0.1.48",
+ "@backstage/plugin-permission-backend": "0.7.10",
+ "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.17",
+ "@backstage/plugin-permission-common": "0.9.7",
+ "@backstage/plugin-permission-node": "0.10.11",
+ "@backstage/plugin-permission-react": "0.4.41",
+ "@backstage/plugin-proxy-backend": "0.6.11",
+ "@backstage/plugin-proxy-node": "0.1.13",
+ "@backstage/plugin-scaffolder": "1.36.0",
+ "@backstage/plugin-scaffolder-backend": "3.2.0",
+ "@backstage/plugin-scaffolder-backend-module-azure": "0.2.19",
+ "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.3.4",
+ "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.19",
+ "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.19",
+ "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.21",
+ "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.19",
+ "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.19",
+ "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.19",
+ "@backstage/plugin-scaffolder-backend-module-github": "0.9.7",
+ "@backstage/plugin-scaffolder-backend-module-gitlab": "0.11.4",
+ "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.20",
+ "@backstage/plugin-scaffolder-backend-module-rails": "0.5.19",
+ "@backstage/plugin-scaffolder-backend-module-sentry": "0.3.2",
+ "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.20",
+ "@backstage/plugin-scaffolder-common": "2.0.0",
+ "@backstage/plugin-scaffolder-node": "0.13.0",
+ "@backstage/plugin-scaffolder-node-test-utils": "0.3.9",
+ "@backstage/plugin-scaffolder-react": "1.20.0",
+ "@backstage/plugin-search": "1.7.0",
+ "@backstage/plugin-search-backend": "2.1.0",
+ "@backstage/plugin-search-backend-module-catalog": "0.3.13",
+ "@backstage/plugin-search-backend-module-elasticsearch": "1.8.1",
+ "@backstage/plugin-search-backend-module-explore": "0.3.12",
+ "@backstage/plugin-search-backend-module-pg": "0.5.53",
+ "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.18",
+ "@backstage/plugin-search-backend-module-techdocs": "0.4.12",
+ "@backstage/plugin-search-backend-node": "1.4.2",
"@backstage/plugin-search-common": "1.2.22",
- "@backstage/plugin-search-react": "1.10.3",
- "@backstage/plugin-signals": "0.0.28",
- "@backstage/plugin-signals-backend": "0.3.12",
- "@backstage/plugin-signals-node": "0.1.28",
- "@backstage/plugin-signals-react": "0.0.19",
- "@backstage/plugin-techdocs": "1.17.0",
- "@backstage/plugin-techdocs-addons-test-utils": "2.0.2",
- "@backstage/plugin-techdocs-backend": "2.1.5",
+ "@backstage/plugin-search-react": "1.11.0",
+ "@backstage/plugin-signals": "0.0.29",
+ "@backstage/plugin-signals-backend": "0.3.13",
+ "@backstage/plugin-signals-node": "0.1.29",
+ "@backstage/plugin-signals-react": "0.0.20",
+ "@backstage/plugin-techdocs": "1.17.1",
+ "@backstage/plugin-techdocs-addons-test-utils": "2.0.3",
+ "@backstage/plugin-techdocs-backend": "2.1.6",
"@backstage/plugin-techdocs-common": "0.1.1",
- "@backstage/plugin-techdocs-module-addons-contrib": "1.1.33",
- "@backstage/plugin-techdocs-node": "1.14.2",
- "@backstage/plugin-techdocs-react": "1.3.8",
- "@backstage/plugin-user-settings": "0.9.0",
- "@backstage/plugin-user-settings-backend": "0.4.0",
+ "@backstage/plugin-techdocs-module-addons-contrib": "1.1.34",
+ "@backstage/plugin-techdocs-node": "1.14.4",
+ "@backstage/plugin-techdocs-react": "1.3.9",
+ "@backstage/plugin-user-settings": "0.9.1",
+ "@backstage/plugin-user-settings-backend": "0.4.1",
"@backstage/plugin-user-settings-common": "0.1.0"
},
- "changesets": []
+ "changesets": [
+ "add-actions-registry-examples",
+ "angry-clouds-tell",
+ "app-defaults-clarify-failures",
+ "app-routes-redirect-config",
+ "auditor-zod-v4-refactor",
+ "azure-scm-events-layer",
+ "bump-glob-rollup-security",
+ "catalog-entity-page-no-header",
+ "catalog-exists-query-optimization",
+ "catalog-graph-nfs-page",
+ "catalog-import-nfs-page",
+ "catalog-search-table-sync",
+ "chatty-cups-create",
+ "clarify-fetch-failures",
+ "cold-bikes-beam",
+ "cool-shoes-hide",
+ "create-app-1774970958",
+ "dependabot-03b295a",
+ "dependabot-2a735a0",
+ "dependabot-cc35625",
+ "drop-react-17-packages-ui",
+ "easy-pens-judge",
+ "fix-avatar-flex-shrink",
+ "fix-aws-s3-url-reader-custom-endpoints",
+ "fix-bui-relative-href-resolution",
+ "fix-catalog-filter-flicker",
+ "fix-catalog-refresh-state-deadlock",
+ "fix-catalog-table-loading-flash",
+ "fix-entity-info-card-header-overflow",
+ "fix-org-profile-card-overflow",
+ "fix-sidebar-settings-nav",
+ "fix-translation-ref-ts6",
+ "fix-zod-generic-auth-node",
+ "fix-zod-generic-frontend-plugin-api",
+ "frontend-app-api-permission-error",
+ "funny-items-wink",
+ "fuzzy-mangos-design",
+ "gitlab-scm-events-layer",
+ "happy-masks-allow",
+ "hide-compat-wrapper-headers",
+ "hip-parents-fall",
+ "hot-yaks-crash",
+ "kubernetes-nfs-metadata",
+ "lazy-jars-shake",
+ "loopback-redirect-uri-port",
+ "lucky-things-write",
+ "notifications-nfs-metadata",
+ "olive-peaches-fly",
+ "olive-ravens-smell",
+ "orange-friends-march",
+ "org-nfs-metadata",
+ "parallelize-cli-reports",
+ "plugin-header-remove-toolbar-wrapper",
+ "quiet-streets-laugh",
+ "remove-cli-module-new-dep",
+ "remove-empty-examples-scaffolder-bridge",
+ "remove-jest-when-dep",
+ "remove-plugin-header-action-define-params",
+ "remove-type-fest-github",
+ "renovate-9d44e41",
+ "renovate-ced70c0",
+ "repo-tools-peer-deps-react18",
+ "scaffolder-nfs-page-layout",
+ "show-pagination-label",
+ "shy-doors-repair",
+ "signals-nfs-metadata",
+ "simplify-compat-route-ref",
+ "simplify-route-ref-types",
+ "smart-cycles-fall",
+ "specialized-app-apis",
+ "swift-lizards-bathe",
+ "ts6-cli-dom-asynciterable",
+ "ts6-eslint-plugin",
+ "ts6-plugin-scaffolder",
+ "ui-menu-item-style-refactor",
+ "ui-react-aria-deps",
+ "unprocessed-entities-devtools",
+ "update-app-visualizer-header-action",
+ "wicked-impalas-fry"
+ ]
}
diff --git a/.changeset/pretty-days-taste.md b/.changeset/pretty-days-taste.md
deleted file mode 100644
index 51c97e42ee..0000000000
--- a/.changeset/pretty-days-taste.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/backend-defaults': patch
----
-
-Fixed `yarn backstage-cli config:check --strict --config app-config.yaml` config validation error by adding
-an optional `default` type discriminator to PostgreSQL connection configuration,
-allowing `config:check` to properly validate `default` connection configurations.
diff --git a/.changeset/quiet-streets-laugh.md b/.changeset/quiet-streets-laugh.md
new file mode 100644
index 0000000000..9c4007ff72
--- /dev/null
+++ b/.changeset/quiet-streets-laugh.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-github': patch
+---
+
+Fixed a bug where `GithubEntityProvider` with `validateLocationsExist: true` and `filters.branch` configured would always check for the catalog file on the repository's default branch (`HEAD`) instead of the configured branch. This caused repositories to be filtered out when the catalog file only existed on the non-default branch.
diff --git a/.changeset/rare-adults-attack.md b/.changeset/rare-adults-attack.md
deleted file mode 100644
index 8117f26dea..0000000000
--- a/.changeset/rare-adults-attack.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/backend-plugin-api': patch
-'@backstage/backend-defaults': patch
----
-
-Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
diff --git a/.changeset/remove-cli-module-new-dep.md b/.changeset/remove-cli-module-new-dep.md
new file mode 100644
index 0000000000..65d813f575
--- /dev/null
+++ b/.changeset/remove-cli-module-new-dep.md
@@ -0,0 +1,5 @@
+---
+'@backstage/create-app': patch
+---
+
+Removed the unnecessary `@backstage/cli-module-new` dependency from the `next-app` template, since it is already included through `@backstage/cli-defaults`.
diff --git a/.changeset/remove-empty-examples-scaffolder-bridge.md b/.changeset/remove-empty-examples-scaffolder-bridge.md
new file mode 100644
index 0000000000..1eafeaebb9
--- /dev/null
+++ b/.changeset/remove-empty-examples-scaffolder-bridge.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Removed unnecessary empty `examples` array from actions bridged via the actions registry.
diff --git a/.changeset/remove-jest-when-dep.md b/.changeset/remove-jest-when-dep.md
new file mode 100644
index 0000000000..0167661523
--- /dev/null
+++ b/.changeset/remove-jest-when-dep.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend-module-rails': patch
+---
+
+Removed unused `jest-when` dev dependency.
diff --git a/.changeset/remove-plugin-header-action-define-params.md b/.changeset/remove-plugin-header-action-define-params.md
new file mode 100644
index 0000000000..93a85856e2
--- /dev/null
+++ b/.changeset/remove-plugin-header-action-define-params.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-plugin-api': patch
+---
+
+Removed the unnecessary need to use `defineParams` callback from `PluginHeaderActionBlueprint`. It still works, but is no longer required.
diff --git a/.changeset/remove-type-fest-github.md b/.changeset/remove-type-fest-github.md
new file mode 100644
index 0000000000..1b8e67f17d
--- /dev/null
+++ b/.changeset/remove-type-fest-github.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-github': patch
+---
+
+Removed the `type-fest` dev dependency, replacing its `PartialDeep` import with a local helper type in tests.
diff --git a/.changeset/renovate-8b1c21e.md b/.changeset/renovate-8b1c21e.md
deleted file mode 100644
index 52b1ed546a..0000000000
--- a/.changeset/renovate-8b1c21e.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli': patch
----
-
-Updated dependency `webpack` to `~5.105.0`.
diff --git a/.changeset/renovate-9d44e41.md b/.changeset/renovate-9d44e41.md
new file mode 100644
index 0000000000..0fd0cb5306
--- /dev/null
+++ b/.changeset/renovate-9d44e41.md
@@ -0,0 +1,5 @@
+---
+'@backstage/ui': patch
+---
+
+Updated dependency `@types/use-sync-external-store` to `^1.0.0`.
diff --git a/.changeset/renovate-ced70c0.md b/.changeset/renovate-ced70c0.md
new file mode 100644
index 0000000000..4b7e2abcc7
--- /dev/null
+++ b/.changeset/renovate-ced70c0.md
@@ -0,0 +1,11 @@
+---
+'@backstage/backend-defaults': patch
+'@backstage/backend-dynamic-feature-service': patch
+'@backstage/plugin-catalog-backend': patch
+'@backstage/plugin-events-backend-module-google-pubsub': patch
+'@backstage/plugin-gateway-backend': patch
+'@backstage/plugin-scaffolder-backend': patch
+'@backstage/plugin-signals': patch
+---
+
+Updated dependency `wait-for-expect` to `^4.0.0`.
diff --git a/.changeset/repo-tools-peer-deps-react18.md b/.changeset/repo-tools-peer-deps-react18.md
new file mode 100644
index 0000000000..7d24da45a6
--- /dev/null
+++ b/.changeset/repo-tools-peer-deps-react18.md
@@ -0,0 +1,5 @@
+---
+'@backstage/repo-tools': patch
+---
+
+Added support for packages that only support React 18+ in the `peer-deps` command.
diff --git a/.changeset/rude-groups-shout.md b/.changeset/rude-groups-shout.md
deleted file mode 100644
index 59e4a22aa9..0000000000
--- a/.changeset/rude-groups-shout.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/ui': patch
----
-
-Improved type safety in `useDefinition` by centralizing prop resolution and strengthening the `BgPropsConstraint` to require that `bg` provider components declare `children` as a required prop in their OwnProps type.
diff --git a/.changeset/scaffolder-export-form-fields-api.md b/.changeset/scaffolder-export-form-fields-api.md
deleted file mode 100644
index 02ead2d2b2..0000000000
--- a/.changeset/scaffolder-export-form-fields-api.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder': patch
----
-
-Added back the `formFieldsApiRef` and `ScaffolderFormFieldsApi` alpha exports that were unintentionally removed.
diff --git a/.changeset/scaffolder-nfs-page-layout.md b/.changeset/scaffolder-nfs-page-layout.md
new file mode 100644
index 0000000000..2f0f848e40
--- /dev/null
+++ b/.changeset/scaffolder-nfs-page-layout.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Fixed the layout of the scaffolder plugin in the new frontend system to use the new page layout.
diff --git a/.changeset/show-pagination-label.md b/.changeset/show-pagination-label.md
new file mode 100644
index 0000000000..6470390d71
--- /dev/null
+++ b/.changeset/show-pagination-label.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Added `showPaginationLabel` prop to `TablePagination` and `useTable` pagination options. When set to `false`, the pagination label (e.g., "1 - 20 of 150") is hidden while navigation controls remain visible. Defaults to `true`.
+
+**Affected components:** `TablePagination`, `useTable`
diff --git a/.changeset/shy-doors-repair.md b/.changeset/shy-doors-repair.md
new file mode 100644
index 0000000000..e9330a231a
--- /dev/null
+++ b/.changeset/shy-doors-repair.md
@@ -0,0 +1,11 @@
+---
+'@backstage/plugin-auth-backend': minor
+---
+
+**BREAKING**: The setting `auth.omitIdentityTokenOwnershipClaim` has had its default value switched to `true`.
+
+With this setting Backstage user tokens issued by the `auth` backend will no longer contain an `ent` claim - the one with the user's ownership entity refs. This means that tokens issued in large orgs no longer risk hitting HTTP header size limits.
+
+To get ownership info for the current user, code should use the `userInfo` core service. In practice code will typically already conform to this since the `ent` claim has not been readily exposed in any other way for quite some time. But code which explicitly decodes Backstage tokens - which is strongly discouraged - may be affected by this change.
+
+The setting will remain for some time to allow it to be set back to `false` if need be, but it will be removed entirely in a future release.
diff --git a/.changeset/signals-nfs-metadata.md b/.changeset/signals-nfs-metadata.md
new file mode 100644
index 0000000000..de9c09421b
--- /dev/null
+++ b/.changeset/signals-nfs-metadata.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-signals': patch
+---
+
+Added `title` and `icon` to the new frontend system plugin definition.
diff --git a/.changeset/silver-pigs-remain.md b/.changeset/silver-pigs-remain.md
deleted file mode 100644
index d6481f2990..0000000000
--- a/.changeset/silver-pigs-remain.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/ui': patch
----
-
-Added proper cursor styles for RadioGroup items.
-
-**Affected components:** RadioGroup
diff --git a/.changeset/simplify-compat-route-ref.md b/.changeset/simplify-compat-route-ref.md
new file mode 100644
index 0000000000..a9b78f8ecf
--- /dev/null
+++ b/.changeset/simplify-compat-route-ref.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-compat-api': patch
+---
+
+Removed unnecessary type argument from internal `createRouteRef` call.
diff --git a/.changeset/simplify-route-ref-types.md b/.changeset/simplify-route-ref-types.md
new file mode 100644
index 0000000000..470c2659db
--- /dev/null
+++ b/.changeset/simplify-route-ref-types.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-plugin-api': minor
+---
+
+Simplified the type signature of `createRouteRef` by replacing the dual `TParams`/`TParamKeys` type parameters with a single `TParamKey` parameter. This is a breaking change for callers that explicitly provided type arguments, but most usage that relies on inference is unaffected.
diff --git a/.changeset/six-suits-write.md b/.changeset/six-suits-write.md
new file mode 100644
index 0000000000..3789c6342f
--- /dev/null
+++ b/.changeset/six-suits-write.md
@@ -0,0 +1,5 @@
+---
+'@backstage/integration': patch
+---
+
+Fixed `SingleInstanceGithubCredentialsProvider` to return app credentials when `getCredentials` is called with a bare host URL (e.g. `https://github.com`) instead of falling back to a personal access token.
diff --git a/.changeset/sixty-pianos-begin.md b/.changeset/sixty-pianos-begin.md
deleted file mode 100644
index 98db19c21d..0000000000
--- a/.changeset/sixty-pianos-begin.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/repo-tools': patch
----
-
-Updated `@microsoft/api-extractor` to `7.57.3` and added tests for `getTsDocConfig`
diff --git a/.changeset/smart-cycles-fall.md b/.changeset/smart-cycles-fall.md
new file mode 100644
index 0000000000..dcc5e942bd
--- /dev/null
+++ b/.changeset/smart-cycles-fall.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-react': patch
+---
+
+Fixed entity relation cards (e.g., "Has components") only showing one entity at a time by using `paginationOptions: { type: 'none' }` instead of deriving page size from data length.
diff --git a/.changeset/soft-beers-bathe.md b/.changeset/soft-beers-bathe.md
new file mode 100644
index 0000000000..ae2b8a53a3
--- /dev/null
+++ b/.changeset/soft-beers-bathe.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli-module-build': patch
+---
+
+When building dist-workspaces with --always-pack, batch `yarn pack` operations to avoid packing packages and their dependencies simultaneously.
diff --git a/.changeset/solid-pianos-act.md b/.changeset/solid-pianos-act.md
new file mode 100644
index 0000000000..701e7ed755
--- /dev/null
+++ b/.changeset/solid-pianos-act.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Use Backstage Link component for markdown anchor rendering to ensure consistent internal and external link behavior.
diff --git a/.changeset/specialized-app-apis.md b/.changeset/specialized-app-apis.md
new file mode 100644
index 0000000000..4ffa18457d
--- /dev/null
+++ b/.changeset/specialized-app-apis.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-app-api': patch
+---
+
+Added `apis` to `BootstrapSpecializedApp` and `FinalizedSpecializedApp` types.
diff --git a/.changeset/stable-translation-plugin-app.md b/.changeset/stable-translation-plugin-app.md
deleted file mode 100644
index 6e57cfeeb8..0000000000
--- a/.changeset/stable-translation-plugin-app.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-app': patch
----
-
-Switched translation API imports (`translationApiRef`, `appLanguageApiRef`) from the alpha `@backstage/core-plugin-api/alpha` path to the stable `@backstage/frontend-plugin-api` export. This has no effect on runtime behavior.
diff --git a/.changeset/stable-translation-test-utils.md b/.changeset/stable-translation-test-utils.md
deleted file mode 100644
index 3d624a5dc8..0000000000
--- a/.changeset/stable-translation-test-utils.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/frontend-test-utils': patch
----
-
-Switched `MockTranslationApi` and related test utility imports from `@backstage/core-plugin-api/alpha` to the stable `@backstage/frontend-plugin-api` export. The `TranslationApi` type in the API report is now sourced from a single package. This has no effect on runtime behavior.
diff --git a/.changeset/stupid-pans-hope.md b/.changeset/stupid-pans-hope.md
deleted file mode 100644
index 4fd8c5914a..0000000000
--- a/.changeset/stupid-pans-hope.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/catalog-client': patch
----
-
-Minor update to catalog client docs
diff --git a/.changeset/swift-flowers-grin.md b/.changeset/swift-flowers-grin.md
deleted file mode 100644
index 33466615cd..0000000000
--- a/.changeset/swift-flowers-grin.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-catalog-backend': patch
----
-
-Changed the `search` table foreign key to point to `final_entities` instead of `refresh_state`
diff --git a/.changeset/swift-lizards-bathe.md b/.changeset/swift-lizards-bathe.md
new file mode 100644
index 0000000000..f70155a1ea
--- /dev/null
+++ b/.changeset/swift-lizards-bathe.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Fixed the ButtonIcon's loading spinner animation
+
+**Affected components:** ButtonIcon
diff --git a/.changeset/swift-ravens-jog.md b/.changeset/swift-ravens-jog.md
deleted file mode 100644
index db92764560..0000000000
--- a/.changeset/swift-ravens-jog.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-'@backstage/plugin-mcp-actions-backend': patch
----
-
-Adds two new metrics to track MCP server operations and sessions.
-
-- `mcp.server.operation.duration`: The duration taken to process an individual MCP operation
-- `mcp.server.session.duration`: The duration of the MCP session from the perspective of the server
diff --git a/.changeset/tired-bushes-write.md b/.changeset/tired-bushes-write.md
deleted file mode 100644
index af49530ef4..0000000000
--- a/.changeset/tired-bushes-write.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-auth-backend': patch
----
-
-Update migrations to be reversible
diff --git a/.changeset/ts6-cli-dom-asynciterable.md b/.changeset/ts6-cli-dom-asynciterable.md
new file mode 100644
index 0000000000..e27a81d60f
--- /dev/null
+++ b/.changeset/ts6-cli-dom-asynciterable.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Added `DOM.AsyncIterable` to the default `lib` in the shared TypeScript configuration, enabling standard async iteration support for DOM APIs such as `FileSystemDirectoryHandle`. This aligns behavior with [TypeScript 6.0](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/#the-dom-lib-now-contains-domiterable-and-domasynciterable), where this lib is included in `DOM` by default.
diff --git a/.changeset/ts6-eslint-plugin.md b/.changeset/ts6-eslint-plugin.md
new file mode 100644
index 0000000000..fa603a7c19
--- /dev/null
+++ b/.changeset/ts6-eslint-plugin.md
@@ -0,0 +1,5 @@
+---
+'@backstage/eslint-plugin': patch
+---
+
+Fixed `no-mixed-plugin-imports` rule to return `null` from non-fixable suggestion handlers and added an explicit `SuggestionReportDescriptor[]` type annotation, matching the stricter type checking in TypeScript 6.0.
diff --git a/.changeset/ts6-plugin-scaffolder.md b/.changeset/ts6-plugin-scaffolder.md
new file mode 100644
index 0000000000..07aa9b642c
--- /dev/null
+++ b/.changeset/ts6-plugin-scaffolder.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Removed custom `IterableDirectoryHandle` and `WritableFileHandle` types in favor of the standard DOM `FileSystemDirectoryHandle` and `FileSystemFileHandle` types, which are now available through the `DOM.AsyncIterable` lib added to the shared TypeScript configuration.
diff --git a/.changeset/twenty-worlds-create.md b/.changeset/twenty-worlds-create.md
deleted file mode 100644
index e7e4770938..0000000000
--- a/.changeset/twenty-worlds-create.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/backend-test-utils': patch
----
-
-Adds a new metrics service mock to be leveraged in tests
diff --git a/.changeset/ui-menu-item-style-refactor.md b/.changeset/ui-menu-item-style-refactor.md
new file mode 100644
index 0000000000..7ff7b28c9e
--- /dev/null
+++ b/.changeset/ui-menu-item-style-refactor.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Simplified the `Menu` component's item structure by removing the inner wrapper element and applying styles directly to the menu item, improving DOM clarity.
+
+**Affected components:** Menu
diff --git a/.changeset/ui-react-aria-deps.md b/.changeset/ui-react-aria-deps.md
new file mode 100644
index 0000000000..2a003e1a2d
--- /dev/null
+++ b/.changeset/ui-react-aria-deps.md
@@ -0,0 +1,5 @@
+---
+'@backstage/ui': patch
+---
+
+Added `react-aria`, `react-stately`, `@react-aria/interactions`, `@react-stately/layout`, and `@react-stately/overlays` as dependencies.
diff --git a/.changeset/unprocessed-entities-devtools.md b/.changeset/unprocessed-entities-devtools.md
new file mode 100644
index 0000000000..4a34c16938
--- /dev/null
+++ b/.changeset/unprocessed-entities-devtools.md
@@ -0,0 +1,11 @@
+---
+'@backstage/plugin-catalog-unprocessed-entities': patch
+---
+
+The unprocessed entities view is now primarily intended for use as a tab within the DevTools plugin. The standalone page is still available but disabled by default. To re-enable it, add the following to your `app-config.yaml`:
+
+```yaml
+app:
+ extensions:
+ - page:catalog-unprocessed-entities
+```
diff --git a/.changeset/update-app-visualizer-header-action.md b/.changeset/update-app-visualizer-header-action.md
new file mode 100644
index 0000000000..e95e84eb43
--- /dev/null
+++ b/.changeset/update-app-visualizer-header-action.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-app-visualizer': patch
+---
+
+Updated `PluginHeaderActionBlueprint` usage to pass params as a plain object.
diff --git a/.changeset/wicked-impalas-fry.md b/.changeset/wicked-impalas-fry.md
new file mode 100644
index 0000000000..35633b51e2
--- /dev/null
+++ b/.changeset/wicked-impalas-fry.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Fixed HeaderNav hover indicator covering tab text when theme uses opaque background colors. Also fixed an incorrect CSS variable reference (`--bui-font-family` → `--bui-font-regular`).
+
+**Affected components:** Header
diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md
deleted file mode 100644
index 170a4eb868..0000000000
--- a/.claude/CLAUDE.md
+++ /dev/null
@@ -1 +0,0 @@
-Follow the instructions at /.github/copilot-instructions.md
diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md
new file mode 120000
index 0000000000..be77ac83a1
--- /dev/null
+++ b/.claude/CLAUDE.md
@@ -0,0 +1 @@
+../AGENTS.md
\ No newline at end of file
diff --git a/.cursor/rules/general.mdc b/.cursor/rules/general.mdc
deleted file mode 100644
index 3a81a0cb5f..0000000000
--- a/.cursor/rules/general.mdc
+++ /dev/null
@@ -1,7 +0,0 @@
----
-description: General project guidelines for Backstage development
-globs:
-alwaysApply: true
----
-
-Follow the instructions at /.github/copilot-instructions.md
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index f4369920a5..e6dc0e98f5 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -3,7 +3,7 @@
"forwardPorts": [3000, 7007],
"build": { "dockerfile": "Dockerfile" },
"features": {
- "ghcr.io/devcontainers/features/common-utils:2.5.6": {},
+ "ghcr.io/devcontainers/features/common-utils:2.5.7": {},
"ghcr.io/devcontainers-extra/features/mkdocs:2": {}
},
"postCreateCommand": "bash .devcontainer/setup.sh",
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 725c6c676b..7e5cc36bdf 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -12,8 +12,10 @@ yarn.lock @backstage/maintainers @backst
/.github @backstage/operations-maintainers
/.github/vale @backstage/maintainers @backstage/documentation-maintainers
/.patches @backstage/operations-maintainers
+/.storybook @backstage/design-system-maintainers
/beps/0001-notifications-system @backstage/maintainers @backstage/notifications-maintainers
/docs @backstage/maintainers @backstage/documentation-maintainers
+/docs-ui @backstage/design-system-maintainers
/docs/assets/search @backstage/search-maintainers
/docs/auth @backstage/auth-maintainers
/docs/backend-system @backstage/framework-maintainers
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 2157025b7b..9ded15067a 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -1,49 +1,3 @@
-Backstage is an open platform for building developer portals. This is a TypeScript monorepo using Yarn workspaces.
-
-## Key Directories
-
-- `/packages`: Core framework packages (prefixed `@backstage/`)
-- `/plugins`: Plugin packages (prefixed `@backstage/plugin-*`)
-- `/packages/app` and `/packages/backend`: Example app for local development
-- `/packages/app`: Main example app using the new frontend system
-- `/packages/app-legacy`: Example app using the old frontend system
-- `/docs`: Documentation files
-
-Packages prefixed with `core-` (e.g., `@backstage/core-plugin-api`) are part of the old frontend system. Packages prefixed with `frontend-` (e.g., `@backstage/frontend-plugin-api`) are part of the new frontend system (NFS). Packages prefixed with `backend-` (e.g., `@backstage/backend-plugin-api`) are part of the backend system.
-
-## Code Standards
-
-The following files contain guidelines for the project:
-
-- `/CONTRIBUTING.md`: comprehensive contribution guidelines.
-- `/STYLE.md`: guidelines for code style.
-- `/REVIEWING.md`: guidelines for pull requests and writing changesets.
-- `/SECURITY.md`: guidelines for security.
-- `/docs/architecture-decisions/`: contains the architecture decisions for the project.
-
-## Development Flow
-
-Before any of these commands can be run, you need to run `yarn install` in the project root.
-
-- Build: There is no need to build the project during development, and it is verified automatically in the CI pipeline.
-- Test: Use `yarn test --no-watch ` in the project root to run tests. The path can be either a single file or a directory. Always provide a path, avoid running all tests.
-- Type checking: Use `yarn tsc` in the project root to run the type checker.
-- Code formatting: Use `yarn prettier --write ` to format code.
-- Lint: Use `yarn lint --fix` in the project root to run the linter.
-- API reports: Before submitting a pull request with changes to any package in the workspace, run `yarn build:api-reports` in the project root to generate API reports for all packages.
-- Dev server: Use `yarn start` to run the example app locally (frontend on :3000, backend on :7007).
-- Create: Use `yarn new` to scaffold new plugins, packages, or modules.
-
-You MUST NOT create a release by running `yarn changesets version` or `yarn release` as part of any changes. Releases are created by separate workflows.
-
-All changes that affect the published version of packages in the `/packages` and `/plugins` directories must be accompanied by a changeset. Only non-private packages require changesets. See the guidelines in `/CONTRIBUTING.md#creating-changesets` for information on how to write good changesets. Changesets are stored in the `/.changeset` directory. Breaking changes must be accompanied by a `minor` version bump for packages below version `1.0.0`, or a `major` version bump for packages at version `1.0.0` or higher.
-
-Never update ESLint, Prettier, or TypeScript configuration files unless specifically requested.
-
-## Repository Structure
-
-See `/docs/contribute/project-structure.md` for a detailed description of the repository structure.
-
## Pull Request Reviews
When reviewing a pull request you MUST calculate a priority score between 0 and 100, where a higher score indicates higher priority. You MUST include the score as an HTML comment at the very beginning of the "PR Overview" section, using the following format: ``.
diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt
index f6f37b17cf..0a96735646 100644
--- a/.github/vale/config/vocabularies/Backstage/accept.txt
+++ b/.github/vale/config/vocabularies/Backstage/accept.txt
@@ -30,6 +30,7 @@ backend's
backported
backporting
BEPs
+bg
bigint
Bigtable
Billett
@@ -42,11 +43,13 @@ breakpoint
Brex
broadcasted
bugfixes
+BUIProvider
builtins
bundler
bundlers
bursty
callout
+callouts
camelCase
CDNs
Chai
@@ -57,6 +60,7 @@ chanwit
Chanwit
CI/CD
classname
+cleye
cli
CLIs
cloudbuild
@@ -136,6 +140,7 @@ dyno
ecco
elasticsearch
Entra
+enums
env
Env
esbuild
@@ -245,6 +250,8 @@ Levenshtein
lightbox
Lightsail
limitranges
+liveness
+LLMs
LocalStack
lockdown
lockfile
@@ -391,6 +398,7 @@ repos
requestors
rerender
rerenders
+resizable
resourcequotas
retryable
reusability
@@ -425,6 +433,7 @@ scrollable
scrollbar
scrollbars
sdks
+SearchField
seb
semlas
semver
@@ -494,6 +503,7 @@ templater
Templater
templaters
Templaters
+TextField
TFRecord
theia
Themer
@@ -510,6 +520,7 @@ toolsets
tooltip
tooltips
touchpoint
+touchpoints
transpilation
transpile
transpiled
@@ -527,6 +538,7 @@ unassign
unbreak
Unconference
undici
+ungrouped
unicode
unmanaged
unmount
@@ -550,11 +562,13 @@ validators
Valkey
varchar
viewport
+virtualized
vite
VMware
Vodafone
VPCs
VSCode
+walkthroughs
Wayfair
Weaveworks
Webpack
@@ -573,6 +587,3 @@ zod
Zolotusky
zoomable
zsh
-resizable
-enums
-LLMs
diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml
index aca727743a..d416916bb0 100644
--- a/.github/workflows/api-breaking-changes-comment.yml
+++ b/.github/workflows/api-breaking-changes-comment.yml
@@ -23,7 +23,7 @@ jobs:
comment-cache-key: ${{ steps.hash.outputs.COMMENT_FILE_HASH }}
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
disable-sudo: true
egress-policy: block
@@ -74,7 +74,7 @@ jobs:
- name: Cache Comment
if: ${{ steps.event.outputs.ACTION != 'closed' }}
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
+ uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: comment.md
key: ${{ steps.hash.outputs.COMMENT_FILE_HASH }}
@@ -103,7 +103,7 @@ jobs:
- name: Fetch cached Manifests File
id: cache
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: comment.md
key: ${{ needs.setup.outputs.comment-cache-key }}
diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml
index a96556bb45..53c90cb066 100644
--- a/.github/workflows/api-breaking-changes.yml
+++ b/.github/workflows/api-breaking-changes.yml
@@ -14,7 +14,7 @@ jobs:
if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }}
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -27,13 +27,13 @@ jobs:
run: git fetch --depth 1 origin ${{ github.base_ref }}
- name: setup-node
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: linux-v22
@@ -47,7 +47,7 @@ jobs:
cat ${{ github.event_path }} > event.json
- name: Upload Artifacts
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: preview-spec
path: |
diff --git a/.github/workflows/automate_area-labels.yml b/.github/workflows/automate_area-labels.yml
index cdf4a28b55..90b8ea9d16 100644
--- a/.github/workflows/automate_area-labels.yml
+++ b/.github/workflows/automate_area-labels.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml
index adfa7d29ec..34b1791ca8 100644
--- a/.github/workflows/automate_changeset_feedback.yml
+++ b/.github/workflows/automate_changeset_feedback.yml
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -34,7 +34,7 @@ jobs:
ref: 'refs/pull/${{ github.event.pull_request.number }}/merge'
- name: fetch base
run: git fetch --depth 1 origin ${{ github.base_ref }}
- - uses: backstage/actions/changeset-feedback@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ - uses: backstage/actions/changeset-feedback@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
name: Generate feedback
with:
diff-ref: 'origin/master'
diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml
index c1ecde9194..62db24ce17 100644
--- a/.github/workflows/automate_merge_message.yml
+++ b/.github/workflows/automate_merge_message.yml
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/automate_stale.yml b/.github/workflows/automate_stale.yml
index 34ab03f207..4bed28af3a 100644
--- a/.github/workflows/automate_stale.yml
+++ b/.github/workflows/automate_stale.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/ci-noop.yml b/.github/workflows/ci-noop.yml
index d9851778c6..71b0c68791 100644
--- a/.github/workflows/ci-noop.yml
+++ b/.github/workflows/ci-noop.yml
@@ -6,6 +6,7 @@ on:
pull_request:
paths:
- 'microsite/**'
+ - '!microsite/data/**'
- 'beps/**'
permissions:
@@ -40,7 +41,7 @@ jobs:
name: Test ${{ matrix.node-version }}
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9ac6120d28..d35644e152 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,9 +2,11 @@ name: CI
on:
# NOTE: If you change these you must update ci-noop.yml as well
pull_request:
- paths-ignore:
- - 'microsite/**'
- - 'beps/**'
+ paths:
+ - '**'
+ - '!microsite/**'
+ - 'microsite/data/**'
+ - '!beps/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -28,7 +30,7 @@ jobs:
name: Install ${{ matrix.node-version }}
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -38,7 +40,7 @@ jobs:
run: |
mkdir -p ./context
echo "${{ github.event.pull_request.number }}" > ./context/pr-number
- - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: matrix.node-version == '22.x'
with:
name: pr-context
@@ -47,13 +49,13 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
@@ -76,20 +78,20 @@ jobs:
name: Verify ${{ matrix.node-version }}
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
@@ -239,13 +241,13 @@ jobs:
run: git fetch origin ${{ github.event.pull_request.base.ref }}
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
@@ -255,7 +257,7 @@ jobs:
# Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds
- name: restore backstage-cli cache
- uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: .cache/backstage-cli
key: ${{ runner.os }}-v${{ matrix.node-version }}-backstage-cli-${{ github.run_id }}
@@ -266,10 +268,10 @@ jobs:
run: yarn prettier:check --cache --cache-location .cache/backstage-cli/.prettier-cache
- name: lint changed packages
- run: yarn backstage-cli repo lint --since origin/master --successCache --successCacheDir .cache/backstage-cli
+ run: yarn backstage-cli repo lint --since origin/master --success-cache --success-cache-dir .cache/backstage-cli
- name: test changed packages
- run: yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --since origin/master --successCache --successCacheDir .cache/backstage-cli
+ run: yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --since origin/master --success-cache --success-cache-dir .cache/backstage-cli
env:
BACKSTAGE_TEST_DISABLE_DOCKER: 1
BACKSTAGE_TEST_DATABASE_POSTGRES18_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres18.ports[5432] }}
@@ -279,7 +281,7 @@ jobs:
# Always save success cache even if there were failures, that way it can be used in re-triggered builds
- name: save backstage-cli cache
- uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
if: always()
with:
path: .cache/backstage-cli
diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml
index 73c5202fb3..61f8a2ce51 100644
--- a/.github/workflows/cron.yml
+++ b/.github/workflows/cron.yml
@@ -10,11 +10,11 @@ jobs:
timeout-minutes: 10
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- - uses: backstage/actions/cron@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ - uses: backstage/actions/cron@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }}
private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }}
diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml
index ad7029af14..a1e0b49c7a 100644
--- a/.github/workflows/deploy_docker-image.yml
+++ b/.github/workflows/deploy_docker-image.yml
@@ -20,7 +20,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -31,13 +31,13 @@ jobs:
ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }}
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
@@ -52,17 +52,17 @@ jobs:
working-directory: ./example-app
- name: Login to GitHub Container Registry
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
+ uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
+ uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build and push
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
+ uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
context: './example-app'
file: ./example-app/packages/backend/Dockerfile
diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml
index 1de546aaa5..e07aa4409b 100644
--- a/.github/workflows/deploy_microsite.yml
+++ b/.github/workflows/deploy_microsite.yml
@@ -23,55 +23,56 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- - name: find latest release
+ - name: find latest release branch
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
id: find-release
with:
script: |
- const { data } = await github.rest.repos.listTags({
+ const data = await octokit.paginate(github.rest.repos.listBranches, {
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100,
})
- const [{tag}] = data
+ const [{branch}] = data
.map(i => i.name)
- .filter(tag => tag.match(/^v\d+\.\d+\.\d+$/))
- .map(tag => ({
- tag,
- val: tag
+ .filter(branch => branch.match(/^patch\/v\d+\.\d+\.\d+$/))
+ .map(branch => ({
+ branch,
+ val: branch
+ .split('/')[1]
.slice(1)
.split('.')
.reduce((val, part) => Number(val) * 1000 + Number(part))
}))
.sort((a, b) => b.val - a.val)
- return tag
+ return branch
result-encoding: string
- name: checkout latest release
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
- ref: refs/tags/${{ steps.find-release.outputs.result }}
+ ref: refs/heads/${{ steps.find-release.outputs.result }}
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v22.x
# Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds
- name: restore package-docs cache
- uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: .cache/package-docs
key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }}
@@ -82,7 +83,7 @@ jobs:
run: yarn backstage-repo-tools package-docs
- name: upload API reference
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: stable-reference
path: type-docs/
@@ -91,7 +92,7 @@ jobs:
# Always save success cache even if there were failures, that way it can be used in re-triggered builds
- name: save package-docs cache
- uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
if: always()
with:
path: .cache/package-docs
@@ -106,7 +107,7 @@ jobs:
run: yarn docusaurus gen-api-docs all
- name: upload OpenAPI API docs
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: stable-openapi-docs
path: docs/**/api/**/*
@@ -124,7 +125,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -132,19 +133,19 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v22.x
# Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds
- name: restore package-docs cache
- uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: .cache/package-docs
key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-${{ github.run_id }}
@@ -155,7 +156,7 @@ jobs:
run: yarn backstage-repo-tools package-docs
- name: upload API reference
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: next-reference
path: type-docs/
@@ -164,7 +165,7 @@ jobs:
# Always save success cache even if there were failures, that way it can be used in re-triggered builds
- name: save package-docs cache
- uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
if: always()
with:
path: .cache/package-docs
@@ -174,7 +175,7 @@ jobs:
run: yarn build-storybook
- name: Upload Storybook
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: storybook
path: dist-storybook
@@ -190,7 +191,7 @@ jobs:
run: yarn docusaurus gen-api-docs all
- name: upload OpenAPI API docs
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: next-openapi-docs
path: docs/**/api/**/*
@@ -218,12 +219,12 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -232,14 +233,14 @@ jobs:
- name: checkout latest release
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
- ref: refs/tags/${{ needs.stable.outputs.release }}
+ ref: refs/heads/${{ needs.stable.outputs.release }}
- name: microsite yarn install
run: yarn install --immutable
working-directory: microsite
- name: download stable OpenAPI API docs
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: stable-openapi-docs
path: docs
@@ -281,7 +282,7 @@ jobs:
working-directory: microsite
- name: download next OpenAPI API docs
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: next-openapi-docs
path: docs
@@ -290,17 +291,17 @@ jobs:
run: yarn build
working-directory: microsite
- - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: storybook
path: microsite/build/storybook
- - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: stable-reference
path: microsite/build/api/stable/
- - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: next-reference
path: microsite/build/api/next/
diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml
index 21f6a92863..fd92bf9b64 100644
--- a/.github/workflows/deploy_packages.yml
+++ b/.github/workflows/deploy_packages.yml
@@ -71,12 +71,12 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
@@ -94,7 +94,7 @@ jobs:
run: yarn backstage-cli config:check --lax
- name: backstage-cli cache
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: .cache/backstage-cli
key: ${{ runner.os }}-v${{ matrix.node-version }}-backstage-cli-${{ github.run_id }}
@@ -102,7 +102,7 @@ jobs:
${{ runner.os }}-v${{ matrix.node-version }}-backstage-cli-
- name: lint
- run: yarn backstage-cli repo lint --successCache --successCacheDir .cache/backstage-cli
+ run: yarn backstage-cli repo lint --success-cache --success-cache-dir .cache/backstage-cli
- name: type checking and declarations
run: yarn tsc:full
@@ -115,7 +115,7 @@ jobs:
- name: test (and upload coverage)
run: |
- yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --coverage --successCache --successCacheDir .cache/backstage-cli
+ yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --coverage --success-cache --success-cache-dir .cache/backstage-cli
env:
BACKSTAGE_TEST_DISABLE_DOCKER: 1
BACKSTAGE_TEST_DATABASE_postgres18_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres18.ports[5432] }}
@@ -125,11 +125,26 @@ jobs:
- name: Discord notification
if: ${{ failure() }}
- uses: Ilshidur/action-discord@d2594079a10f1d6739ee50a2471f0ca57418b554 # 0.4.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
with:
- args: 'Master build failed https://github.com/{{GITHUB_REPOSITORY}}/actions/runs/{{GITHUB_RUN_ID}}'
+ script: |
+ const webhook = process.env.DISCORD_WEBHOOK;
+ if (!webhook) {
+ throw new Error('DISCORD_WEBHOOK secret is not set');
+ }
+ const repo = context.repo.owner + '/' + context.repo.repo;
+ const runId = context.runId;
+ const message = `Master build failed https://github.com/${repo}/actions/runs/${runId}`;
+ const response = await fetch(webhook, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ content: message }),
+ });
+ if (!response.ok) {
+ throw new Error(`Discord webhook request failed: ${response.status} ${response.statusText}`);
+ }
# A separate release build that is only run for commits that are the result of merging the "Version Packages" PR
# We can't re-use the output from the above step, but we'll have a guaranteed node_modules cache and
@@ -147,14 +162,28 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
# Notify maintainers that a new release is ready to be published
- name: Discord notification
- uses: Ilshidur/action-discord@d2594079a10f1d6739ee50a2471f0ca57418b554 # 0.4.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_MAINTAINERS_WEBHOOK }}
with:
- args: 'A new release is ready to be [published](https://github.com/backstage/publishing/actions/workflows/publish-main.yml) from {{GITHUB_SHA}}'
+ script: |
+ const webhook = process.env.DISCORD_WEBHOOK;
+ if (!webhook) {
+ throw new Error('DISCORD_MAINTAINERS_WEBHOOK secret is not set');
+ }
+ const sha = context.sha;
+ const message = `A new release is ready to be [published](https://github.com/backstage/publishing/actions/workflows/publish-main.yml) from ${sha}`;
+ const response = await fetch(webhook, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ content: message }),
+ });
+ if (!response.ok) {
+ throw new Error(`Discord webhook request failed: ${response.status} ${response.statusText}`);
+ }
diff --git a/.github/workflows/issue.yaml b/.github/workflows/issue.yaml
index 791ca69a87..4ba57ebb00 100644
--- a/.github/workflows/issue.yaml
+++ b/.github/workflows/issue.yaml
@@ -16,7 +16,7 @@ jobs:
if: github.repository == 'backstage/backstage'
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/mui-migration-tracker.yml b/.github/workflows/mui-migration-tracker.yml
index cff389f20b..8cd3023cc3 100644
--- a/.github/workflows/mui-migration-tracker.yml
+++ b/.github/workflows/mui-migration-tracker.yml
@@ -18,7 +18,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -26,13 +26,13 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v22.x
diff --git a/.github/workflows/notify_discord-pr.yml b/.github/workflows/notify_discord-pr.yml
new file mode 100644
index 0000000000..f7be8d3d3b
--- /dev/null
+++ b/.github/workflows/notify_discord-pr.yml
@@ -0,0 +1,85 @@
+name: Discord PR notification
+
+on:
+ pull_request_target:
+ types: [opened, ready_for_review]
+
+permissions:
+ actions: none
+ checks: none
+ contents: none
+ deployments: none
+ issues: none
+ packages: none
+ pages: none
+ pull-requests: none
+ repository-projects: none
+ security-events: none
+ statuses: none
+
+jobs:
+ notify:
+ # Only run for non-draft PRs in the main repo
+ if: github.repository == 'backstage/backstage' && github.event.pull_request.draft == false
+ runs-on: ubuntu-latest
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
+ with:
+ egress-policy: audit
+
+ - name: Send Discord notification
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ DISCORD_PULL_REQUESTS_WEBHOOK: ${{ secrets.DISCORD_PULL_REQUESTS_WEBHOOK }}
+ with:
+ script: |
+ if (!process.env.DISCORD_PULL_REQUESTS_WEBHOOK) {
+ throw new Error('DISCORD_PULL_REQUESTS_WEBHOOK secret is not set');
+ }
+
+ const { pull_request: pr, action } = context.payload;
+
+ let description = (pr.body || '').slice(0, 300);
+ if ((pr.body || '').length > 300) {
+ description += '...';
+ }
+
+ // Discord embed titles are limited to 256 characters
+ const titlePrefix = `#${pr.number}: `;
+ const maxTitleLength = 256 - titlePrefix.length;
+ let prTitle = pr.title || '';
+ if (prTitle.length > maxTitleLength) {
+ prTitle = prTitle.slice(0, maxTitleLength - 3) + '...';
+ }
+
+ const isOpened = action === 'opened';
+ const color = isOpened ? 5763719 : 3447003; // green : blue
+ const footer = isOpened ? 'New pull request' : 'Ready for review';
+
+ const payload = {
+ embeds: [{
+ title: `${titlePrefix}${prTitle}`,
+ url: pr.html_url,
+ description,
+ color,
+ author: {
+ name: pr.user.login,
+ url: pr.user.html_url,
+ icon_url: pr.user.avatar_url,
+ },
+ footer: { text: footer },
+ }],
+ allowed_mentions: { parse: [] },
+ };
+
+ const response = await fetch(process.env.DISCORD_PULL_REQUESTS_WEBHOOK, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload),
+ });
+
+ if (!response.ok) {
+ const body = await response.text().catch(() => '');
+ throw new Error(`Discord webhook failed: ${response.status} ${response.statusText} - ${body}`);
+ }
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index c1c55f7abc..29c96b03d4 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -29,7 +29,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -58,7 +58,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: 'Upload artifact'
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: SARIF file
path: results.sarif
@@ -67,6 +67,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
- uses: github/codeql-action/upload-sarif@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3
+ uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
with:
sarif_file: results.sarif
diff --git a/.github/workflows/sync_bui-docs.yml b/.github/workflows/sync_bui-docs.yml
index 2a12f8f72d..58491479cf 100644
--- a/.github/workflows/sync_bui-docs.yml
+++ b/.github/workflows/sync_bui-docs.yml
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -16,13 +16,13 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v22.x
diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml
index 9fc82de604..38a119edbc 100644
--- a/.github/workflows/sync_code-formatting.yml
+++ b/.github/workflows/sync_code-formatting.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -20,12 +20,12 @@ jobs:
fetch-depth: 0
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml
index fb51949fd1..2b1dd1098f 100644
--- a/.github/workflows/sync_dependabot-changesets.yml
+++ b/.github/workflows/sync_dependabot-changesets.yml
@@ -11,21 +11,80 @@ jobs:
if: github.actor == 'dependabot[bot]' && github.repository == 'backstage/backstage'
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
+ - name: Approve yarn.lock-only PRs
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ with:
+ github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
+ script: |
+ const { data: files } = await github.rest.pulls.listFiles({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.payload.pull_request.number,
+ });
+
+ if (files.some(file => !file.filename.endsWith("yarn.lock"))) {
+ console.log("Skipping approval since some files are not yarn.lock");
+ return;
+ }
+
+ await github.rest.pulls.createReview({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.payload.pull_request.number,
+ event: "APPROVE",
+ });
+
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 2
ref: ${{ github.head_ref }}
token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
+ - name: Verify Dependabot branch
+ id: verify
+ run: |
+ if git branch --show-current | grep -q '^dependabot/'; then
+ echo "is_dependabot=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "Not a dependabot branch, skipping"
+ echo "is_dependabot=false" >> "$GITHUB_OUTPUT"
+ fi
- name: Configure Git
+ if: steps.verify.outputs.is_dependabot == 'true'
run: |
git config --global user.email noreply@backstage.io
git config --global user.name 'Github changeset workflow'
+ - name: Setup Node
+ if: steps.verify.outputs.is_dependabot == 'true'
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ with:
+ node-version: 22.x
+ - name: Dedupe lockfiles
+ if: steps.verify.outputs.is_dependabot == 'true'
+ id: dedupe
+ run: |
+ corepack enable
+ changed=false
+
+ for dir in . docs-ui microsite; do
+ if [ "$dir" = "." ]; then lockfile=yarn.lock; else lockfile="$dir/yarn.lock"; fi
+ if git diff --name-only HEAD~1 | grep -q "^${lockfile}$"; then
+ yarn --cwd "$dir" dedupe
+ if ! git diff --quiet "$lockfile"; then
+ git add "$lockfile"
+ changed=true
+ fi
+ fi
+ done
+
+ echo "changed=$changed" >> "$GITHUB_OUTPUT"
- name: Generate changeset
+ if: steps.verify.outputs.is_dependabot == 'true'
+ id: changeset
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
@@ -50,12 +109,6 @@ jobs:
await fs.writeFile(fileName, body);
}
- const branch = await exec.getExecOutput('git branch --show-current');
- if (!branch.stdout.startsWith('dependabot/')) {
- console.log('Not a dependabot branch, skipping');
- return;
- }
-
const diffOutput = await exec.getExecOutput('git diff --name-only HEAD~1');
const diffFiles = diffOutput.stdout.split('\n');
@@ -79,5 +132,9 @@ jobs:
const { stdout: commitMessage } = await exec.getExecOutput('git show --pretty=format:%s -s HEAD');
await createChangeset(fileName, commitMessage, packageNames);
await exec.exec('git', ['add', fileName]);
- await exec.exec('git commit -C HEAD --amend --no-edit');
- await exec.exec('git push --force');
+ core.setOutput('changed', 'true');
+ - name: Commit and push
+ if: steps.verify.outputs.is_dependabot == 'true' && (steps.dedupe.outputs.changed == 'true' || steps.changeset.outputs.changed == 'true')
+ run: |
+ git commit -C HEAD --amend --no-edit --no-verify
+ git push --force
diff --git a/.github/workflows/sync_patch-release.yml b/.github/workflows/sync_patch-release.yml
index e126fe2ab9..628635af1d 100644
--- a/.github/workflows/sync_patch-release.yml
+++ b/.github/workflows/sync_patch-release.yml
@@ -20,7 +20,7 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -32,7 +32,7 @@ jobs:
token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/sync_pull-requests-scheduled.yml b/.github/workflows/sync_pull-requests-scheduled.yml
index 08e6e3b0c0..3854a0f4df 100644
--- a/.github/workflows/sync_pull-requests-scheduled.yml
+++ b/.github/workflows/sync_pull-requests-scheduled.yml
@@ -19,7 +19,7 @@ jobs:
count: ${{ steps.batch.outputs.count }}
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -70,12 +70,12 @@ jobs:
max-parallel: 1
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- name: Backstage PR automation
- uses: backstage/actions/pr-automation@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/pr-automation@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }}
private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }}
diff --git a/.github/workflows/sync_pull-requests-trigger.yml b/.github/workflows/sync_pull-requests-trigger.yml
index 152837b811..fe87517bd1 100644
--- a/.github/workflows/sync_pull-requests-trigger.yml
+++ b/.github/workflows/sync_pull-requests-trigger.yml
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -44,7 +44,7 @@ jobs:
echo "$ACTOR" > ./context/actor
echo "$ACTION" > ./context/action
- - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pr-context
path: context/
diff --git a/.github/workflows/sync_pull-requests.yml b/.github/workflows/sync_pull-requests.yml
index ca2524fe1c..da67b613d7 100644
--- a/.github/workflows/sync_pull-requests.yml
+++ b/.github/workflows/sync_pull-requests.yml
@@ -15,13 +15,13 @@ jobs:
github.event.workflow_run.event == 'pull_request'
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- name: Download PR context
id: download
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
continue-on-error: true
with:
name: pr-context
@@ -48,7 +48,7 @@ jobs:
- name: Backstage PR automation
if: steps.context.outputs.pr-number
- uses: backstage/actions/pr-automation@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/pr-automation@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }}
private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }}
diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml
index dd145df153..62478a7891 100644
--- a/.github/workflows/sync_release-manifest.yml
+++ b/.github/workflows/sync_release-manifest.yml
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -19,13 +19,13 @@ jobs:
ref: v${{ github.event.client_payload.version }}
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v22.x
@@ -81,9 +81,5 @@ jobs:
owner: 'backstage',
repo: 'upgrade-helper-diff',
workflow_id: 'release.yml',
- ref: 'master',
- inputs: {
- version: require('./packages/create-app/package.json').version,
- releaseVersion: require('./package.json').version
- },
+ ref: 'master'
});
diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml
index 8575cab760..04495e606b 100644
--- a/.github/workflows/sync_renovate-changesets.yml
+++ b/.github/workflows/sync_renovate-changesets.yml
@@ -11,10 +11,33 @@ jobs:
if: github.actor == 'renovate[bot]' && github.repository == 'backstage/backstage'
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
+ - name: Approve yarn.lock-only PRs
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ with:
+ github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
+ script: |
+ const { data: files } = await github.rest.pulls.listFiles({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.payload.pull_request.number,
+ });
+
+ if (files.some(file => !file.filename.endsWith("yarn.lock"))) {
+ console.log("Skipping approval since some files are not yarn.lock");
+ return;
+ }
+
+ await github.rest.pulls.createReview({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.payload.pull_request.number,
+ event: "APPROVE",
+ });
+
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml
index 75ad266a8b..b04c5b9ab7 100644
--- a/.github/workflows/sync_snyk-github-issues.yml
+++ b/.github/workflows/sync_snyk-github-issues.yml
@@ -12,19 +12,19 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v22.x
diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml
index a8b364fbb8..a5f5707a52 100644
--- a/.github/workflows/sync_snyk-monitor.yml
+++ b/.github/workflows/sync_snyk-monitor.yml
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -58,6 +58,6 @@ jobs:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
NODE_OPTIONS: --max-old-space-size=7168
- name: Upload Snyk report
- uses: github/codeql-action/upload-sarif@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3
+ uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
with:
sarif_file: snyk.sarif
diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml
index 7df18100b0..ad6239d149 100644
--- a/.github/workflows/sync_version-packages.yml
+++ b/.github/workflows/sync_version-packages.yml
@@ -19,7 +19,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -30,7 +30,7 @@ jobs:
token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -49,8 +49,23 @@ jobs:
- name: Discord notification
if: ${{ failure() }}
- uses: Ilshidur/action-discord@d2594079a10f1d6739ee50a2471f0ca57418b554 # 0.4.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
with:
- args: 'Version Packages Sync Failed https://github.com/{{GITHUB_REPOSITORY}}/actions/runs/{{GITHUB_RUN_ID}}'
+ script: |
+ const webhook = process.env.DISCORD_WEBHOOK;
+ if (!webhook) {
+ throw new Error('DISCORD_WEBHOOK secret is not set');
+ }
+ const repo = context.repo.owner + '/' + context.repo.repo;
+ const runId = context.runId;
+ const message = `Version Packages Sync Failed https://github.com/${repo}/actions/runs/${runId}`;
+ const response = await fetch(webhook, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ content: message }),
+ });
+ if (!response.ok) {
+ throw new Error(`Discord webhook request failed: ${response.status} ${response.statusText}`);
+ }
diff --git a/.github/workflows/verify_accessibility-noop.yml b/.github/workflows/verify_accessibility-noop.yml
index 51caba5acd..8cf164ecfa 100644
--- a/.github/workflows/verify_accessibility-noop.yml
+++ b/.github/workflows/verify_accessibility-noop.yml
@@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml
index 12456caf8e..83d27612ae 100644
--- a/.github/workflows/verify_accessibility.yml
+++ b/.github/workflows/verify_accessibility.yml
@@ -20,17 +20,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v22.x
- name: run Lighthouse CI
diff --git a/.github/workflows/verify_chromatic-noop.yml b/.github/workflows/verify_chromatic-noop.yml
index cb4c96902a..5fe97c8903 100644
--- a/.github/workflows/verify_chromatic-noop.yml
+++ b/.github/workflows/verify_chromatic-noop.yml
@@ -20,7 +20,7 @@ jobs:
name: Chromatic
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/verify_chromatic.yml b/.github/workflows/verify_chromatic.yml
index 74ff9595f4..60cc1745d0 100644
--- a/.github/workflows/verify_chromatic.yml
+++ b/.github/workflows/verify_chromatic.yml
@@ -24,7 +24,7 @@ jobs:
name: Chromatic
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -34,13 +34,13 @@ jobs:
fetch-depth: 10000 # Required to retrieve git history
- name: Use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: Install dependencies
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
@@ -49,7 +49,7 @@ jobs:
- name: Run Chromatic
id: chromatic
- uses: chromaui/action@latest
+ uses: chromaui/action@f191a0224b10e1a38b2091cefb7b7a2337009116 # latest
with:
token: ${{ secrets.GITHUB_TOKEN }}
# projectToken intentionally shared to allow collaborators to run Chromatic on forks
@@ -82,7 +82,7 @@ jobs:
- name: Post Chromatic Link in PR Comment
if: github.event_name == 'pull_request' && steps.chromatic.outputs.url && github.event.pull_request.head.repo.full_name == github.repository
- uses: mshick/add-pr-comment@v2
+ uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2
with:
message: |
## 🎨 Visual Testing with Chromatic
diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml
index a919dff7b0..b9b1c734cf 100644
--- a/.github/workflows/verify_codeql.yml
+++ b/.github/workflows/verify_codeql.yml
@@ -42,7 +42,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -55,7 +55,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3
+ uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -66,7 +66,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3
+ uses: github/codeql-action/autobuild@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -80,4 +80,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3
+ uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml
index af42f25390..792402f7ad 100644
--- a/.github/workflows/verify_docs-quality.yml
+++ b/.github/workflows/verify_docs-quality.yml
@@ -7,29 +7,48 @@ on:
- '**.md'
jobs:
- check-all-files:
+ check-docs:
+ permissions:
+ contents: read
+ pull-requests: read
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- # Vale does not support file excludes, so we use the script to generate a list of files instead
- # The action also does not allow args or a local config file to be passed in, so the files array
- # also contains an "--config=.github/vale/config.ini" option
- - name: generate vale args
- id: generate
- run: echo "args=$(node scripts/check-docs-quality.js --ci-args)" >> $GITHUB_OUTPUT
+ - name: Install vale
+ run: |
+ VALE_VERSION="3.7.1"
+ VALE_CHECKSUM="ba4924bf2c5884499f09b02a6eb3318b29df40a3e81701c0804b9b1aefcd9483"
+ VALE_DIST_DIR="/tmp/vale-dist"
+ VALE_ARCHIVE="vale_${VALE_VERSION}_Linux_64-bit.tar.gz"
- - name: documentation quality check
- uses: errata-ai/vale-action@d89dee975228ae261d22c15adcd03578634d429c # v2.1.1
- with:
- # This also contains --config=.github/vale/config.ini ... :/
- files: '${{ steps.generate.outputs.args }}'
- version: latest
+ mkdir -p "$VALE_DIST_DIR"
+
+ # Download pinned Vale binary
+ curl -fsSL -H "Authorization: token $GH_TOKEN" "https://github.com/errata-ai/vale/releases/download/v${VALE_VERSION}/${VALE_ARCHIVE}" -o "$VALE_DIST_DIR/$VALE_ARCHIVE"
+
+ # Verify the integrity of the downloaded archive
+ echo "$VALE_CHECKSUM $VALE_DIST_DIR/$VALE_ARCHIVE" | sha256sum --check
+
+ mkdir -p "$HOME/.local/bin"
+ tar -xzf "$VALE_DIST_DIR/$VALE_ARCHIVE" -C "$HOME/.local/bin" vale
+ echo "$HOME/.local/bin" >> "$GITHUB_PATH"
+ vale --version
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_TOKEN: ${{ github.token }}
+
+ - name: Get changed files
+ run: |
+ gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" \
+ --paginate -q '.[] | select(.status != "removed") | .filename' > /tmp/pr-files.txt
+ env:
+ GH_TOKEN: ${{ github.token }}
+
+ - name: Documentation quality check
+ run: node scripts/check-docs-quality.js --ci /tmp/pr-files.txt
diff --git a/.github/workflows/verify_e2e-linux-noop.yml b/.github/workflows/verify_e2e-linux-noop.yml
index 38ee55f72c..dbba242bae 100644
--- a/.github/workflows/verify_e2e-linux-noop.yml
+++ b/.github/workflows/verify_e2e-linux-noop.yml
@@ -29,7 +29,7 @@ jobs:
name: E2E Linux ${{ matrix.node-version }}
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml
index c9a0696054..1a0eea2884 100644
--- a/.github/workflows/verify_e2e-linux.yml
+++ b/.github/workflows/verify_e2e-linux.yml
@@ -43,7 +43,7 @@ jobs:
name: E2E Linux ${{ matrix.node-version }}
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -53,7 +53,7 @@ jobs:
run: |
mkdir -p ./context
echo "${{ github.event.pull_request.number }}" > ./context/pr-number
- - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: matrix.node-version == '22.x' && github.event_name == 'pull_request'
with:
name: pr-context
@@ -67,12 +67,12 @@ jobs:
git config --global user.name 'GitHub e2e user'
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml
index 0ea20acc46..370d10310d 100644
--- a/.github/workflows/verify_e2e-techdocs.yml
+++ b/.github/workflows/verify_e2e-techdocs.yml
@@ -32,7 +32,7 @@ jobs:
name: Techdocs
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -42,11 +42,11 @@ jobs:
python-version: '3.9'
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
diff --git a/.github/workflows/verify_e2e-windows-noop.yml b/.github/workflows/verify_e2e-windows-noop.yml
index 4759555905..a04da45600 100644
--- a/.github/workflows/verify_e2e-windows-noop.yml
+++ b/.github/workflows/verify_e2e-windows-noop.yml
@@ -16,7 +16,7 @@ permissions:
jobs:
noop:
- runs-on: windows-2022
+ runs-on: windows-2025
strategy:
matrix:
@@ -25,7 +25,7 @@ jobs:
name: E2E Windows ${{ matrix.node-version }}
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml
index dc9e7ebd9e..0bce3fd594 100644
--- a/.github/workflows/verify_e2e-windows.yml
+++ b/.github/workflows/verify_e2e-windows.yml
@@ -20,7 +20,7 @@ concurrency:
jobs:
build:
- runs-on: windows-2022
+ runs-on: windows-2025
strategy:
matrix:
@@ -33,7 +33,7 @@ jobs:
name: E2E Windows ${{ matrix.node-version }}
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -52,7 +52,7 @@ jobs:
git config --global user.name 'GitHub e2e user'
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -63,7 +63,7 @@ jobs:
python-version: '3.10'
- name: Add msbuild to PATH
- uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0
+ uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0
- name: Setup gyp env
run: |
@@ -80,7 +80,7 @@ jobs:
uses: browser-actions/setup-chrome@803ef6dfb4fdf22089c9563225d95e4a515820a0 # latest
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml
index 84d666f00e..80cd7907e2 100644
--- a/.github/workflows/verify_fossa.yml
+++ b/.github/workflows/verify_fossa.yml
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/verify_microsite-noop.yml b/.github/workflows/verify_microsite-noop.yml
index acab96962c..08eeac4431 100644
--- a/.github/workflows/verify_microsite-noop.yml
+++ b/.github/workflows/verify_microsite-noop.yml
@@ -21,7 +21,7 @@ jobs:
name: Microsite
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml
index 71276ec85d..8578f0d41a 100644
--- a/.github/workflows/verify_microsite.yml
+++ b/.github/workflows/verify_microsite.yml
@@ -28,55 +28,56 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- - name: find latest release
+ - name: find latest release branch
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
id: find-release
with:
script: |
- const { data } = await github.rest.repos.listTags({
+ const data = await octokit.paginate(github.rest.repos.listBranches, {
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100,
})
- const [{tag}] = data
+ const [{branch}] = data
.map(i => i.name)
- .filter(tag => tag.match(/^v\d+\.\d+\.\d+$/))
- .map(tag => ({
- tag,
- val: tag
+ .filter(branch => branch.match(/^patch\/v\d+\.\d+\.\d+$/))
+ .map(branch => ({
+ branch,
+ val: branch
+ .split('/')[1]
.slice(1)
.split('.')
.reduce((val, part) => Number(val) * 1000 + Number(part))
}))
.sort((a, b) => b.val - a.val)
- return tag
+ return branch
result-encoding: string
- name: checkout latest release
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
- ref: refs/tags/${{ steps.find-release.outputs.result }}
+ ref: refs/heads/${{ steps.find-release.outputs.result }}
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v22.x
# Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds
- name: restore package-docs cache
- uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: .cache/package-docs
key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }}
@@ -87,14 +88,14 @@ jobs:
run: yarn backstage-repo-tools package-docs
- name: upload API reference
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: stable-reference
path: type-docs/
# Always save success cache even if there were failures, that way it can be used in re-triggered builds
- name: save package-docs cache
- uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
if: always()
with:
path: .cache/package-docs
@@ -109,7 +110,7 @@ jobs:
run: yarn docusaurus gen-api-docs all
- name: upload OpenAPI API docs
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: stable-openapi-docs
path: docs/**/api/**/*
@@ -126,7 +127,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
@@ -134,19 +135,19 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@c4dd040f963edd226462b5f32efd319ffca12ac5 # v0.7.7
+ uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
with:
cache-prefix: ${{ runner.os }}-v22.x
# Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds
- name: restore package-docs cache
- uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: .cache/package-docs
key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-next-${{ github.run_id }}
@@ -157,14 +158,14 @@ jobs:
run: yarn backstage-repo-tools package-docs
- name: upload API reference
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: next-reference
path: type-docs/
# Always save success cache even if there were failures, that way it can be used in re-triggered builds
- name: save package-docs cache
- uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
+ uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
if: always()
with:
path: .cache/package-docs
@@ -174,7 +175,7 @@ jobs:
run: yarn build-storybook
- name: Upload Storybook
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: storybook
path: dist-storybook
@@ -190,7 +191,7 @@ jobs:
run: yarn docusaurus gen-api-docs all
- name: upload OpenAPI API docs
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: next-openapi-docs
path: docs/**/api/**/*
@@ -212,14 +213,14 @@ jobs:
name: Microsite
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
@@ -246,7 +247,7 @@ jobs:
run: node scripts/verify-lockfile-duplicates.js
- name: download stable OpenAPI API docs
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: stable-openapi-docs
path: docs
@@ -286,7 +287,7 @@ jobs:
run: mkdocs build --strict
- name: download next OpenAPI API docs
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: next-openapi-docs
path: docs
diff --git a/.github/workflows/verify_microsite_accessibility-noop.yml b/.github/workflows/verify_microsite_accessibility-noop.yml
index 87963dd78e..a90b892fae 100644
--- a/.github/workflows/verify_microsite_accessibility-noop.yml
+++ b/.github/workflows/verify_microsite_accessibility-noop.yml
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml
index 76862a9fa7..226f1f71be 100644
--- a/.github/workflows/verify_microsite_accessibility.yml
+++ b/.github/workflows/verify_microsite_accessibility.yml
@@ -15,14 +15,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Use Node.js 22.x
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22.x
diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml
index 7651160795..d8b6695e75 100644
--- a/.github/workflows/verify_windows.yml
+++ b/.github/workflows/verify_windows.yml
@@ -12,7 +12,7 @@ permissions:
jobs:
build:
- runs-on: windows-2022
+ runs-on: windows-2025
strategy:
fail-fast: false
@@ -29,14 +29,14 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
+ uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -46,7 +46,7 @@ jobs:
run: yarn install --immutable
- name: lint
- run: yarn backstage-cli repo lint --successCache
+ run: yarn backstage-cli repo lint --success-cache
- name: type checking and declarations
run: yarn tsc:full
@@ -55,7 +55,7 @@ jobs:
run: yarn lint:type-deps
- name: test
- run: yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --successCache
+ run: yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --success-cache
env:
BACKSTAGE_TEST_DISABLE_DOCKER: 1
diff --git a/.gitignore b/.gitignore
index dec469903e..e93a77b462 100644
--- a/.gitignore
+++ b/.gitignore
@@ -190,3 +190,4 @@ dist-storybook/
# Personal allow patterns etc
.claude/settings.local.json
+.claude/worktrees/
diff --git a/.patches/pr-32973.txt b/.patches/pr-32973.txt
deleted file mode 100644
index 6d83c5d66d..0000000000
--- a/.patches/pr-32973.txt
+++ /dev/null
@@ -1 +0,0 @@
-Fixes the search component to register the first search that happens on initial navigation to the search page.
\ No newline at end of file
diff --git a/.patches/pr-33446.txt b/.patches/pr-33446.txt
new file mode 100644
index 0000000000..ebef1bc0c2
--- /dev/null
+++ b/.patches/pr-33446.txt
@@ -0,0 +1 @@
+Fixed incorrect name of the `legacy-frontend-plugin` template
diff --git a/.patches/pr-33552.txt b/.patches/pr-33552.txt
new file mode 100644
index 0000000000..562c06fc53
--- /dev/null
+++ b/.patches/pr-33552.txt
@@ -0,0 +1 @@
+Add showPaginationLabel prop to TablePagination
\ No newline at end of file
diff --git a/.patches/pr-33597.txt b/.patches/pr-33597.txt
new file mode 100644
index 0000000000..f95fc325b4
--- /dev/null
+++ b/.patches/pr-33597.txt
@@ -0,0 +1 @@
+Fix relative href resolution for BUI link components
\ No newline at end of file
diff --git a/.patches/pr-33614.txt b/.patches/pr-33614.txt
new file mode 100644
index 0000000000..fcf43e0b9d
--- /dev/null
+++ b/.patches/pr-33614.txt
@@ -0,0 +1 @@
+Fix entity relation cards showing only one entity
\ No newline at end of file
diff --git a/.storybook/main.ts b/.storybook/main.ts
index f891cd9692..865ae90848 100644
--- a/.storybook/main.ts
+++ b/.storybook/main.ts
@@ -13,11 +13,12 @@ const isChromatic = process.env.STORYBOOK_STORY_SET === 'chromatic';
// All stories for full development
const allStories = isChromatic
- ? ['packages/ui']
+ ? ['packages/ui', 'plugins/app']
: [
'packages/ui',
'packages/core-components',
'packages/app',
+ 'plugins/app',
'plugins/org',
'plugins/search',
'plugins/search-react',
@@ -54,9 +55,8 @@ export default defineMain({
name: getAbsolutePath('@storybook/react-vite'),
options: {},
},
- features: {
- experimentalComponentsManifest: true,
- experimentalCodeExamples: true, // optional
+ typescript: {
+ reactDocgen: 'react-docgen',
},
viteFinal: async (config, { configType }) => {
// Add Node.js polyfills for browser compatibility
diff --git a/.storybook/modes.ts b/.storybook/modes.ts
index 7087fa35eb..ae4ff76a7c 100644
--- a/.storybook/modes.ts
+++ b/.storybook/modes.ts
@@ -15,4 +15,19 @@ export const allModes = {
themeMode: 'dark',
themeName: 'spotify',
},
+ 'light spotify neutral-1': {
+ themeMode: 'light',
+ themeName: 'spotify',
+ background: 'neutral-1',
+ },
+ 'light spotify neutral-2': {
+ themeMode: 'light',
+ themeName: 'spotify',
+ background: 'neutral-2',
+ },
+ 'light spotify neutral-3': {
+ themeMode: 'light',
+ themeName: 'spotify',
+ background: 'neutral-3',
+ },
} as const;
diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
index 6a0177cafe..916c48951d 100644
--- a/.storybook/preview.tsx
+++ b/.storybook/preview.tsx
@@ -3,10 +3,10 @@ import addonDocs from '@storybook/addon-docs';
import addonThemes from '@storybook/addon-themes';
import addonLinks from '@storybook/addon-links';
import { definePreview } from '@storybook/react-vite';
-import { useEffect } from 'react';
+import React, { useEffect } from 'react';
import { TestApiProvider } from '@backstage/test-utils';
import { AlertDisplay } from '@backstage/core-components';
-import { apis } from './support/apis';
+import { apis, appThemeApi } from './support/apis';
import { useGlobals } from 'storybook/preview-api';
import { UnifiedThemeProvider, themes } from '@backstage/theme';
import { allModes } from './modes';
@@ -19,6 +19,7 @@ import './storybook.css';
// Custom themes
import './themes/spotify.css';
+import { Box } from '../packages/ui/src/components/Box';
export default definePreview({
tags: ['manifest'],
@@ -30,8 +31,8 @@ export default definePreview({
toolbar: {
icon: 'circlehollow',
items: [
- { value: 'light', icon: 'circlehollow', title: 'Light' },
- { value: 'dark', icon: 'circle', title: 'Dark' },
+ { value: 'light', icon: 'sun', title: 'Light' },
+ { value: 'dark', icon: 'moon', title: 'Dark' },
],
dynamicTitle: true,
},
@@ -49,11 +50,26 @@ export default definePreview({
dynamicTitle: true,
},
},
+ background: {
+ name: 'Background',
+ description: 'Global background for components',
+ defaultValue: 'app',
+ toolbar: {
+ icon: 'contrast',
+ items: [
+ { value: 'app', title: 'App Background' },
+ { value: 'neutral-1', title: 'Neutral 1 Background' },
+ { value: 'neutral-2', title: 'Neutral 2 Background' },
+ { value: 'neutral-3', title: 'Neutral 3 Background' },
+ ],
+ },
+ },
},
initialGlobals: {
themeMode: 'light',
themeName: 'backstage',
+ background: 'app',
},
parameters: {
@@ -70,7 +86,14 @@ export default definePreview({
options: {
storySort: {
- order: ['Backstage UI', 'Plugins', 'Layout', 'Navigation'],
+ order: [
+ 'Backstage UI',
+ 'Recipes',
+ 'Guidelines',
+ 'Plugins',
+ 'Layout',
+ 'Navigation',
+ ],
},
},
@@ -114,12 +137,14 @@ export default definePreview({
},
decorators: [
- Story => {
+ (Story, context) => {
const [globals] = useGlobals();
const selectedTheme =
globals.themeMode === 'light' ? themes.light : themes.dark;
const selectedThemeMode = globals.themeMode || 'light';
const selectedThemeName = globals.themeName || 'backstage';
+ const selectedBackground = globals.background || 'app';
+ const isFullscreen = context.parameters.layout === 'fullscreen';
useEffect(() => {
document.body.removeAttribute('data-theme-mode');
@@ -132,7 +157,13 @@ export default definePreview({
};
}, [selectedTheme, selectedThemeName]);
+ useEffect(() => {
+ appThemeApi.setActiveThemeId(selectedThemeMode);
+ }, [selectedThemeMode]);
+
document.body.style.backgroundColor = 'var(--bui-bg-app)';
+ document.body.style.padding =
+ isFullscreen && selectedBackground !== 'app' ? '1rem' : '';
const docsStoryElements = document.getElementsByClassName('docs-story');
Array.from(docsStoryElements).forEach(element => {
(element as HTMLElement).style.backgroundColor = 'var(--bui-bg-app)';
@@ -143,7 +174,19 @@ export default definePreview({
{/* @ts-ignore */}
-
+ {Array.from({
+ length:
+ selectedBackground === 'app'
+ ? 0
+ : parseInt(selectedBackground.split('-')[1], 10),
+ }).reduce(
+ children => (
+
+ {children}
+
+ ),
+ ,
+ )}
);
diff --git a/.storybook/support/apis.js b/.storybook/support/apis.js
index 905c5f2dc8..42085591c0 100644
--- a/.storybook/support/apis.js
+++ b/.storybook/support/apis.js
@@ -1,5 +1,6 @@
import {
AlertApiForwarder,
+ AppThemeSelector,
ErrorAlerter,
ErrorApiForwarder,
GithubAuth,
@@ -13,6 +14,7 @@ import {
import {
alertApiRef,
+ appThemeApiRef,
errorApiRef,
githubAuthApiRef,
gitlabAuthApiRef,
@@ -24,10 +26,16 @@ import {
featureFlagsApiRef,
} from '@backstage/core-plugin-api';
+import { themes } from '@backstage/theme';
+
import { translationApiRef } from '@backstage/core-plugin-api/alpha';
import { MockTranslationApi } from '@backstage/test-utils/alpha';
const configApi = new ConfigReader({});
+export const appThemeApi = AppThemeSelector.createWithStorage([
+ { id: 'light', title: 'Light', variant: 'light', theme: themes.light },
+ { id: 'dark', title: 'Dark', variant: 'dark', theme: themes.dark },
+]);
const featureFlagsApi = new LocalStorageFeatureFlags();
const alertApi = new AlertApiForwarder();
const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder());
@@ -62,6 +70,7 @@ const translationApi = MockTranslationApi.create();
export const apis = [
[configApiRef, configApi],
+ [appThemeApiRef, appThemeApi],
[featureFlagsApiRef, featureFlagsApi],
[alertApiRef, alertApi],
[errorApiRef, errorApi],
diff --git a/.storybook/themes/spotify.css b/.storybook/themes/spotify.css
index b72683f53e..30aa0274d0 100644
--- a/.storybook/themes/spotify.css
+++ b/.storybook/themes/spotify.css
@@ -183,29 +183,6 @@
font-weight: var(--bui-font-weight-regular);
}
- .bui-HeaderToolbar {
- padding-top: var(--bui-space-2);
- padding-inline: var(--bui-space-2);
- }
-
- .bui-HeaderToolbarWrapper {
- border-radius: var(--bui-radius-3);
- padding-inline: var(--bui-space-3);
- border: none;
- }
-
- .bui-HeaderToolbarControls {
- right: calc(var(--bui-space-3) - 1px);
- }
-
- .bui-HeaderTabsWrapper {
- margin-top: var(--bui-space-2);
- margin-inline: var(--bui-space-2);
- border-radius: var(--bui-radius-3);
- padding-inline: var(--bui-space-1);
- border: none;
- }
-
.bui-Input {
border-radius: var(--bui-radius-3);
}
@@ -213,6 +190,10 @@
.bui-Tag {
border-radius: var(--bui-radius-full);
}
+
+ .bui-Container {
+ padding-inline: 0;
+ }
}
[data-theme-mode='light'][data-theme-name='spotify'] {
@@ -232,14 +213,6 @@
--bui-border-success: #53db83;
--bui-ring: rgba(0, 0, 0, 0.2);
-
- .bui-HeaderToolbarWrapper {
- border: 1px solid var(--bui-border-2);
- }
-
- .bui-HeaderTabsWrapper {
- border: 1px solid var(--bui-border-2);
- }
}
[data-theme-mode='dark'][data-theme-name='spotify'] {
@@ -270,3 +243,24 @@
--bui-ring: rgba(255, 255, 255, 0.2);
}
+
+/*
+ * Plugin header (@backstage/ui) and story shell header — kept at the bottom of
+ * this file for easier scanning alongside other component overrides above.
+ */
+[data-theme-name='spotify'] {
+ .bui-PluginHeaderToolbar {
+ padding: 0;
+ height: 32px;
+ border: none;
+ background: none;
+ margin-bottom: var(--bui-space-2);
+ }
+
+ .bui-PluginHeaderTabsWrapper {
+ padding: 0;
+ border: none;
+ background: none;
+ margin-left: -8px;
+ }
+}
diff --git a/ADOPTERS.md b/ADOPTERS.md
index 9a0e2fb0fd..a9d75babf0 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -289,4 +289,5 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/
| [Swiss Mobiliar Insurance Company Ltd.](https://www.mobiliar.ch/) | [Patrick Wyler](mailto:patrick.wyler@mobiliar.ch) and [Beat Winistörfer](mailto:beat.winistoerfer@mobiliar.ch) | The portal provides a unified interface for accessing all relevant DevOps information previously scattered across various locations, enhancing accessibility and clarity for all IT employees. It relies on an internal graph database that enhances the Backstage software catalog with many additional elements. Significant effort has been invested in the visual representation of information through graphs and diagrams, facilitating analysis and improving the understanding of dependencies. |
| [DB Systel GmbH](https://www.dbsystel.de/dbsystel-en/) | [DB Systel GmbH](https://github.com/dbsystel) | Deutsche Bahn's Internal Developer Portal leverages Backstage and Crossplane.io to deliver a fully GitOps-driven onboarding experience for engineering teams across the company. Our Scaffolder ecosystem accelerates platform setup (Artifactory, GitLab, OpenShift), service bootstrapping, automated testing, AI-ready backends, and modern frontend development. We also make heavy use of custom catalog modules and frontend plugins that support developers in checking their provisioned resources or security and compliance of their code. |
| [Danske StatsBaner (DSB)](https://www.dsb.dk/om-dsb/job-og-karriere/) | [Isaac Livingston](mailto:isjl@dsb.dk), [Paula-Catalina Curca](mailto:PACU@DSB.DK), [Muhammad Salman Khan](mailto:MUSK@DSB.DK) | DSB's portal is a central catalog of all our microservices, APIs, and systems. Developers can easily create new components with templates that run in our CNCF based developer platform |
-| [NN Group](https://www.nn-group.com/) | [Flavia Naezer (Product Owner)](mailto:flavia.naezer@nn-group.com), [Vijai Ramcharan (Principal Engineer)](mailto:vijai.ramcharan@nn-group.com) | Our Backstage powered IDP supports engineers and other makers and shapers in our large financial enterprise with the Software Catalog, a Store for infrastructure, TechDocs and Portal Platform with mini App-Store.|
\ No newline at end of file
+| [NN Group](https://www.nn-group.com/) | [Flavia Naezer (Product Owner)](mailto:flavia.naezer@nn-group.com), [Vijai Ramcharan (Principal Engineer)](mailto:vijai.ramcharan@nn-group.com) | Our Backstage powered IDP supports engineers and other makers and shapers in our large financial enterprise with the Software Catalog, a Store for infrastructure, TechDocs and Portal Platform with mini App-Store.|
+| [Puzzle ITC](https://www.puzzle.ch/) | [Christoph Raaflaub](https://github.com/chrira) | Portal to our developer platform. |
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000000..15de219e83
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,57 @@
+Backstage is an open platform for building developer portals. This is a TypeScript monorepo using Yarn workspaces.
+
+## Key Directories
+
+- `/packages`: Core framework packages (prefixed `@backstage/`)
+- `/plugins`: Plugin packages (prefixed `@backstage/plugin-*`)
+- `/packages/app`: Main example app using the new frontend system
+- `/packages/app-legacy`: Example app using the old frontend system
+- `/packages/backend`: Example backend for local development
+- `/docs`: Documentation files
+
+Packages prefixed with `core-` (e.g., `@backstage/core-plugin-api`) are part of the old frontend system. Packages prefixed with `frontend-` (e.g., `@backstage/frontend-plugin-api`) are part of the new frontend system. Packages prefixed with `backend-` (e.g., `@backstage/backend-plugin-api`) are part of the backend system.
+
+## Writing Standards
+
+Changes to the docs should follow the documentation style guide at `/docs/contribute/doc-style-guide.md`.
+
+## Code Standards
+
+The following files contain guidelines for the project:
+
+- `/CONTRIBUTING.md`: comprehensive contribution guidelines.
+- `/STYLE.md`: guidelines for code style.
+- `/REVIEWING.md`: guidelines for pull requests and writing changesets.
+- `/SECURITY.md`: guidelines for security.
+- `/docs/architecture-decisions/`: contains the architecture decisions for the project.
+
+When writing or generating code, always match the existing coding style of each individual package and file. Different packages in the monorepo may have different conventions — consistency within a package is more important than consistency across the repo.
+
+When writing or generating tests, prefer fewer thorough tests with multiple assertions over many small tests. When using React Testing Library, prefer using `screen` and `.findBy*` queries over `waitFor`, and avoid adding test IDs to the implementation.
+
+## Development Flow
+
+Before any of these commands can be run, you need to run `yarn install` in the project root.
+
+- Build: There is no need to build the project during development, and it is verified automatically in the CI pipeline.
+- Test: Use `CI=1 yarn test ` in the project root to run tests. The path can be either a single file or a directory. Always provide a path, avoid running all tests.
+- Type checking: Use `yarn tsc` in the project root to run the type checker. Do not try to run it somewhere else than the project root and do not supply any options.
+- Code formatting: Use `yarn prettier --write <...paths>` to format code. Run it explicitly for file paths that you know are changed, not for entire folders - otherwise it may change formatting of unrelated files.
+- Lint: Use `yarn lint --fix` in the project root to run the linter.
+- API reports: Before submitting a pull request with changes to any package in the workspace, run `yarn build:api-reports` in the project root to generate API reports for all packages.
+- Dev server: Use `yarn start` to run the example app locally (frontend on :3000, backend on :7007).
+- Create: Use `yarn new` to scaffold new plugins, packages, or modules.
+
+You MUST NOT run builds or create a release by running `yarn build`, `yarn changesets version`, or `yarn release` as part of any changes. Builds and releases are made by separate workflows.
+
+All changes that affect the published version of packages in the `/packages` and `/plugins` directories must be accompanied by a changeset. Only non-private packages require changesets. See the guidelines in `/CONTRIBUTING.md#creating-changesets` for information on how to write good changesets. Changesets are stored in the `/.changeset` directory and should be created by writing changeset files directly — never use the changeset CLI. Breaking changes must be accompanied by a `minor` version bump for packages below version `1.0.0`, or a `major` version bump for packages at version `1.0.0` or higher. For non-breaking changes that introduce new APIs or features, use `minor` for packages at version `1.0.0` or higher, and `patch` for packages below `1.0.0`. Each changeset message should be relevant to the specific package it targets and written for Backstage adopters as the audience — avoid referencing internal implementation details. If a change spans multiple packages you often need to create separate changesets to make sure they are tailored to each package.
+
+When creating pull requests, use the template at `/.github/PULL_REQUEST_TEMPLATE.md`.
+
+Never update ESLint, Prettier, or TypeScript configuration files unless specifically requested.
+
+Never make changes to the release notes in `/docs/releases` unless explicitly asked. These document past releases and should not be updated based on newer changes.
+
+## Repository Structure
+
+See `/docs/contribute/project-structure.md` for a detailed description of the repository structure.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index adf04c48e2..5d01886873 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -170,7 +170,9 @@ To help your changes get reviewed and merged smoothly, please keep the following
- We really appreciate contributions that improve clarity or fix outdated information. That said, we generally don’t accept changes that are purely stylistic (e.g., rewording a sentence just to tweak the tone or phrasing). If something is **unclear**, **confusing**, or **factually inaccurate**, those are great opportunities to help!
-Ready to get started? You can find all the documentation files in the [docs](docs) directory! If you have any questions or need help, feel free to reach out in the [Backstage Discord Docs Channel](https://discord.com/channels/687207715902193673/687994765559463940)
+Ready to get started? You can find all the documentation files in the [docs](docs) directory!
+Please take a look through [our style guide](./docs/contribute/doc-style-guide.md) as well for writing style tips.
+If you have any questions or need help, feel free to reach out in the [Backstage Discord Docs Channel](https://discord.com/channels/687207715902193673/687994765559463940).
Thank you in advance for your contributions! We really appreciate it. 🙏
diff --git a/OWNERS.md b/OWNERS.md
index e09a0a24c3..4d535fc4b2 100644
--- a/OWNERS.md
+++ b/OWNERS.md
@@ -283,6 +283,7 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling.
| Carlos Esteban Lopez Jaramillo | VMWare | [luchillo17](https://github.com/luchillo17) | `luchillo17#8777` |
| David Tuite | Roadie.io | [dtuite](https://github.com/dtuite) | `David Tuite (roadie.io)#1010` |
| Deepankumar Loganathan | | [deepan10](https://github.com/deepan10) | `deepan10` |
+| Elaine Mattos | DB Systel | [elaine-mattos](https://github.com/elaine-mattos) | `elaine_mattos` |
| Gabriel Dugny | Believe | [GabDug](https://github.com/GabDug) | `GabDug` |
| Heikki Hellgren | OP Financial Group | [drodil](https://github.com/drodil) | `deathammer` |
| Himanshu Mishra | Harness.io | [OrkoHunter](https://github.com/OrkoHunter) | `OrkoHunter#1520` |
diff --git a/app-config.yaml b/app-config.yaml
index 08cd0b693c..86391154f3 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -27,6 +27,128 @@ app:
packageName: example-app
+ routes:
+ bindings:
+ catalog.viewTechDoc: techdocs.docRoot
+ org.catalogIndex: catalog.catalogIndex
+
+ pluginOverrides:
+ - match:
+ pluginId: pages
+ info:
+ description: 'This description was overridden in app-config.yaml'
+ - match:
+ pluginId: /^catalog(-.*)?$/
+ info:
+ ownerEntityRefs: [cubic-belugas]
+ - match:
+ packageName: '@backstage/plugin-scaffolder'
+ info:
+ ownerEntityRefs: [cubic-belugas]
+
+ extensions:
+ # set availableLanguages example
+ - api:app/app-language:
+ config:
+ availableLanguages: ['en', 'es', 'fr', 'de', 'ja']
+ defaultLanguage: 'en'
+ - entity-card:org/members-list:
+ config:
+ showAggregateMembersToggle: true
+ initialRelationAggregation: aggregated
+ - entity-card:org/ownership:
+ config:
+ ownedKinds: ['Component', 'API', 'System']
+
+ # - apis.plugin.graphiql.browse.gitlab: true
+ # - graphiql-endpoint:graphiql/gitlab: true
+
+ - nav-item:search: false
+ - nav-item:user-settings: false
+ - nav-item:catalog
+ - nav-item:api-docs
+ - nav-item:scaffolder
+ - nav-item:app-visualizer
+
+ # Pages
+ - page:catalog/entity:
+ config:
+ showNavItemIcons: true
+ # default content order for all groups, can be 'title' or 'natural'
+ # defaultContentOrder: title
+ groups:
+ # placing a tab at the beginning
+ - overview:
+ title: Overview
+ # example disabling a default group
+ # - development: false
+ # example overriding a default group title
+ - documentation:
+ title: Docs
+ icon: docs
+ # example aliasing a group
+ # aliases:
+ # - docs
+ - deployment:
+ title: Deployments
+ # example adding a new group
+ - custom:
+ title: Custom
+
+ # Entity page cards
+ - entity-card:catalog/about:
+ config:
+ type: info
+ - entity-card:catalog/labels
+ - entity-card:catalog/links:
+ config:
+ filter:
+ kind: component
+ metadata.links:
+ $exists: true
+ # filter: kind:component has:links
+ type: info
+ # - entity-card:linguist/languages
+ - entity-card:catalog-graph/relations:
+ config:
+ height: 300
+ - entity-card:api-docs/has-apis
+ - entity-card:api-docs/consumed-apis
+ - entity-card:api-docs/provided-apis
+ - entity-card:api-docs/providing-components
+ - entity-card:api-docs/consuming-components
+ # Org Plugin
+ - entity-card:org/group-profile
+ - entity-card:org/members-list
+ - entity-card:org/ownership
+ - entity-card:org/user-profile:
+ config:
+ maxRelations: 5
+ hideIcons: true
+ # - entity-card:azure-devops/readme
+
+ # Entity page contents
+ - entity-content:catalog/overview
+ - entity-content:api-docs/definition
+ - entity-content:api-docs/apis:
+ config:
+ # example overriding the default group
+ group: documentation
+ icon: kind:api
+ - entity-content:techdocs:
+ config:
+ icon: techdocs
+ - entity-content:kubernetes/kubernetes:
+ config:
+ # example disassociating from the default group
+ group: false
+ # - entity-content:azure-devops/pipelines
+ # - entity-content:azure-devops/pull-requests
+ # - entity-content:azure-devops/git-tags
+
+ # Disable the catalog-unprocessed-entities element outside devtools
+ - page:catalog-unprocessed-entities: false
+
backend:
# Used for enabling authentication, secret is shared by all backend plugins
# See https://backstage.io/docs/auth/service-to-service-auth for
@@ -68,6 +190,7 @@ backend:
actions:
pluginSources:
- catalog
+ - scaffolder
# See README.md in the proxy-backend plugin for information on the configuration format
proxy:
endpoints:
diff --git a/beps/0013-backstage-ai/README.md b/beps/0013-backstage-ai/README.md
new file mode 100644
index 0000000000..84e7e31027
--- /dev/null
+++ b/beps/0013-backstage-ai/README.md
@@ -0,0 +1,236 @@
+---
+title: Backstage AI Skills Publishing and Discovery
+status: implementable
+authors:
+ - '@drodil'
+owners:
+ - '@backstage/maintainers'
+project-areas:
+ - core
+creation-date: 2026-03-06
+---
+
+# BEP: AI skills for Backstage development
+
+- [Summary](#summary)
+- [Motivation](#motivation)
+ - [Goals](#goals)
+ - [Non-Goals](#non-goals)
+- [Proposal](#proposal)
+- [Design Details](#design-details)
+- [Release Plan](#release-plan)
+- [Dependencies](#dependencies)
+- [Alternatives](#alternatives)
+
+## Summary
+
+This BEP proposes that curated Backstage AI skills are authored in the main Backstage
+repository and published separately to a well-known endpoint on `backstage.io`. Instead
+of creating a new Backstage-owned repository and custom discovery protocol, Backstage
+will publish skills using the well-known layout already supported by [`skills.sh`](https://skills.sh/).
+
+The published output can be released on its own publishing cadence even though the source
+content lives in the main monorepo.
+
+The primary consumer workflow becomes:
+
+- `npx skills add https://backstage.io` to discover and install all published Backstage skills.
+- `npx skills add https://backstage.io/.well-known/skills/` to install a specific skill.
+
+This keeps Backstage aligned with an existing ecosystem convention, reduces maintenance
+surface area, and still gives adopters a clear, official source for reusable AI guidance.
+Prompt templates, instructions, and supporting files can still be shipped, but they are
+packaged within skill directories instead of through a separate Backstage-specific asset system.
+
+## Motivation
+
+Backstage adopters are increasingly using AI-assisted workflows for plugin development,
+migration, maintenance, and documentation. Today, prompt and skill quality varies
+significantly across adopters, and there is no common, well-known channel for sharing proven
+guidance. A lot of adopters already have assets that help AI-assisted development and
+could be shared with the community.
+
+As a result:
+
+- Teams duplicate effort by recreating similar prompts and instructions.
+- Effective practices are hard to discover and spread.
+- Shared guidance is often published in tool-specific or organization-specific layouts.
+- Security and governance concerns are often handled ad hoc.
+
+Backstage should solve this in the simplest interoperable way. Creating a dedicated asset
+repository, a Backstage-specific discovery index, and custom install/update tooling would
+increase maintenance cost while duplicating functionality that `skills.sh` already provides.
+
+By publishing official Backstage skills through a well-known endpoint on `backstage.io`,
+Backstage can provide a reliable baseline for AI-enabled development while preserving local
+extension and customization and staying aligned with an existing ecosystem workflow.
+
+### Goals
+
+- Author official Backstage AI skills in the main Backstage repository.
+- Publish those skills separately to a well-known endpoint on `backstage.io`.
+- Reuse `skills.sh` discovery and installation behavior instead of inventing a Backstage-specific protocol.
+- Define a stable source layout and review process for Backstage-authored skills.
+- Allow skill directories to include prompts, instructions, and supporting files needed by each skill.
+- Publish contributor and review guidelines so assets remain high quality and safe.
+- Provide clear docs for adopters on how to install and customize the published skills.
+
+### Non-Goals
+
+- Building or hosting a new LLM inference platform.
+- Standardizing model provider APIs across all Backstage AI integrations.
+- Automatically executing AI-generated code in adopter repositories.
+- Enforcing centralized telemetry collection from adopter environments.
+- Replacing existing organization-specific prompt repositories. Local assets remain supported.
+- Solving all possible AI governance policy differences between organizations.
+- Creating a separate Backstage-owned repository just for AI skills.
+- Building a Backstage-specific discovery index, manifest, or asset registry.
+- Supporting bundles, lockfiles, version pinning, or custom update flows in this first iteration.
+
+## Proposal
+
+Introduce three deliverables:
+
+1. A Backstage monorepo location that serves as the source of truth for Backstage-authored AI skills.
+1. A publishing workflow that exposes approved skills at `https://backstage.io/.well-known/skills/`.
+1. Documentation for consuming those skills through `skills.sh`
+
+The Backstage source content will contain:
+
+- Reusable skills for common Backstage engineering tasks.
+- Skill definitions, instructions, prompt fragments, and supporting files packaged per skill.
+- Lightweight metadata needed for publication and review.
+- Validation and quality checks executed in CI.
+
+The primary consumer workflow will use `skills.sh` directly:
+
+- `npx skills add https://backstage.io`
+- `npx skills add https://backstage.io/.well-known/skills/`
+
+If Backstage later adds CLI support, it should remain intentionally thin:
+
+- It should default to the Backstage well-known endpoint.
+- It should delegate to `skills.sh` semantics rather than defining its own registry or manifest.
+- It should not introduce separate bundle, version, or update concepts.
+
+This approach keeps discovery and installation interoperable with the broader ecosystem,
+while still letting Backstage publish curated guidance from an official source.
+
+## Design Details
+
+### Source and publishing design
+
+The source of truth for Backstage skills will live in the Backstage monorepo. The exact
+directory can be finalized during implementation, but it should provide a predictable layout
+for authoring, review, and publication.
+
+Each skill directory should be self-contained and may include:
+
+- `SKILL.md` as the primary skill entry point.
+- Supporting prompt or instruction files referenced by the skill.
+- Optional metadata files used by Backstage publishing workflows.
+
+Published output on `backstage.io` will follow the `skills.sh` well-known layout:
+
+- `https://backstage.io/.well-known/skills/index.json`
+- `https://backstage.io/.well-known/skills//SKILL.md`
+- Additional files under `https://backstage.io/.well-known/skills//...`
+
+The published index should contain entries with the current `skills.sh` well-known shape:
+
+- `name`: skill identifier, matching the published directory name.
+- `description`: short description of the skill.
+- `files`: all published files in the skill directory.
+
+Example published layout:
+
+```text
+.well-known/skills/index.json
+.well-known/skills/plugin-authoring/SKILL.md
+.well-known/skills/plugin-authoring/prompts/review.md
+```
+
+Example index structure:
+
+```json
+{
+ "skills": [
+ {
+ "name": "plugin-authoring",
+ "description": "Guidance for creating and reviewing Backstage plugins.",
+ "files": ["SKILL.md", "prompts/review.md"]
+ }
+ ]
+}
+```
+
+Backstage CI should validate the source skill layout, ensure published file references are
+correct, and generate the well-known endpoint contents as part of the publishing workflow.
+
+### Consumer workflow
+
+The main adoption path is documentation, not new tooling:
+
+- Users install Backstage skills with `npx skills add https://backstage.io`.
+- Users can install a specific published skill by pointing at its well-known skill URL.
+- Any install behavior, target path decisions, or local merge semantics remain the responsibility of `skills.sh`.
+
+### Extensibility
+
+Adopters can:
+
+- Keep local-only skills alongside installed Backstage skills.
+- Customize installed files after adding them to their repository.
+- Publish their own well-known skills endpoints independently of Backstage.
+
+Backstage should not block these local workflows by requiring a proprietary asset format.
+
+## Release Plan
+
+Phase 1: Repository bootstrap
+
+- Define the Backstage monorepo location and review guidelines for skills.
+- Seed the source tree with a small set of high-value Backstage skills.
+- Validate contributor workflow and CI checks for source content.
+
+Phase 2: Publishing rollout
+
+- Implement a publishing workflow that generates `/.well-known/skills/` content for `backstage.io`.
+- Publish `index.json` and the initial skill directories.
+- Validate the published endpoint against `skills.sh` consumption behavior.
+
+Phase 3: General availability
+
+- Publish adoption documentation centered on `npx skills add https://backstage.io`.
+- Define support and ownership model for ongoing skill curation and publishing.
+- Evaluate whether an optional thin `backstage-cli` wrapper adds enough value to justify maintenance.
+
+## Dependencies
+
+- `skills.sh` continuing to support the current well-known endpoint format.
+- A Backstage publishing workflow that can emit static files to `backstage.io/.well-known/skills/`.
+- Agreement on long-term ownership and governance model for Backstage-authored skills.
+- Documentation updates describing authoring, publishing, and adopter usage patterns.
+
+## Alternatives
+
+1. Keep AI assets fully decentralized in individual adopter repositories.
+
+This preserves autonomy but does not solve discovery, reuse, or consistency. It also increases duplicated maintenance
+effort across adopters.
+
+2. Create a separate Backstage-owned repository for AI assets.
+
+This was the original direction, but it adds repository sprawl and still requires a distribution mechanism. Keeping the
+source in the main monorepo simplifies governance and keeps the content closer to the Backstage code and docs it
+describes.
+
+3. Build a Backstage-specific discovery and installation protocol.
+
+This would duplicate functionality already available through `skills.sh` and create a long-term maintenance burden for
+limited short-term benefit.
+
+4. Publish assets only as documentation in the Backstage docs site.
+
+This improves discoverability, but without the well-known `skills.sh` layout it does not provide a direct installation
+path for users.
diff --git a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md
index 4852443ecf..4a7e0ad233 100644
--- a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md
+++ b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md
@@ -1,4 +1,7 @@
-# Running the backend behind a Corporate Proxy
+# Legacy: Running the backend behind a Corporate Proxy
+
+> [!NOTE]
+> On Node.js 22.21.0 or later, you can use Node.js's built-in proxy support instead of the workarounds described here. See the [recommended proxy setup guide](../../../docs/tutorials/corporate-proxy.md) for details.
This article helps you get your backend installation up and running making calls through corporate proxies.
diff --git a/docker-compose.deps.yml b/docker-compose.deps.yml
index fa7937fdf1..5b29031058 100644
--- a/docker-compose.deps.yml
+++ b/docker-compose.deps.yml
@@ -2,7 +2,7 @@
name: backstage
services:
psql:
- image: postgres:17.7
+ image: postgres:17.9
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
@@ -24,7 +24,7 @@ services:
timeout: 3s
retries: 5
opensearch:
- image: opensearchproject/opensearch:2.19.4
+ image: opensearchproject/opensearch:2.19.5
environment:
plugins.security.disabled: true
discovery.type: single-node
diff --git a/docs-ui/package.json b/docs-ui/package.json
index 5cb0aa3e44..30b6d28b45 100644
--- a/docs-ui/package.json
+++ b/docs-ui/package.json
@@ -21,14 +21,14 @@
"@lezer/highlight": "^1.2.1",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
- "@next/mdx": "16.1.6",
+ "@next/mdx": "16.2.1",
"@remixicon/react": "^4.6.0",
"@uiw/codemirror-themes": "^4.23.7",
"@uiw/react-codemirror": "^4.23.7",
"clsx": "^2.1.1",
"html-react-parser": "^5.2.5",
"motion": "^12.4.1",
- "next": "16.1.6",
+ "next": "16.2.1",
"next-mdx-remote-client": "^2.1.2",
"prop-types": "^15.8.1",
"react": "19.2.4",
@@ -43,7 +43,7 @@
"@types/react": "19.2.10",
"@types/react-dom": "19.2.3",
"eslint": "^9",
- "eslint-config-next": "16.1.6",
+ "eslint-config-next": "16.2.1",
"postcss": "^8.5.6",
"postcss-import": "^16.1.1",
"typescript": "^5",
diff --git a/docs-ui/scripts/sync-changelog.mjs b/docs-ui/scripts/sync-changelog.mjs
index fa59db741b..48df906747 100644
--- a/docs-ui/scripts/sync-changelog.mjs
+++ b/docs-ui/scripts/sync-changelog.mjs
@@ -98,6 +98,7 @@ function pascalToKebab(str) {
TextField: 'textfield',
DataTable: 'datatable',
ScrollArea: 'scrollarea',
+ 'Grid.Item': 'grid-item',
};
if (specialCases[str]) {
@@ -285,24 +286,37 @@ async function parseListItem(
// Remove the commit SHA prefix from markdown
let description = fullMarkdown.replace(/^-?\s*[a-f0-9]+:\s*/, '').trim();
- // Extract components using bold marker (standard format)
+ // Extract components from "Affected components" line
+ // Supports: **Affected components:** X, **Affected components**: X, Affected components: X
let components = [];
- const componentMatch = description.match(
- /\*\*Affected components:\*\*\s*([^\n]+)/,
- );
+ const unknownNames = [];
+ const affectedComponentsRe = /\*{2}Affected components?:?\*{2}:?\s*([^\n]+)/i;
+ const affectedComponentsPlainRe = /Affected components?:\s*([^\n]+)/i;
+ const componentMatch =
+ description.match(affectedComponentsRe) ||
+ description.match(affectedComponentsPlainRe);
if (componentMatch) {
const componentNames = componentMatch[1]
.split(',')
- .map(name => name.trim())
+ .map(name => name.trim().replace(/`/g, ''))
.filter(Boolean);
- components = componentNames
- .map(name => mapComponentName(name, validComponents))
- .filter(Boolean);
+ componentNames.forEach(name => {
+ const mapped = mapComponentName(name, validComponents);
+ if (mapped) {
+ components.push(mapped);
+ } else {
+ unknownNames.push(name);
+ }
+ });
- // Strip "**Affected components:**" line from description
+ // Deduplicate
+ components = [...new Set(components)];
+
+ // Strip "Affected components" line from description (all formats)
description = description
- .replace(/\n*\*\*Affected components:\*\*[ \t]*[^\n]+/g, '')
+ .replace(/\n*\*{2}Affected components?:?\*{2}:?\s*[^\n]+/gi, '')
+ .replace(/\n*Affected components?:\s*[^\n]+/gi, '')
.trim();
}
@@ -335,6 +349,7 @@ async function parseListItem(
commitSha,
description,
components,
+ unknownNames,
prs,
breaking,
migration,
@@ -814,6 +829,7 @@ async function main() {
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run');
const force = args.includes('--force');
+ const skipPrs = args.includes('--skip-prs');
const changelogTsPath = path.join(__dirname, '../src/utils/changelog.ts');
const changelogMdPath = path.join(
@@ -878,7 +894,9 @@ async function main() {
}
// Fetch PR numbers for new entries (lazy fetch - only for entries that will be written)
- await fetchPRNumbers(relevantEntries, dryRun);
+ if (!skipPrs) {
+ await fetchPRNumbers(relevantEntries, dryRun);
+ }
// Show summary
console.log('\n📝 New entries by component:');
@@ -896,23 +914,8 @@ async function main() {
console.log(` - ${comp}: ${count} ${count === 1 ? 'entry' : 'entries'}`);
});
- // Warn about unknown components
- const unknownComponents = [];
- allEntries.forEach(entry => {
- const fullText = entry.description;
- const componentMatch = fullText.match(
- /Affected components?:[ \t]*([^\n]+)/i,
- );
- if (componentMatch) {
- const names = componentMatch[1].split(',').map(n => n.trim());
- names.forEach(name => {
- if (!mapComponentName(name, validComponents)) {
- unknownComponents.push(name);
- }
- });
- }
- });
-
+ // Warn about unknown components (collected during extraction)
+ const unknownComponents = allEntries.flatMap(e => e.unknownNames || []);
if (unknownComponents.length > 0) {
console.log('\n⚠️ Unknown components (skipped):');
[...new Set(unknownComponents)].forEach(name => {
diff --git a/docs-ui/src/app/components/accordion/page.mdx b/docs-ui/src/app/components/accordion/page.mdx
index 486c93ec80..90c573b087 100644
--- a/docs-ui/src/app/components/accordion/page.mdx
+++ b/docs-ui/src/app/components/accordion/page.mdx
@@ -140,11 +140,18 @@ Allows multiple panels to be open simultaneously.
}
code={groupMultipleOpenSnippet}
/>
-
+
diff --git a/docs-ui/src/app/components/button-link/props-definition.tsx b/docs-ui/src/app/components/button-link/props-definition.tsx
index deae5364df..e85a363b5a 100644
--- a/docs-ui/src/app/components/button-link/props-definition.tsx
+++ b/docs-ui/src/app/components/button-link/props-definition.tsx
@@ -62,6 +62,11 @@ export const buttonLinkPropDefs: Record = {
>
),
},
+ noTrack: {
+ type: 'boolean',
+ description:
+ 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.',
+ },
onSurface: {
type: 'enum',
values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'],
diff --git a/docs-ui/src/app/components/card/components.tsx b/docs-ui/src/app/components/card/components.tsx
index d10e4d7bce..c4d1f809c2 100644
--- a/docs-ui/src/app/components/card/components.tsx
+++ b/docs-ui/src/app/components/card/components.tsx
@@ -7,6 +7,8 @@ import {
CardFooter,
} from '../../../../../packages/ui/src/components/Card/Card';
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
+import { Button } from '../../../../../packages/ui/src/components/Button/Button';
+import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
export const Default = () => {
return (
@@ -32,6 +34,70 @@ export const HeaderAndBody = () => {
);
};
+export const InteractiveButton = () => {
+ return (
+ {}}
+ label="View component details"
+ >
+ Interactive Card
+
+ Click anywhere on this card to trigger the press handler.
+
+
+
+ Click to interact
+
+
+
+ );
+};
+
+export const InteractiveLink = () => {
+ return (
+
+ Link Card
+ This card navigates to a URL when clicked.
+
+
+ Opens backstage.io
+
+
+
+ );
+};
+
+export const InteractiveWithNestedButtons = () => {
+ return (
+ {}}
+ label="View plugin details"
+ >
+ Card with Actions
+
+ Clicking the card background triggers the card press handler. The
+ buttons below remain independently interactive.
+
+
+
+ {}}>
+ Primary
+
+ {}}>
+ Secondary
+
+
+
+
+ );
+};
+
export const WithLongBody = () => {
return (
diff --git a/docs-ui/src/app/components/card/page.mdx b/docs-ui/src/app/components/card/page.mdx
index 6f410fcd97..eb7ea2f7e4 100644
--- a/docs-ui/src/app/components/card/page.mdx
+++ b/docs-ui/src/app/components/card/page.mdx
@@ -12,8 +12,18 @@ import {
defaultSnippet,
headerAndBodySnippet,
withLongBodySnippet,
+ interactiveButtonSnippet,
+ interactiveLinkSnippet,
+ interactiveWithNestedButtonsSnippet,
} from './snippets';
-import { Default, HeaderAndBody, WithLongBody } from './components';
+import {
+ Default,
+ HeaderAndBody,
+ WithLongBody,
+ InteractiveButton,
+ InteractiveLink,
+ InteractiveWithNestedButtons,
+} from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { CardDefinition } from '../../../utils/definitions';
@@ -81,6 +91,51 @@ When body content exceeds the available height, CardBody scrolls while header an
code={withLongBodySnippet}
/>
+## Interactive cards
+
+Cards can be made interactive without wrapping the entire card in a button or link — which would conflict with any interactive elements inside. Instead, a transparent overlay covers the card surface, and nested buttons and links remain independently clickable above it.
+
+### Button
+
+Pass `onPress` and a `label` (used as the accessible name for screen readers) to make the whole card surface pressable.
+
+ }
+ code={interactiveButtonSnippet}
+/>
+
+### Link
+
+Pass `href` to make the card surface navigate to a URL. `label` is required — it provides the accessible name for the invisible overlay link read by screen readers.
+
+ }
+ code={interactiveLinkSnippet}
+/>
+
+### With nested buttons
+
+Buttons and links inside the card remain independently interactive. Clicking them does not trigger the card's `onPress` handler.
+
+ }
+ code={interactiveWithNestedButtonsSnippet}
+/>
+
-
+
diff --git a/docs-ui/src/app/components/card/props-definition.ts b/docs-ui/src/app/components/card/props-definition.ts
index b3e3776648..7e68d890b6 100644
--- a/docs-ui/src/app/components/card/props-definition.ts
+++ b/docs-ui/src/app/components/card/props-definition.ts
@@ -15,6 +15,25 @@ const optionalChildrenPropDef: Record = {
export const cardPropDefs: Record = {
...optionalChildrenPropDef,
+ onPress: {
+ type: 'enum',
+ values: ['() => void'],
+ responsive: false,
+ description:
+ 'Handler called when the card is pressed. Makes the card interactive as a button. Requires label.',
+ },
+ href: {
+ type: 'string',
+ responsive: false,
+ description:
+ 'URL to navigate to. Makes the card interactive as a link. Mutually exclusive with onPress.',
+ },
+ label: {
+ type: 'string',
+ responsive: false,
+ description:
+ 'Accessible label announced by screen readers for the interactive overlay. Required when onPress or href is provided.',
+ },
...classNamePropDefs,
...stylePropDefs,
};
diff --git a/docs-ui/src/app/components/card/snippets.ts b/docs-ui/src/app/components/card/snippets.ts
index 6f956e5518..86b9df5e0a 100644
--- a/docs-ui/src/app/components/card/snippets.ts
+++ b/docs-ui/src/app/components/card/snippets.ts
@@ -17,6 +17,50 @@ export const headerAndBodySnippet = `Body content without a footer
`;
+export const interactiveButtonSnippet = ` console.log('Card pressed')}
+ label="View component details"
+>
+ Interactive Card
+ Click anywhere on this card to trigger the press handler.
+ Click to interact
+ `;
+
+export const interactiveLinkSnippet = `
+ Link Card
+ This card navigates to a URL when clicked.
+ Opens backstage.io
+ `;
+
+export const interactiveWithNestedButtonsSnippet = `import { Button, Flex } from '@backstage/ui';
+
+ console.log('Card pressed')}
+ label="View plugin details"
+>
+ Card with Actions
+
+ Clicking the card background triggers the card press handler.
+ The buttons below remain independently interactive.
+
+
+
+ console.log('Primary')}>
+ Primary
+
+ console.log('Secondary')}>
+ Secondary
+
+
+
+ `;
+
export const withLongBodySnippet = `import { Text } from '@backstage/ui';
diff --git a/docs-ui/src/app/components/dialog/page.mdx b/docs-ui/src/app/components/dialog/page.mdx
index 12ce2d4135..3989b02730 100644
--- a/docs-ui/src/app/components/dialog/page.mdx
+++ b/docs-ui/src/app/components/dialog/page.mdx
@@ -125,4 +125,12 @@ You can also control the dialog using your own states.
-
+
diff --git a/docs-ui/src/app/components/grid/page.mdx b/docs-ui/src/app/components/grid/page.mdx
index 8862586830..488a67ca0e 100644
--- a/docs-ui/src/app/components/grid/page.mdx
+++ b/docs-ui/src/app/components/grid/page.mdx
@@ -61,4 +61,4 @@ Use Grid.Item to span multiple columns.
-
+
diff --git a/docs-ui/src/app/components/header-page/components.tsx b/docs-ui/src/app/components/header/components.tsx
similarity index 84%
rename from docs-ui/src/app/components/header-page/components.tsx
rename to docs-ui/src/app/components/header/components.tsx
index 791afc5bbe..dc32bdd6cb 100644
--- a/docs-ui/src/app/components/header-page/components.tsx
+++ b/docs-ui/src/app/components/header/components.tsx
@@ -1,6 +1,6 @@
'use client';
-import { HeaderPage } from '../../../../../packages/ui/src/components/HeaderPage/HeaderPage';
+import { Header } from '../../../../../packages/ui/src/components/Header/Header';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
import {
@@ -31,7 +31,7 @@ const breadcrumbs = [
export const WithEverything = () => (
- (
export const WithLongBreadcrumbs = () => (
-
+
);
export const WithTabs = () => (
-
+
);
export const WithCustomActions = () => (
- Custom action}
- />
+
);
export const WithMenu = () => (
-
diff --git a/docs-ui/src/app/components/header-page/page.mdx b/docs-ui/src/app/components/header/page.mdx
similarity index 88%
rename from docs-ui/src/app/components/header-page/page.mdx
rename to docs-ui/src/app/components/header/page.mdx
index ac99fb1af0..0ae9b045aa 100644
--- a/docs-ui/src/app/components/header-page/page.mdx
+++ b/docs-ui/src/app/components/header/page.mdx
@@ -19,11 +19,11 @@ import {
} from './snippets';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
-import { HeaderPageDefinition } from '../../../utils/definitions';
+import { HeaderDefinition } from '../../../utils/definitions';
import { ChangelogComponent } from '@/components/ChangelogComponent';
@@ -61,6 +61,6 @@ Use `customActions` to add a dropdown menu.
} code={withMenu} />
-
+
-
+
diff --git a/docs-ui/src/app/components/header-page/props-definition.tsx b/docs-ui/src/app/components/header/props-definition.tsx
similarity index 68%
rename from docs-ui/src/app/components/header-page/props-definition.tsx
rename to docs-ui/src/app/components/header/props-definition.tsx
index 393141870f..4caacf1c13 100644
--- a/docs-ui/src/app/components/header-page/props-definition.tsx
+++ b/docs-ui/src/app/components/header/props-definition.tsx
@@ -1,5 +1,4 @@
import { classNamePropDefs, type PropDef } from '@/utils/propDefs';
-import { Chip } from '@/components/Chip';
export const headerPagePropDefs: Record = {
title: {
@@ -13,9 +12,9 @@ export const headerPagePropDefs: Record = {
},
tabs: {
type: 'complex',
- description: 'Navigation tabs displayed below the title.',
+ description: 'Navigation items displayed below the title.',
complexType: {
- name: 'HeaderTab[]',
+ name: 'HeaderNavTabItem[]',
properties: {
id: {
type: 'string',
@@ -29,28 +28,29 @@ export const headerPagePropDefs: Record = {
},
href: {
type: 'string',
- required: true,
- description: 'URL to navigate to when tab is clicked.',
- },
- matchStrategy: {
- type: "'exact' | 'prefix'",
required: false,
- default: "'exact'",
- description: (
- <>
- Route matching strategy. Use exact for exact path
- match, prefix if pathname starts with href.
- >
- ),
+ description:
+ 'URL to navigate to when tab is clicked. Present on flat tabs, absent on groups.',
+ },
+ items: {
+ type: 'HeaderNavTab[]',
+ required: false,
+ description:
+ 'Child tabs rendered as a dropdown menu. Present on groups, absent on flat tabs.',
},
},
},
},
+ activeTabId: {
+ type: 'string',
+ description:
+ 'ID of the currently active tab. Can be a flat tab ID or a child tab ID within a group.',
+ },
breadcrumbs: {
type: 'complex',
description: 'Breadcrumb trail displayed above the title.',
complexType: {
- name: 'HeaderPageBreadcrumb[]',
+ name: 'HeaderBreadcrumb[]',
properties: {
label: {
type: 'string',
diff --git a/docs-ui/src/app/components/header-page/snippets.ts b/docs-ui/src/app/components/header/snippets.ts
similarity index 78%
rename from docs-ui/src/app/components/header-page/snippets.ts
rename to docs-ui/src/app/components/header/snippets.ts
index 607675717c..8dfef0e69e 100644
--- a/docs-ui/src/app/components/header-page/snippets.ts
+++ b/docs-ui/src/app/components/header/snippets.ts
@@ -1,8 +1,8 @@
-export const usage = `import { HeaderPage } from '@backstage/ui';
+export const usage = `import { Header } from '@backstage/ui';
- `;
+`;
-export const defaultSnippet = ` `;
-export const withBreadcrumbs = ` `;
-export const withTabs = ` `;
-export const withCustomActions = `Custom action}
/>`;
-export const withMenu = `
diff --git a/docs-ui/src/app/components/link/props-definition.tsx b/docs-ui/src/app/components/link/props-definition.tsx
index f0aa95114e..951d67e8b4 100644
--- a/docs-ui/src/app/components/link/props-definition.tsx
+++ b/docs-ui/src/app/components/link/props-definition.tsx
@@ -38,7 +38,7 @@ export const linkPropDefs: Record = {
'body-small',
'body-x-small',
],
- default: 'body',
+ default: 'body-medium',
responsive: true,
description:
'Typography style. Title variants for headings, body for paragraph text.',
@@ -56,7 +56,7 @@ export const linkPropDefs: Record = {
},
color: {
type: 'enum',
- values: ['primary', 'secondary', 'danger', 'warning', 'success'],
+ values: ['primary', 'secondary', 'danger', 'warning', 'success', 'info'],
default: 'primary',
responsive: true,
description:
@@ -68,6 +68,11 @@ export const linkPropDefs: Record = {
'Truncates text with ellipsis when it overflows its container.',
default: 'false',
},
+ noTrack: {
+ type: 'boolean',
+ description:
+ 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.',
+ },
standalone: {
type: 'boolean',
description: 'Removes underline by default. Underline appears on hover.',
diff --git a/docs-ui/src/app/components/list/components.tsx b/docs-ui/src/app/components/list/components.tsx
new file mode 100644
index 0000000000..5630027f94
--- /dev/null
+++ b/docs-ui/src/app/components/list/components.tsx
@@ -0,0 +1,159 @@
+'use client';
+
+import {
+ List,
+ ListRow,
+} from '../../../../../packages/ui/src/components/List/List';
+import { MenuItem } from '../../../../../packages/ui/src/components/Menu/Menu';
+import {
+ TagGroup,
+ Tag,
+} from '../../../../../packages/ui/src/components/TagGroup/TagGroup';
+import { useState } from 'react';
+import type { Selection } from 'react-aria-components';
+import {
+ RiJavascriptLine,
+ RiReactjsLine,
+ RiShipLine,
+ RiTerminalLine,
+ RiCodeLine,
+ RiDeleteBinLine,
+ RiEdit2Line,
+ RiShareBoxLine,
+} from '@remixicon/react';
+
+const items = [
+ {
+ id: 'react',
+ label: 'React',
+ description: 'A JavaScript library for building user interfaces',
+ icon: ,
+ tags: ['frontend', 'ui'],
+ },
+ {
+ id: 'typescript',
+ label: 'TypeScript',
+ description: 'Typed superset of JavaScript',
+ icon: ,
+ tags: ['typed', 'js'],
+ },
+ {
+ id: 'javascript',
+ label: 'JavaScript',
+ description: 'The language of the web',
+ icon: ,
+ tags: ['web'],
+ },
+ {
+ id: 'rust',
+ label: 'Rust',
+ description: 'Systems programming with memory safety',
+ icon: ,
+ tags: ['systems', 'fast'],
+ },
+ {
+ id: 'go',
+ label: 'Go',
+ description: 'Simple, fast, and reliable',
+ icon: ,
+ tags: ['backend'],
+ },
+];
+
+const menuItems = (
+ <>
+ }>Edit
+ }>Share
+ } color="danger">
+ Delete
+
+ >
+);
+
+export const Default = () => (
+
+ {item => (
+
+ {item.tags.map(tag => (
+ {tag}
+ ))}
+
+ }
+ >
+ {item.label}
+
+ )}
+
+);
+
+export const WithIcons = () => (
+
+ {item => (
+
+ {item.label}
+
+ )}
+
+);
+
+export const WithDescription = () => (
+
+ {item => (
+
+ {item.label}
+
+ )}
+
+);
+
+export const SelectionModeSingle = () => {
+ const [selected, setSelected] = useState(new Set(['react']));
+
+ return (
+
+ {item => {item.label} }
+
+ );
+};
+
+export const SelectionModeMultiple = () => {
+ const [selected, setSelected] = useState(
+ new Set(['react', 'typescript']),
+ );
+
+ return (
+
+ {item => {item.label} }
+
+ );
+};
+
+export const Disabled = () => (
+
+ {item => {item.label} }
+
+);
diff --git a/docs-ui/src/app/components/list/page.mdx b/docs-ui/src/app/components/list/page.mdx
new file mode 100644
index 0000000000..26353de81c
--- /dev/null
+++ b/docs-ui/src/app/components/list/page.mdx
@@ -0,0 +1,103 @@
+import { PropsTable } from '@/components/PropsTable';
+import { Snippet } from '@/components/Snippet';
+import { CodeBlock } from '@/components/CodeBlock';
+import { ReactAriaLink } from '@/components/ReactAriaLink';
+import {
+ Default,
+ WithIcons,
+ WithDescription,
+ SelectionModeSingle,
+ SelectionModeMultiple,
+ Disabled,
+} from './components';
+import { listPropDefs, listRowPropDefs } from './props-definition';
+import {
+ usage,
+ preview,
+ withIcons,
+ withDescription,
+ selectionModeSingle,
+ selectionModeMultiple,
+ disabled,
+} from './snippets';
+import { PageTitle } from '@/components/PageTitle';
+import { Theming } from '@/components/Theming';
+import { ListDefinition, ListRowDefinition } from '../../../utils/definitions';
+import { ChangelogComponent } from '@/components/ChangelogComponent';
+
+export const reactAriaUrls = {
+ gridList: 'https://react-aria.adobe.com/GridList',
+};
+
+
+
+ } code={preview} />
+
+## Usage
+
+
+
+## API reference
+
+### List
+
+Container for a list of interactive rows.
+
+
+
+
+
+### ListRow
+
+Individual row within a List.
+
+
+
+
+
+## Examples
+
+### With icons
+
+ } code={withIcons} />
+
+### With description
+
+ }
+ code={withDescription}
+/>
+
+### Single selection
+
+ }
+ code={selectionModeSingle}
+/>
+
+### Multiple selection
+
+ }
+ code={selectionModeMultiple}
+/>
+
+### Disabled items
+
+ } code={disabled} />
+
+
+
+
diff --git a/docs-ui/src/app/components/list/props-definition.tsx b/docs-ui/src/app/components/list/props-definition.tsx
new file mode 100644
index 0000000000..b021fc6e0a
--- /dev/null
+++ b/docs-ui/src/app/components/list/props-definition.tsx
@@ -0,0 +1,84 @@
+import {
+ classNamePropDefs,
+ childrenPropDefs,
+ type PropDef,
+} from '@/utils/propDefs';
+
+export const listPropDefs: Record = {
+ items: {
+ type: 'enum',
+ values: ['Iterable'],
+ description: 'Item objects in the collection.',
+ },
+ renderEmptyState: {
+ type: 'enum',
+ values: ['(props: GridListRenderProps) => ReactNode'],
+ description: 'Content to display when the collection is empty.',
+ },
+ selectionMode: {
+ type: 'enum',
+ values: ['none', 'single', 'multiple'],
+ description: 'The type of selection allowed.',
+ },
+ selectedKeys: {
+ type: 'enum',
+ values: ['all', 'Iterable'],
+ description: 'The currently selected keys (controlled).',
+ },
+ defaultSelectedKeys: {
+ type: 'enum',
+ values: ['all', 'Iterable'],
+ description: 'The initial selected keys (uncontrolled).',
+ },
+ disabledKeys: {
+ type: 'enum',
+ values: ['Iterable'],
+ description: 'Keys of items that should be disabled.',
+ },
+ onSelectionChange: {
+ type: 'enum',
+ values: ['(keys: Selection) => void'],
+ description: 'Handler called when the selection changes.',
+ },
+ ...childrenPropDefs,
+ ...classNamePropDefs,
+};
+
+export const listRowPropDefs: Record = {
+ id: {
+ type: 'string',
+ description: 'Unique identifier for the row.',
+ },
+ textValue: {
+ type: 'string',
+ description:
+ 'Text value for accessibility. Derived from children if string.',
+ },
+ icon: {
+ type: 'enum',
+ values: ['ReactNode'],
+ description: 'Icon displayed before the row label.',
+ },
+ description: {
+ type: 'string',
+ description: 'Secondary description text displayed below the label.',
+ },
+ isDisabled: {
+ type: 'boolean',
+ description: 'Whether the row is disabled.',
+ },
+ menuItems: {
+ type: 'enum',
+ values: ['ReactNode'],
+ description:
+ 'Menu items rendered inside an automatically managed dropdown. Pass MenuItem nodes.',
+ },
+ customActions: {
+ type: 'enum',
+ values: ['ReactNode'],
+ description:
+ 'Custom action elements displayed on the right side of the row, e.g. tags.',
+ },
+ ...childrenPropDefs,
+ ...classNamePropDefs,
+};
diff --git a/docs-ui/src/app/components/list/snippets.ts b/docs-ui/src/app/components/list/snippets.ts
new file mode 100644
index 0000000000..ccaaf7e28d
--- /dev/null
+++ b/docs-ui/src/app/components/list/snippets.ts
@@ -0,0 +1,72 @@
+export const usage = `import { List, ListRow } from '@backstage/ui';
+
+
+ {item => {item.label} }
+
`;
+
+export const preview = `
+ {item => (
+
+ {item.tags.map(tag => (
+ {tag}
+ ))}
+
+ }
+ >
+ {item.label}
+
+ )}
+
`;
+
+export const withIcons = `
+ {item => (
+
+ {item.label}
+
+ )}
+
`;
+
+export const withDescription = `
+ {item => (
+
+ {item.label}
+
+ )}
+
`;
+
+export const selectionModeSingle = `const [selected, setSelected] = useState(new Set(['react']));
+
+
+ {item => {item.label} }
+
`;
+
+export const selectionModeMultiple = `const [selected, setSelected] = useState(new Set(['react', 'typescript']));
+
+
+ {item => {item.label} }
+
`;
+
+export const disabled = `
+ {item => {item.label} }
+
`;
diff --git a/docs-ui/src/app/components/menu/page.mdx b/docs-ui/src/app/components/menu/page.mdx
index f1de264d73..158598fe71 100644
--- a/docs-ui/src/app/components/menu/page.mdx
+++ b/docs-ui/src/app/components/menu/page.mdx
@@ -226,4 +226,17 @@ Set `selectionMode="multiple"` to allow multiple selections.
-
+
diff --git a/docs-ui/src/app/components/menu/props-definition.tsx b/docs-ui/src/app/components/menu/props-definition.tsx
index 5e11b22737..f6cb88b3e2 100644
--- a/docs-ui/src/app/components/menu/props-definition.tsx
+++ b/docs-ui/src/app/components/menu/props-definition.tsx
@@ -309,6 +309,11 @@ export const menuItemPropDefs: Record = {
type: 'boolean',
description: 'Whether the item is disabled.',
},
+ noTrack: {
+ type: 'boolean',
+ description:
+ 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.',
+ },
textValue: {
type: 'string',
description: 'Text used for typeahead and accessibility.',
diff --git a/docs-ui/src/app/components/plugin-header/components.tsx b/docs-ui/src/app/components/plugin-header/components.tsx
index b3d831e718..045d0956c4 100644
--- a/docs-ui/src/app/components/plugin-header/components.tsx
+++ b/docs-ui/src/app/components/plugin-header/components.tsx
@@ -1,7 +1,7 @@
'use client';
import { PluginHeader } from '../../../../../packages/ui/src/components/PluginHeader/PluginHeader';
-import { HeaderPage } from '../../../../../packages/ui/src/components/HeaderPage/HeaderPage';
+import { Header } from '../../../../../packages/ui/src/components/Header/Header';
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
import { MemoryRouter } from 'react-router-dom';
@@ -58,11 +58,11 @@ export const WithAllOptions = () => (
);
-export const WithHeaderPage = () => (
+export const WithHeader = () => (
<>
- Custom action}
diff --git a/docs-ui/src/app/components/plugin-header/page.mdx b/docs-ui/src/app/components/plugin-header/page.mdx
index 5f8e02bfc4..cbe4dfe141 100644
--- a/docs-ui/src/app/components/plugin-header/page.mdx
+++ b/docs-ui/src/app/components/plugin-header/page.mdx
@@ -4,7 +4,7 @@ import { Snippet } from '@/components/Snippet';
import {
WithAllOptionsAndTabs,
WithAllOptions,
- WithHeaderPage,
+ WithHeader,
} from './components';
import { headerPropDefs } from './props-definition';
import {
@@ -12,7 +12,7 @@ import {
simple,
defaultSnippet,
withTabs,
- withHeaderPage,
+ withHeader,
} from './snippets';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
@@ -46,11 +46,11 @@ Tabs use React Router and highlight automatically based on the current route.
} code={withTabs} open />
-### Plugin header with HeaderPage
+### Plugin header with Header
-Combine with [HeaderPage](/components/header-page) for multi-level navigation.
+Combine with [Header](/components/header) for multi-level navigation.
- } code={withHeaderPage} open />
+ } code={withHeader} open />
diff --git a/docs-ui/src/app/components/plugin-header/snippets.ts b/docs-ui/src/app/components/plugin-header/snippets.ts
index 4e9518af73..874a16184a 100644
--- a/docs-ui/src/app/components/plugin-header/snippets.ts
+++ b/docs-ui/src/app/components/plugin-header/snippets.ts
@@ -43,7 +43,7 @@ export const withTabs = ` `;
-export const withHeaderPage = `
-
-
+
diff --git a/docs-ui/src/app/components/search-autocomplete/components.tsx b/docs-ui/src/app/components/search-autocomplete/components.tsx
new file mode 100644
index 0000000000..4052ee0e3f
--- /dev/null
+++ b/docs-ui/src/app/components/search-autocomplete/components.tsx
@@ -0,0 +1,156 @@
+'use client';
+
+import { useState } from 'react';
+import {
+ SearchAutocomplete,
+ SearchAutocompleteItem,
+} from '../../../../../packages/ui/src/components/SearchAutocomplete/SearchAutocomplete';
+import { PluginHeader } from '../../../../../packages/ui/src/components/PluginHeader/PluginHeader';
+import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
+import { Text } from '../../../../../packages/ui/src/components/Text/Text';
+import { MemoryRouter } from 'react-router-dom';
+
+const fruits = [
+ { id: 'apple', name: 'Apple', description: 'A round fruit' },
+ { id: 'banana', name: 'Banana', description: 'A yellow curved fruit' },
+ { id: 'blueberry', name: 'Blueberry', description: 'A small blue berry' },
+ { id: 'cherry', name: 'Cherry', description: 'A small red stone fruit' },
+ { id: 'grape', name: 'Grape', description: 'Grows in clusters on vines' },
+ { id: 'lemon', name: 'Lemon', description: 'A sour yellow citrus fruit' },
+ { id: 'mango', name: 'Mango', description: 'A tropical stone fruit' },
+ { id: 'orange', name: 'Orange', description: 'A citrus fruit' },
+ { id: 'peach', name: 'Peach', description: 'A fuzzy stone fruit' },
+ {
+ id: 'strawberry',
+ name: 'Strawberry',
+ description: 'A red fruit with seeds on its surface',
+ },
+];
+
+export const WithItems = () => {
+ const [inputValue, setInputValue] = useState('');
+
+ const filtered = fruits.filter(fruit =>
+ fruit.name.toLowerCase().includes(inputValue.toLowerCase()),
+ );
+
+ return (
+
+ {filtered.map(fruit => (
+
+ {fruit.name}
+
+ ))}
+
+ );
+};
+
+export const WithRichContent = () => {
+ const [inputValue, setInputValue] = useState('');
+
+ const filtered = fruits.filter(fruit =>
+ fruit.name.toLowerCase().includes(inputValue.toLowerCase()),
+ );
+
+ return (
+
+ {filtered.map(fruit => (
+
+ {fruit.name}
+
+ {fruit.description}
+
+
+ ))}
+
+ );
+};
+
+export const InHeader = () => {
+ const [inputValue, setInputValue] = useState('');
+
+ const filtered = fruits.filter(fruit =>
+ fruit.name.toLowerCase().includes(inputValue.toLowerCase()),
+ );
+
+ return (
+
+
+
+ {filtered.map(fruit => (
+
+ {fruit.name}
+
+ ))}
+
+ >
+ }
+ />
+
+ );
+};
+
+export const WithSelection = () => {
+ const [inputValue, setInputValue] = useState('');
+ const [selected, setSelected] = useState(null);
+
+ const filtered = fruits.filter(fruit =>
+ fruit.name.toLowerCase().includes(inputValue.toLowerCase()),
+ );
+
+ return (
+
+
+ {filtered.map(fruit => (
+ {
+ setSelected(fruit.name);
+ setInputValue('');
+ }}
+ >
+ {fruit.name}
+
+ ))}
+
+ Last selected: {selected ?? 'none'}
+
+ );
+};
diff --git a/docs-ui/src/app/components/search-autocomplete/page.mdx b/docs-ui/src/app/components/search-autocomplete/page.mdx
new file mode 100644
index 0000000000..ed307f92e2
--- /dev/null
+++ b/docs-ui/src/app/components/search-autocomplete/page.mdx
@@ -0,0 +1,97 @@
+import { PropsTable } from '@/components/PropsTable';
+import { Snippet } from '@/components/Snippet';
+import { ReactAriaLink } from '@/components/ReactAriaLink';
+import {
+ searchAutocompletePropDefs,
+ searchAutocompleteItemPropDefs,
+} from './props-definition';
+import {
+ usage,
+ defaultSnippet,
+ withRichContent,
+ inHeader,
+ withSelection,
+} from './snippets';
+import {
+ WithItems,
+ WithRichContent,
+ InHeader,
+ WithSelection,
+} from './components';
+import { PageTitle } from '@/components/PageTitle';
+import { Theming } from '@/components/Theming';
+import { ChangelogComponent } from '@/components/ChangelogComponent';
+import { CodeBlock } from '@/components/CodeBlock';
+import {
+ SearchAutocompleteDefinition,
+ SearchAutocompleteItemDefinition,
+} from '../../../utils/definitions';
+
+export const reactAriaUrls = {
+ autocomplete: 'https://react-aria.adobe.com/Autocomplete',
+};
+
+
+
+ } code={defaultSnippet} />
+
+## Usage
+
+
+
+## API reference
+
+### SearchAutocomplete
+
+
+
+
+
+### SearchAutocompleteItem
+
+Individual result item within the autocomplete list.
+
+
+
+## Examples
+
+### With rich content
+
+Here's a view when items include a title and description.
+
+ }
+ code={withRichContent}
+/>
+
+### In header
+
+Use `popoverWidth` to control the dropdown width when placed in constrained layouts like `PluginHeader`.
+
+ } code={inHeader} />
+
+### With selection
+
+Use `onAction` on items to handle selection and reset the input.
+
+ }
+ code={withSelection}
+/>
+
+
+
+
+
+
diff --git a/docs-ui/src/app/components/search-autocomplete/props-definition.tsx b/docs-ui/src/app/components/search-autocomplete/props-definition.tsx
new file mode 100644
index 0000000000..7ddaea61e2
--- /dev/null
+++ b/docs-ui/src/app/components/search-autocomplete/props-definition.tsx
@@ -0,0 +1,97 @@
+import {
+ classNamePropDefs,
+ stylePropDefs,
+ type PropDef,
+} from '@/utils/propDefs';
+import { Chip } from '@/components/Chip';
+
+export const searchAutocompletePropDefs: Record = {
+ 'aria-label': {
+ type: 'string',
+ description: 'Accessible label for the search input.',
+ },
+ 'aria-labelledby': {
+ type: 'string',
+ description: 'ID of the element that labels the search input.',
+ },
+ inputValue: {
+ type: 'string',
+ description: 'The current input value (controlled).',
+ },
+ onInputChange: {
+ type: 'enum',
+ values: ['(value: string) => void'],
+ description: 'Handler called when the input value changes.',
+ },
+ placeholder: {
+ type: 'string',
+ default: 'Search',
+ description:
+ 'Placeholder text shown when the input is empty. Also used as the accessible label when neither aria-label nor aria-labelledby is provided.',
+ },
+ size: {
+ type: 'enum',
+ values: ['small', 'medium'],
+ default: 'small',
+ responsive: true,
+ description: (
+ <>
+ Visual size of the input. Use small for inline or dense
+ layouts, medium for standalone fields.
+ >
+ ),
+ },
+ isLoading: {
+ type: 'boolean',
+ default: 'false',
+ description:
+ 'Whether results are currently loading. Dims existing results and announces loading state to screen readers.',
+ },
+ popoverWidth: {
+ type: 'string',
+ description:
+ 'Width of the results popover. Accepts any CSS width value. Matches the input width when not set.',
+ },
+ popoverPlacement: {
+ type: 'enum',
+ values: ['bottom start', 'bottom end', 'top start', 'top end'],
+ default: 'bottom start',
+ description: 'Placement of the results popover relative to the input.',
+ },
+ defaultOpen: {
+ type: 'boolean',
+ default: 'false',
+ description: 'Whether the results popover is open by default.',
+ },
+ children: {
+ type: 'enum',
+ values: ['ReactNode'],
+ description: 'The result items to render inside the autocomplete.',
+ },
+ ...classNamePropDefs,
+ ...stylePropDefs,
+};
+
+export const searchAutocompleteItemPropDefs: Record = {
+ id: {
+ type: 'string',
+ description: 'Unique identifier for the item.',
+ },
+ textValue: {
+ type: 'string',
+ description:
+ 'Plain text value used for keyboard navigation and accessibility.',
+ },
+ onAction: {
+ type: 'enum',
+ values: ['() => void'],
+ description: 'Handler called when the item is selected.',
+ },
+ children: {
+ type: 'enum',
+ values: ['ReactNode'],
+ required: true,
+ description: 'Content to render inside the item.',
+ },
+ ...classNamePropDefs,
+};
diff --git a/docs-ui/src/app/components/search-autocomplete/snippets.ts b/docs-ui/src/app/components/search-autocomplete/snippets.ts
new file mode 100644
index 0000000000..fe799ff828
--- /dev/null
+++ b/docs-ui/src/app/components/search-autocomplete/snippets.ts
@@ -0,0 +1,139 @@
+export const usage = `import { SearchAutocomplete, SearchAutocompleteItem } from '@backstage/ui';
+
+
+ {items.map(item => (
+
+ {item.name}
+
+ ))}
+ `;
+
+export const defaultSnippet = `const fruits = [
+ { id: 'apple', name: 'Apple' },
+ { id: 'banana', name: 'Banana' },
+ { id: 'cherry', name: 'Cherry' },
+ { id: 'grape', name: 'Grape' },
+ { id: 'orange', name: 'Orange' },
+];
+
+function Example() {
+ const [inputValue, setInputValue] = useState('');
+
+ const filtered = fruits.filter(fruit =>
+ fruit.name.toLowerCase().includes(inputValue.toLowerCase()),
+ );
+
+ return (
+
+ {filtered.map(fruit => (
+
+ {fruit.name}
+
+ ))}
+
+ );
+}`;
+
+export const withRichContent = `const fruits = [
+ { id: 'apple', name: 'Apple', description: 'A round fruit' },
+ { id: 'banana', name: 'Banana', description: 'A yellow curved fruit' },
+ { id: 'cherry', name: 'Cherry', description: 'A small red stone fruit' },
+];
+
+function Example() {
+ const [inputValue, setInputValue] = useState('');
+
+ const filtered = fruits.filter(fruit =>
+ fruit.name.toLowerCase().includes(inputValue.toLowerCase()),
+ );
+
+ return (
+
+ {filtered.map(fruit => (
+
+ {fruit.name}
+
+ {fruit.description}
+
+
+ ))}
+
+ );
+}`;
+
+export const inHeader = `
+
+ {filtered.map(fruit => (
+
+ {fruit.name}
+
+ ))}
+
+ >
+ }
+/>`;
+
+export const withSelection = `function Example() {
+ const [inputValue, setInputValue] = useState('');
+ const [selected, setSelected] = useState(null);
+
+ const filtered = fruits.filter(fruit =>
+ fruit.name.toLowerCase().includes(inputValue.toLowerCase()),
+ );
+
+ return (
+
+
+ {filtered.map(fruit => (
+ {
+ setSelected(fruit.name);
+ setInputValue('');
+ }}
+ >
+ {fruit.name}
+
+ ))}
+
+ Last selected: {selected ?? 'none'}
+
+ );
+}`;
diff --git a/docs-ui/src/app/components/slider/components.tsx b/docs-ui/src/app/components/slider/components.tsx
new file mode 100644
index 0000000000..55da7279c1
--- /dev/null
+++ b/docs-ui/src/app/components/slider/components.tsx
@@ -0,0 +1,85 @@
+'use client';
+
+import { Slider } from '../../../../../packages/ui/src/components/Slider';
+
+export const SingleValue = () => {
+ return (
+
+ );
+};
+
+export const Default = () => {
+ return (
+
+ );
+};
+
+export const WithCustomRange = () => {
+ return (
+
+ );
+};
+
+export const WithFormattedValues = () => {
+ return (
+
+ );
+};
+
+export const WithDescription = () => {
+ return (
+
+ );
+};
+
+export const Required = () => {
+ return (
+
+ );
+};
+
+export const Disabled = () => {
+ return (
+
+ );
+};
diff --git a/docs-ui/src/app/components/slider/page.mdx b/docs-ui/src/app/components/slider/page.mdx
new file mode 100644
index 0000000000..c46185f9f6
--- /dev/null
+++ b/docs-ui/src/app/components/slider/page.mdx
@@ -0,0 +1,109 @@
+import { PropsTable } from '@/components/PropsTable';
+import { Snippet } from '@/components/Snippet';
+import { CodeBlock } from '@/components/CodeBlock';
+import { ReactAriaLink } from '@/components/ReactAriaLink';
+import { sliderPropDefs } from './props-definition';
+import {
+ snippetUsage,
+ singleValueSnippet,
+ defaultSnippet,
+ withCustomRangeSnippet,
+ withFormattedValuesSnippet,
+ withDescriptionSnippet,
+ requiredSnippet,
+ disabledSnippet,
+} from './snippets';
+import {
+ SingleValue,
+ Default,
+ WithCustomRange,
+ WithFormattedValues,
+ WithDescription,
+ Required,
+ Disabled,
+} from './components';
+import { PageTitle } from '@/components/PageTitle';
+import { Theming } from '@/components/Theming';
+import { ChangelogComponent } from '@/components/ChangelogComponent';
+import { SliderDefinition } from '../../../utils/definitions';
+
+export const reactAriaUrls = {
+ slider: 'https://react-spectrum.adobe.com/react-aria/Slider.html',
+};
+
+
+
+ } code={defaultSnippet} />
+
+## Usage
+
+
+
+## API reference
+
+
+
+
+
+## Examples
+
+### Single value
+
+Use a single number as the default value to create a single-thumb slider.
+
+ }
+ code={singleValueSnippet}
+/>
+
+### Custom range
+
+Define custom minimum, maximum, and step values for specific use cases.
+
+ }
+ code={withCustomRangeSnippet}
+/>
+
+### Formatted values
+
+Use the `formatOptions` prop with standard Intl.NumberFormat options to customize how values are displayed.
+
+ }
+ code={withFormattedValuesSnippet}
+/>
+
+### With description
+
+Add helpful context with a description below the label.
+
+ }
+ code={withDescriptionSnippet}
+/>
+
+### Required
+
+Mark a field as required to show a "Required" indicator in the label.
+
+ } code={requiredSnippet} />
+
+### Disabled
+
+ } code={disabledSnippet} />
+
+
+
+
diff --git a/docs-ui/src/app/components/slider/props-definition.ts b/docs-ui/src/app/components/slider/props-definition.ts
new file mode 100644
index 0000000000..98bddc3bf9
--- /dev/null
+++ b/docs-ui/src/app/components/slider/props-definition.ts
@@ -0,0 +1,92 @@
+import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs';
+import type { PropDef } from '@/utils/propDefs';
+
+export const sliderPropDefs: Record = {
+ label: {
+ type: 'string',
+ description: 'The label text for the slider.',
+ },
+ description: {
+ type: 'string',
+ description: 'Additional description text displayed below the label.',
+ },
+ secondaryLabel: {
+ type: 'string',
+ description:
+ 'Optional secondary label displayed next to the main label (e.g., "Optional").',
+ },
+ isRequired: {
+ type: 'boolean',
+ description:
+ 'Whether the field is required. Displays "Required" in the label if true.',
+ },
+ minValue: {
+ type: 'number',
+ description: 'The minimum value of the slider.',
+ default: '0',
+ },
+ maxValue: {
+ type: 'number',
+ description: 'The maximum value of the slider.',
+ default: '100',
+ },
+ step: {
+ type: 'number',
+ description: 'The step increment for slider values.',
+ default: '1',
+ },
+ value: {
+ type: 'enum',
+ values: ['number', '[number, number]'],
+ description:
+ 'Controlled value. Use a single number for a single-thumb slider, or an array [min, max] for a range slider. Use with onChange for controlled behavior.',
+ },
+ defaultValue: {
+ type: 'enum',
+ values: ['number', '[number, number]'],
+ description:
+ 'Initial value for uncontrolled usage. Use a single number for a single-thumb slider, or an array [min, max] for a range slider.',
+ default: 'minValue or [minValue, maxValue]',
+ },
+ onChange: {
+ type: 'enum',
+ values: ['(value: number | [number, number]) => void'],
+ description: 'Called when the slider value changes.',
+ },
+ onChangeEnd: {
+ type: 'enum',
+ values: ['(value: number | [number, number]) => void'],
+ description:
+ 'Called when the user stops dragging, useful for triggering actions only on final values.',
+ },
+ formatOptions: {
+ type: 'object',
+ description:
+ 'Intl.NumberFormat options for formatting the displayed value (e.g., { style: "currency", currency: "USD" }).',
+ },
+ isDisabled: {
+ type: 'boolean',
+ description: 'Prevents user interaction when true.',
+ },
+ orientation: {
+ type: 'enum',
+ values: ['horizontal', 'vertical'],
+ description: 'The orientation of the slider.',
+ default: 'horizontal',
+ },
+ name: {
+ type: 'string',
+ description: 'Form field name for form submission.',
+ },
+ 'aria-label': {
+ type: 'string',
+ description:
+ 'Accessible label for screen readers when no visible label is provided.',
+ },
+ 'aria-labelledby': {
+ type: 'string',
+ description: 'ID of an element that labels the slider for accessibility.',
+ },
+ ...classNamePropDefs,
+ ...stylePropDefs,
+};
diff --git a/docs-ui/src/app/components/slider/snippets.ts b/docs-ui/src/app/components/slider/snippets.ts
new file mode 100644
index 0000000000..02a4271bd7
--- /dev/null
+++ b/docs-ui/src/app/components/slider/snippets.ts
@@ -0,0 +1,67 @@
+export const snippetUsage = `import { Slider } from '@backstage/ui';
+
+ `;
+
+export const singleValueSnippet = ` `;
+
+export const defaultSnippet = ` `;
+
+export const withCustomRangeSnippet = ` `;
+
+export const withFormattedValuesSnippet = ` `;
+
+export const withDescriptionSnippet = ` `;
+
+export const requiredSnippet = ` `;
+
+export const disabledSnippet = ` `;
diff --git a/docs-ui/src/app/components/table/page.mdx b/docs-ui/src/app/components/table/page.mdx
index 6eb280cbe2..5323804c88 100644
--- a/docs-ui/src/app/components/table/page.mdx
+++ b/docs-ui/src/app/components/table/page.mdx
@@ -42,6 +42,7 @@ import {
tableCombinedSnippet,
tableCustomRowSnippet,
tablePrimitivesSnippet,
+ tableCellRequirementSnippet,
} from './snippets';
import { ChangelogComponent } from '@/components/ChangelogComponent';
import { PageTitle } from '@/components/PageTitle';
@@ -94,6 +95,18 @@ For full control over state, use the controlled props instead:
- `search` / `onSearchChange`
- `filter` / `onFilterChange`
+### Cell Requirement
+
+Every cell rendered via `ColumnConfig.cell` (or inside a custom `RowRenderFn`) **must** return a cell component as the top-level element. The available cell components are:
+
+- **`CellText`** — displays a title with optional description and icon.
+- **`CellProfile`** — displays an avatar with a name and optional description.
+- **`Cell`** — a generic wrapper for fully custom cell content.
+
+Returning bare text, React fragments, or other elements without wrapping them in one of these cell components will break the table layout.
+
+
+
## Common Patterns
### Sorting
@@ -106,7 +119,7 @@ With `mode: 'complete'`, sorting happens client-side. Provide a `sortFn` that re
### Pagination
-Configure page size and available options through `paginationOptions`. The table displays navigation controls automatically.
+Configure page size and available options through `paginationOptions`. The table displays navigation controls automatically. In `complete` mode, set `type: 'none'` to disable pagination and show all rows.
@@ -292,4 +305,17 @@ Low-level components for building custom table layouts.
-
+
diff --git a/docs-ui/src/app/components/table/props-definition.tsx b/docs-ui/src/app/components/table/props-definition.tsx
index be24e86bf0..6e5809412e 100644
--- a/docs-ui/src/app/components/table/props-definition.tsx
+++ b/docs-ui/src/app/components/table/props-definition.tsx
@@ -45,14 +45,44 @@ export const useTableOptionsPropDefs: Record = {
'The data for the table. Only applicable for "complete" mode, and either this or `getData` must be provided.',
},
paginationOptions: {
- type: 'enum',
- values: ['object'],
- description: (
- <>
- Pagination configuration including pageSize ,{' '}
- pageSizeOptions , and initialOffset .
- >
- ),
+ type: 'complex',
+ description: 'Pagination configuration.',
+ complexType: {
+ name: 'PaginationOptions',
+ properties: {
+ type: {
+ type: "'page' | 'none'",
+ description:
+ "Pagination mode. Set to 'none' to disable pagination and show all rows (complete mode only). Defaults to 'page'.",
+ },
+ pageSize: {
+ type: 'number',
+ description: 'Number of items per page. Defaults to 20.',
+ },
+ pageSizeOptions: {
+ type: 'number[]',
+ description: 'Available page size options for the dropdown.',
+ },
+ initialOffset: {
+ type: 'number',
+ description: 'Starting offset for the first page.',
+ },
+ showPageSizeOptions: {
+ type: 'boolean',
+ description:
+ 'Whether to show the page size dropdown. Defaults to true.',
+ },
+ showPaginationLabel: {
+ type: 'boolean',
+ description:
+ "Whether to display the pagination label (e.g., '1 - 20 of 150').",
+ },
+ getLabel: {
+ type: '(props) => string',
+ description: 'Custom function to generate the pagination label text.',
+ },
+ },
+ },
},
// Uncontrolled state
initialSort: {
@@ -413,6 +443,12 @@ export const tablePaginationPropDefs: Record = {
values: ['(props) => string'],
description: 'Custom function to generate the pagination label text.',
},
+ showPaginationLabel: {
+ type: 'boolean',
+ default: 'true',
+ description:
+ 'Whether to display the pagination label (e.g., "1 - 20 of 150"). When false, only navigation controls are shown.',
+ },
};
// =============================================================================
@@ -430,6 +466,17 @@ export const tableRootPropDefs: Record = {
>
),
},
+ loading: {
+ type: 'boolean',
+ default: 'false',
+ description: (
+ <>
+ Whether the table is in a loading state (e.g., initial data fetch). Adds{' '}
+ aria-busy attribute and data-loading data
+ attribute for styling.
+ >
+ ),
+ },
};
export const columnPropDefs: Record = {
@@ -456,6 +503,11 @@ export const rowPropDefs: Record = {
description:
'Row content. Can be a render function receiving column config.',
},
+ noTrack: {
+ type: 'boolean',
+ description:
+ 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.',
+ },
...classNamePropDefs,
...stylePropDefs,
};
diff --git a/docs-ui/src/app/components/table/snippets.ts b/docs-ui/src/app/components/table/snippets.ts
index ab4bf53a58..39f266f2ea 100644
--- a/docs-ui/src/app/components/table/snippets.ts
+++ b/docs-ui/src/app/components/table/snippets.ts
@@ -57,6 +57,23 @@ const {
filter, // { value, onChange } for filters
} = useTable({ ... });`;
+// =============================================================================
+// Cell Requirement
+// =============================================================================
+
+export const tableCellRequirementSnippet = `// ✅ Correct — CellText is the top-level element
+cell: item => ;
+
+// ✅ Correct — Cell wraps custom content
+cell: item => (
+
+
+ |
+);
+
+// ❌ Wrong — bare text without a cell wrapper
+cell: item => {item.name} ;`;
+
// =============================================================================
// Common Patterns
// =============================================================================
diff --git a/docs-ui/src/app/components/tabs/page.mdx b/docs-ui/src/app/components/tabs/page.mdx
index 6925127943..a07b70dcd3 100644
--- a/docs-ui/src/app/components/tabs/page.mdx
+++ b/docs-ui/src/app/components/tabs/page.mdx
@@ -113,4 +113,4 @@ Add `href` to Tab components to enable URL-based tab selection. The active tab i
-
+
diff --git a/docs-ui/src/app/components/tabs/props-definition.ts b/docs-ui/src/app/components/tabs/props-definition.ts
index e1c1f056cb..92d1c3d258 100644
--- a/docs-ui/src/app/components/tabs/props-definition.ts
+++ b/docs-ui/src/app/components/tabs/props-definition.ts
@@ -68,6 +68,11 @@ export const tabPropDefs: Record = {
default: 'false',
description: 'Disables this tab. Use for temporarily unavailable options.',
},
+ noTrack: {
+ type: 'boolean',
+ description:
+ 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.',
+ },
...childrenPropDefs,
...classNamePropDefs,
};
diff --git a/docs-ui/src/app/components/tag-group/page.mdx b/docs-ui/src/app/components/tag-group/page.mdx
index 1b283a8170..18551ffed7 100644
--- a/docs-ui/src/app/components/tag-group/page.mdx
+++ b/docs-ui/src/app/components/tag-group/page.mdx
@@ -88,4 +88,4 @@ Individual tag item within a group.
-
+
diff --git a/docs-ui/src/app/components/tag-group/props-definition.tsx b/docs-ui/src/app/components/tag-group/props-definition.tsx
index 3f64b03e8c..dc10d5f7e7 100644
--- a/docs-ui/src/app/components/tag-group/props-definition.tsx
+++ b/docs-ui/src/app/components/tag-group/props-definition.tsx
@@ -84,6 +84,11 @@ export const tagPropDefs: Record = {
type: 'boolean',
description: 'Whether the tag is disabled.',
},
+ noTrack: {
+ type: 'boolean',
+ description:
+ 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.',
+ },
...childrenPropDefs,
...classNamePropDefs,
};
diff --git a/docs-ui/src/app/components/tooltip/page.mdx b/docs-ui/src/app/components/tooltip/page.mdx
index 8a9711d56c..feccc05225 100644
--- a/docs-ui/src/app/components/tooltip/page.mdx
+++ b/docs-ui/src/app/components/tooltip/page.mdx
@@ -62,4 +62,4 @@ The tooltip appears in the specified direction relative to the trigger.
-
+
diff --git a/docs-ui/src/app/get-started/installation/page.mdx b/docs-ui/src/app/get-started/installation/page.mdx
index cdb14d5924..d87c2bcba4 100644
--- a/docs-ui/src/app/get-started/installation/page.mdx
+++ b/docs-ui/src/app/get-started/installation/page.mdx
@@ -1,6 +1,10 @@
import { CodeBlock } from '@/components/CodeBlock';
import { Banner } from '@/components/Banner';
-import { snippet } from './snippets';
+import {
+ snippet,
+ analyticsSetupSnippet,
+ analyticsNoTrackSnippet,
+} from './snippets';
# Installation
@@ -44,3 +48,30 @@ your plugin and import the components you need.
/>
+
+## BUIProvider
+
+`BUIProvider` provides routing and analytics integration for all BUI components. It must be rendered inside a React Router context for client-side navigation to work in components like Link, ButtonLink, Tabs, Menu, TagGroup, and Table.
+
+### Setup
+
+If you're using the **new frontend system**, the provider is wired automatically via `@backstage/plugin-app` — no setup required.
+
+For the **old frontend system**, the `BUIProvider` is included in the app shell from `@backstage/core-app-api` and works out of the box.
+
+If you need to set up the provider manually (e.g. in a custom app shell), wrap your app content with the `BUIProvider` inside your Router and pass in Backstage's `useAnalytics` hook:
+
+
+
+
+
+### Analytics
+
+Once configured, BUI components with navigation behavior — Link, ButtonLink, Tab, MenuItem, Tag, and Row — fire a `click` event through Backstage's analytics system when a user navigates. Events include the link text as the subject and the destination URL in the attributes, along with any `AnalyticsContext` metadata (such as `pluginId`) from the component's position in the tree.
+
+To suppress tracking on an individual component, use the `noTrack` prop:
+
+
diff --git a/docs-ui/src/app/get-started/installation/snippets.ts b/docs-ui/src/app/get-started/installation/snippets.ts
index 7e63005172..61edb266b3 100644
--- a/docs-ui/src/app/get-started/installation/snippets.ts
+++ b/docs-ui/src/app/get-started/installation/snippets.ts
@@ -4,3 +4,19 @@ export const snippet = `import { Flex, Button, Text } from '@backstage/ui';
Hello World
Click me
;`;
+
+export const analyticsSetupSnippet = `import { BUIProvider } from '@backstage/ui';
+import { useAnalytics } from '@backstage/core-plugin-api';
+import { BrowserRouter } from 'react-router-dom';
+
+// BUIProvider must be inside a Router for client-side navigation
+
+
+
+
+ `;
+
+export const analyticsNoTrackSnippet = `// Suppress analytics for a specific link
+
+ Skip tracking
+`;
diff --git a/docs-ui/src/app/hooks/page.mdx b/docs-ui/src/app/hooks/page.mdx
new file mode 100644
index 0000000000..ea73f07ce3
--- /dev/null
+++ b/docs-ui/src/app/hooks/page.mdx
@@ -0,0 +1,7 @@
+import { HookGrid } from '@/components/HookGrid';
+
+# Hooks
+
+Backstage UI custom hooks provide easy access to design system features, style management, and responsive interface creation.
+
+
diff --git a/docs-ui/src/app/hooks/use-breakpoint/components.tsx b/docs-ui/src/app/hooks/use-breakpoint/components.tsx
new file mode 100644
index 0000000000..c58010c66f
--- /dev/null
+++ b/docs-ui/src/app/hooks/use-breakpoint/components.tsx
@@ -0,0 +1,17 @@
+'use client';
+
+import { useBreakpoint } from '@backstage/ui';
+
+export function UseBreakpointExample() {
+ const { breakpoint, up, down } = useBreakpoint();
+
+ return (
+
+
Current Breakpoint: {breakpoint}
+ {(up('md') &&
The viewport is larger than 1024px.
) ||
+ (down('sm') &&
The viewport is smaller than 768px.
) || (
+
The viewport is between 768px and 1024px.
+ )}
+
+ );
+}
diff --git a/docs-ui/src/app/hooks/use-breakpoint/page.mdx b/docs-ui/src/app/hooks/use-breakpoint/page.mdx
new file mode 100644
index 0000000000..f7f3378a41
--- /dev/null
+++ b/docs-ui/src/app/hooks/use-breakpoint/page.mdx
@@ -0,0 +1,47 @@
+import { ChangelogComponent } from '@/components/ChangelogComponent';
+import { PageTitle } from '@/components/PageTitle';
+import { PropsTable } from '@/components/PropsTable';
+import { Snippet } from '@/components/Snippet';
+import { Banner } from '@/components/Banner';
+import { useBreakpointReturnDefs } from './props-definition';
+import { useBreakpointExampleSnippet } from './snippets';
+import { UseBreakpointExample } from './components';
+
+
+
+## Usage
+
+
+
+The `useBreakpoint` hook returns the active breakpoint and two functions, `up` and `down`, to check if the viewport is above, or below a given breakpoint, letting you adjust your UI responsively.
+
+ }
+ code={useBreakpointExampleSnippet}
+/>
+
+## Breakpoints
+
+The default breakpoints are:
+
+- **initial**: < 640px
+- **xs**: ≥ 640px < 768px
+- **sm**: ≥ 768px < 1024px
+- **md**: ≥ 1024px < 1280px
+- **lg**: ≥ 1280px < 1536px
+- **xl**: ≥ 1536px
+
+## Return Value
+
+
+
+
diff --git a/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts b/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts
new file mode 100644
index 0000000000..b1702a82cc
--- /dev/null
+++ b/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts
@@ -0,0 +1,23 @@
+import { type PropDef } from '@/utils/propDefs';
+
+const Breakpoint = ['"initial"', '"xs"', '"sm"', '"md"', '"lg"', '"xl"'];
+
+export const useBreakpointReturnDefs: Record = {
+ breakpoint: {
+ type: 'enum',
+ values: Breakpoint,
+ description: 'The current active breakpoint based on screen width',
+ },
+ up: {
+ type: 'enum',
+ values: [`(breakpoint: ${Breakpoint.join(' | ')}) => boolean`],
+ description:
+ 'Function that takes a breakpoint and returns true if the screen width is at or above that breakpoint',
+ },
+ down: {
+ type: 'enum',
+ values: [`(breakpoint: ${Breakpoint.join(' | ')}) => boolean`],
+ description:
+ 'Function that takes a breakpoint and returns true if the screen width is at or below that breakpoint',
+ },
+};
diff --git a/docs-ui/src/app/hooks/use-breakpoint/snippets.ts b/docs-ui/src/app/hooks/use-breakpoint/snippets.ts
new file mode 100644
index 0000000000..19e6debe65
--- /dev/null
+++ b/docs-ui/src/app/hooks/use-breakpoint/snippets.ts
@@ -0,0 +1,13 @@
+export const useBreakpointExampleSnippet = `import { useBreakpoint } from '@backstage/ui';
+
+function ResponsiveComponent() {
+ const { breakpoint, up, down } = useBreakpoint();
+
+ return (
+
+
Current Breakpoint: {breakpoint}
+ {up('md') &&
The viewport is medium or larger.
}
+ {down('sm') &&
The viewport is small or smaller.
}
+
+ );
+}`;
diff --git a/docs-ui/src/app/snippets.ts b/docs-ui/src/app/snippets.ts
index e16463a085..4a93c31831 100644
--- a/docs-ui/src/app/snippets.ts
+++ b/docs-ui/src/app/snippets.ts
@@ -1,19 +1,19 @@
export const surfacesSnippet = `
-
+
Hello World
-
+
Hello World
`;
-export const adaptiveSnippet = `
+export const adaptiveSnippet = `
{/* automatically set background to neutral-2 */}
Button with background set to neutral-3
`;
-export const customCardSnippet = `
+export const customCardSnippet = `
Hello World
`;
diff --git a/docs-ui/src/components/Banner/styles.module.css b/docs-ui/src/components/Banner/styles.module.css
index 9666c5d521..d9d3323376 100644
--- a/docs-ui/src/components/Banner/styles.module.css
+++ b/docs-ui/src/components/Banner/styles.module.css
@@ -16,13 +16,13 @@
.info {
background-color: #f2f2f2;
border-color: #cdcdcd;
- color: #888888;
+ color: #4b4b4b;
}
.warning {
background-color: #fff2b9;
- border-color: #ffd000;
- color: #d79927;
+ border-color: #d79927;
+ color: #8a3d09;
}
.icon {
diff --git a/docs-ui/src/components/Changelog/utils.tsx b/docs-ui/src/components/Changelog/utils.tsx
index d2daed1176..f39aa7bba5 100644
--- a/docs-ui/src/components/Changelog/utils.tsx
+++ b/docs-ui/src/components/Changelog/utils.tsx
@@ -1,4 +1,4 @@
-import type { ChangelogProps } from '@/utils/types';
+import type { ChangelogProps, Component } from '@/utils/types';
// Badge Components
export const Badge = ({
@@ -83,15 +83,22 @@ export const formatPRLinks = (prs: string[]): string => {
export const generateEntryMarkdown = (
entry: ChangelogProps,
- options: { showComponentBadges?: boolean } = {},
+ options: {
+ showComponentBadges?: boolean;
+ componentBadgeFilter?: Component[];
+ } = {},
): string => {
- const { showComponentBadges = true } = options;
+ const { showComponentBadges = true, componentBadgeFilter } = options;
const prs = formatPRLinks(entry.prs);
// Prepend component names as badges if available and requested
+ const entryComponents = entry.components ?? [];
+ const badgeComponents = componentBadgeFilter
+ ? entryComponents.filter(c => componentBadgeFilter.includes(c))
+ : entryComponents;
const componentBadges =
- showComponentBadges && entry.components.length > 0
- ? entry.components
+ showComponentBadges && badgeComponents.length > 0
+ ? badgeComponents
.map(c => `${toTitleCase(c)} `)
.join(' ') + ' '
: '';
@@ -120,6 +127,7 @@ export const generateEntryMarkdown = (
export interface GenerateChangelogOptions {
showComponentBadges?: boolean;
+ componentBadgeFilter?: Component[];
headingLevel?: number;
}
@@ -127,7 +135,11 @@ export const generateChangelogMarkdown = (
entries: ChangelogProps[],
options: GenerateChangelogOptions = {},
): string => {
- const { showComponentBadges = true, headingLevel = 2 } = options;
+ const {
+ showComponentBadges = true,
+ componentBadgeFilter,
+ headingLevel = 2,
+ } = options;
// Group changelog entries by version
const groupedChangelog = groupByVersion(entries);
@@ -168,7 +180,12 @@ export const generateChangelogMarkdown = (
const bumpSections = sections
.map(({ title, entries: sectionEntries }) => {
const entriesMarkdown = sectionEntries
- .map(e => generateEntryMarkdown(e, { showComponentBadges }))
+ .map(e =>
+ generateEntryMarkdown(e, {
+ showComponentBadges,
+ componentBadgeFilter,
+ }),
+ )
.join('\n\n');
return `${sectionHeading} ${title}\n\n${entriesMarkdown}`;
diff --git a/docs-ui/src/components/ChangelogComponent/index.tsx b/docs-ui/src/components/ChangelogComponent/index.tsx
index 92068d04f8..013764dbca 100644
--- a/docs-ui/src/components/ChangelogComponent/index.tsx
+++ b/docs-ui/src/components/ChangelogComponent/index.tsx
@@ -1,22 +1,34 @@
import { changelog } from '@/utils/changelog';
import { MDXRemote } from 'next-mdx-remote-client/rsc';
import { formattedMDXComponents } from '@/mdx-components';
-import type { Component } from '@/utils/changelog';
+import type { AtLeastOne, Component, Hook } from '@/utils/changelog';
import {
Badge,
BreakingBadge,
generateChangelogMarkdown,
} from '../Changelog/utils';
-export const ChangelogComponent = ({ component }: { component: Component }) => {
- const componentChangelog = changelog.filter(c =>
- c.components.includes(component),
+type ChangelogComponentProps = AtLeastOne<{
+ component: Component | Component[];
+ hook: Hook;
+}>;
+
+export const ChangelogComponent = ({
+ component,
+ hook,
+}: Readonly) => {
+ const components = Array.isArray(component) ? component : [component];
+ const componentChangelog = changelog.filter(
+ c =>
+ c.components?.some(cc => components.includes(cc)) ||
+ c.hooks?.includes(hook),
);
const content = `## Changelog
${generateChangelogMarkdown(componentChangelog, {
- showComponentBadges: false,
+ showComponentBadges: components.length > 1,
+ componentBadgeFilter: components.length > 1 ? components : undefined,
headingLevel: 3,
})}`;
diff --git a/docs-ui/src/components/HookGrid/HookGrid.module.css b/docs-ui/src/components/HookGrid/HookGrid.module.css
new file mode 100644
index 0000000000..a6dd54a864
--- /dev/null
+++ b/docs-ui/src/components/HookGrid/HookGrid.module.css
@@ -0,0 +1,29 @@
+.grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ margin-top: 1rem;
+ margin-bottom: 2rem;
+}
+
+@media (min-width: 768px) {
+ .grid {
+ grid-template-columns: repeat(4, 1fr);
+ }
+}
+
+.item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 10px 0;
+ font-size: 14px;
+ font-weight: 400;
+ color: var(--primary);
+ text-decoration: none;
+
+ &:hover {
+ color: var(--link);
+ text-decoration: underline;
+ text-underline-offset: 2px;
+ }
+}
diff --git a/docs-ui/src/components/HookGrid/HookGrid.tsx b/docs-ui/src/components/HookGrid/HookGrid.tsx
new file mode 100644
index 0000000000..871ee885d2
--- /dev/null
+++ b/docs-ui/src/components/HookGrid/HookGrid.tsx
@@ -0,0 +1,21 @@
+'use client';
+
+import Link from 'next/link';
+import { hooks } from '@/utils/data';
+import styles from './HookGrid.module.css';
+
+export const HookGrid = () => {
+ return (
+
+ {hooks.map(item => (
+
+ {item.title}
+
+ ))}
+
+ );
+};
diff --git a/docs-ui/src/components/HookGrid/index.ts b/docs-ui/src/components/HookGrid/index.ts
new file mode 100644
index 0000000000..934f73aacf
--- /dev/null
+++ b/docs-ui/src/components/HookGrid/index.ts
@@ -0,0 +1 @@
+export * from './HookGrid';
diff --git a/docs-ui/src/components/Navigation/Navigation.tsx b/docs-ui/src/components/Navigation/Navigation.tsx
index bad77e942e..d27303d94b 100644
--- a/docs-ui/src/components/Navigation/Navigation.tsx
+++ b/docs-ui/src/components/Navigation/Navigation.tsx
@@ -4,14 +4,14 @@ import Link from 'next/link';
import { usePathname } from 'next/navigation';
import clsx from 'clsx';
import {
- RiCollageLine,
+ RiCodeSSlashLine,
RiFileHistoryLine,
RiHazeLine,
RiPaletteLine,
RiServiceLine,
RiStackLine,
} from '@remixicon/react';
-import { components } from '@/utils/data';
+import { components, hooks } from '@/utils/data';
import styles from './Navigation.module.css';
interface NavigationProps {
@@ -66,7 +66,7 @@ export const Navigation = ({ onLinkClick }: NavigationProps) => {
-
+
Components
{components.map(item => {
@@ -92,6 +92,29 @@ export const Navigation = ({ onLinkClick }: NavigationProps) => {
);
})}
+
+ Hooks
+
+ {hooks.map(item => {
+ const isActive = pathname === `/hooks/${item.slug}`;
+ return (
+
+ {item.title}
+
+ {item.status === 'alpha' && 'Alpha'}
+ {item.status === 'beta' && 'Beta'}
+ {item.status === 'inProgress' && 'In Progress'}
+ {item.status === 'stable' && 'Stable'}
+ {item.status === 'deprecated' && 'Deprecated'}
+
+
+ );
+ })}
>
);
};
diff --git a/docs-ui/src/components/Toolbar/Toolbar.tsx b/docs-ui/src/components/Toolbar/Toolbar.tsx
index 4a071bf45e..ea094b2a81 100644
--- a/docs-ui/src/components/Toolbar/Toolbar.tsx
+++ b/docs-ui/src/components/Toolbar/Toolbar.tsx
@@ -3,6 +3,7 @@
import { useState, useEffect } from 'react';
import {
RiArrowDownSLine,
+ RiDiscordLine,
RiGithubLine,
RiMoonLine,
RiSearchLine,
@@ -95,6 +96,7 @@ export const Toolbar = ({ version }: ToolbarProps) => {
@@ -103,11 +105,23 @@ export const Toolbar = ({ version }: ToolbarProps) => {
+
+
+
+ +
+ \`\`\``,
+ breaking: true,
+ commitSha: '46a9adc',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32723'],
+ description: `**BREAKING**: Removed gray scale tokens and renamed background surface tokens to neutral tokens
+
+ The \`--bui-gray-1\` through \`--bui-gray-8\` tokens have been removed. The \`--bui-bg-surface-*\` and \`--bui-bg-neutral-on-surface-*\` tokens have been replaced by a unified \`--bui-bg-neutral-*\` scale.`,
+ migration: `Replace surface tokens directly:
+
+\`\`\`diff
+- background: var(--bui-bg-surface-0);
++ background: var(--bui-bg-neutral-0);
+\`\`\`
+
+Replace on-surface tokens shifted by +1:
+
+\`\`\`diff
+- background: var(--bui-bg-neutral-on-surface-0);
++ background: var(--bui-bg-neutral-1);
+\`\`\`
+
+Replace gray tokens 1-4 with neutral equivalents (\`--bui-gray-5\` through \`--bui-gray-8\` have no direct replacement):
+
+\`\`\`diff
+- background: var(--bui-gray-1);
++ background: var(--bui-bg-neutral-1);
+\`\`\``,
+ breaking: true,
+ commitSha: 'b63c25b',
+ },
+ {
+ components: [
+ 'box',
+ 'button',
+ 'button-icon',
+ 'button-link',
+ 'toggle-button',
+ 'card',
+ 'flex',
+ 'grid',
+ ],
+ version: '0.12.0',
+ prs: ['32711'],
+ description: `**BREAKING**: Replaced \`Surface\` / \`onSurface\` system with new provider/consumer background system
+
+ The old \`Surface\` type (\`'0'\`–\`'3'\`, \`'auto'\`) and its associated props (\`surface\`, \`onSurface\`) have been replaced by a provider/consumer \`bg\` architecture.
+
+ **Types:**
+
+ - \`ContainerBg\` — \`'neutral-1'\` | \`'neutral-2'\` | \`'neutral-3'\` | \`'danger'\` | \`'warning'\` | \`'success'\`
+ - \`ProviderBg\` — \`ContainerBg | 'neutral-auto'\`
+
+ Consumer components (e.g. Button) inherit the parent's \`bg\` via \`data-on-bg\`, and CSS handles the visual step-up. See "Neutral level capping" below for details on how levels are bounded.
+
+ **Hooks:**
+
+ - \`useBgProvider(bg?)\` — for provider components. Returns \`{ bg: undefined }\` when no \`bg\` is given (transparent). Supports \`'neutral-auto'\` to auto-increment from the parent context.
+ - \`useBgConsumer()\` — for consumer components. Returns the parent container's \`bg\` unchanged.
+
+ **Component roles:**
+
+ - **Provider-only** (Box, Flex, Grid): set \`data-bg\`, wrap children in \`BgProvider\`. **Transparent by default** — they do _not_ auto-increment; pass \`bg="neutral-auto"\` explicitly if you want automatic neutral stepping.
+ - **Consumer-only** (Button, ButtonIcon, ButtonLink): set \`data-on-bg\`, inherit the parent container's \`bg\` unchanged.
+ - **Provider + Consumer** (Card): sets both \`data-bg\` and \`data-on-bg\`, wraps children. Card passes \`bg="neutral-auto"\` to its inner Box, so it auto-increments from the parent context.
+
+ **Neutral level capping:**
+
+ Provider components cap at \`neutral-3\`. There is no \`neutral-4\` prop value. The \`neutral-4\` level exists only in consumer component CSS — for example, a Button sitting on a \`neutral-3\` surface uses \`neutral-4\` tokens internally via \`data-on-bg\`.`,
+ migration: `Rename the \`surface\` prop to \`bg\` on provider components and update values:
+
+\`\`\`diff
+-
++
+
+-
++
+
+-
++
+
+-
++
+\`\`\`
+
+Remove \`onSurface\` from consumer components — they now always inherit from the parent container:
+
+\`\`\`diff
+-
++
+
+-
++
+
+-
++
+\`\`\`
+
+Update type imports:
+
+\`\`\`diff
+- import type { Surface, LeafSurfaceProps, ContainerSurfaceProps } from '@backstage/ui';
++ import type { ContainerBg, ProviderBg } from '@backstage/ui';
+\`\`\`
+
+Replace hook usage in custom components:
+
+\`\`\`diff
+- import { useSurface, SurfaceProvider } from '@backstage/ui';
++ import { useBgProvider, useBgConsumer, BgProvider } from '@backstage/ui';
+
+- const { surface } = useSurface({ surface: props.surface });
++ const { bg } = useBgProvider(props.bg);
+
+- const { surface } = useSurface({ onSurface: props.onSurface });
++ const { bg } = useBgConsumer();
+\`\`\`
+
+Update CSS selectors targeting surface data attributes:
+
+\`\`\`diff
+- [data-surface='1'] { ... }
++ [data-bg='neutral-1'] { ... }
+
+- [data-on-surface='1'] { ... }
++ [data-on-bg='neutral-1'] { ... }
+\`\`\`
+
+Note: Provider components use \`data-bg\` (values: \`neutral-1\` through \`neutral-3\`, plus intent values). Consumer components use \`data-on-bg\`, which reflects the parent container's \`bg\` directly. The \`neutral-4\` level never appears as a prop or \`data-bg\` value — it is used only in consumer CSS.`,
+ breaking: true,
+ commitSha: '7898df0',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32837'],
+ description: `**BREAKING:** Renamed, added, and removed CSS tokens.
+
+ - Renamed \`--bui-bg-neutral-0\` to \`--bui-bg-app\`.
+ - Renamed \`--bui-border\` to \`--bui-border-2\`.
+ - Added \`--bui-border-1\` for subtle, low-contrast borders.
+ - Added \`--bui-bg-popover\` for the background color of popovers, tooltips, menus, and dialogs.
+ - Removed \`--bui-border-hover\`, \`--bui-border-pressed\`, and \`--bui-border-disabled\`.`,
+ migration: `\`\`\`diff
+- var(--bui-bg-neutral-0)
++ var(--bui-bg-app)
+
+- var(--bui-border)
++ var(--bui-border-2)
+\`\`\`
+
+Remove any references to \`--bui-border-hover\`, \`--bui-border-pressed\`, and \`--bui-border-disabled\` as these tokens no longer exist.`,
+ breaking: true,
+ commitSha: '4137a43',
+ },
+ {
+ components: ['button-icon', 'button-link'],
+ version: '0.12.0',
+ prs: ['32308'],
+ description: `**BREAKING**: Changed CSS selectors for \`ButtonIcon\` and \`ButtonLink\` components. Custom styles targeting \`.bui-Button\` to style these components must be updated to use \`.bui-ButtonIcon\` or \`.bui-ButtonLink\` respectively.
+
+ \`\`\`diff
+ -/* This no longer styles ButtonIcon or ButtonLink */
+ -.bui-Button[data-variant="primary"] { ... }
+ +/* Use component-specific selectors */
+ +.bui-ButtonIcon[data-variant="primary"] { ... }
+ +.bui-ButtonLink[data-variant="primary"] { ... }
+ \`\`\``,
+ breaking: true,
+ commitSha: 'b1f723b',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32472'],
+ description: `**BREAKING**: The \`cell\` and \`header\` properties in \`ColumnConfig\` now return \`ReactElement\` instead of \`ReactNode\`.
+
+ This fixes an issue where React Aria's Collection component would inject an \`id\` prop into Fragment wrappers, causing "Invalid prop \`id\` supplied to \`React.Fragment\`" errors on render.
+
+ Migration:
+
+ \`\`\`diff
+ const columns: ColumnConfig[] = [
+ {
+ id: 'name',
+ label: 'Name',
+ - cell: (item) => item.name,
+ + cell: (item) => ,
+ - header: () => 'Name',
+ + header: () => Name ,
+ },
+ ];
+ \`\`\``,
+ breaking: true,
+ commitSha: 'caeb9ad',
+ },
+ {
+ components: ['plugin-header'],
+ version: '0.12.0',
+ prs: ['32875'],
+ description: `**BREAKING**: Renamed the \`Header\` component to \`PluginHeader\` for clarity.
+
+ The following exports have been renamed:
+
+ - \`Header\` → \`PluginHeader\`
+ - \`HeaderProps\` → \`PluginHeaderProps\`
+ - \`HeaderDefinition\` → \`PluginHeaderDefinition\`
+
+ The \`HeaderTab\` type is unchanged as it is shared with \`HeaderPage\`.
+
+ CSS class names have been updated from \`bui-Header*\` to \`bui-PluginHeader*\`.`,
+ migration: `\`\`\`diff
+-import { Header, HeaderDefinition } from '@backstage/ui';
++import { PluginHeader, PluginHeaderDefinition } from '@backstage/ui';
+
+-
++
+\`\`\``,
+ breaking: true,
+ commitSha: '0ec3c0e',
+ },
+ {
+ components: ['button'],
+ version: '0.12.0',
+ prs: ['32541'],
+ description: `**BREAKING**: Removed \`large\` size variant from Button component as it was never implemented.`,
+ migration: `\`\`\`diff
+- Click me
++ Click me
+\`\`\``,
+ breaking: true,
+ commitSha: '058ffd9',
+ },
+ {
+ components: ['link'],
+ version: '0.12.0',
+ prs: ['32608'],
+ description: `**BREAKING**: Removed link and tint color tokens, added new status foreground tokens, and improved Link component styling
+
+ The following color tokens have been removed:
+
+ - \`--bui-fg-link\` (and all related tokens: \`-hover\`, \`-pressed\`, \`-disabled\`)
+ - \`--bui-fg-tint\` (and all related tokens: \`-hover\`, \`-pressed\`, \`-disabled\`)
+ - \`--bui-bg-tint\` (and all related tokens: \`-hover\`, \`-pressed\`, \`-disabled\`)
+ - \`--bui-border-tint\` (and all related tokens)
+
+ **New Status Tokens:**
+
+ Added dedicated tokens for status colors that distinguish between usage on status backgrounds vs. standalone usage:
+
+ - \`--bui-fg-danger-on-bg\` / \`--bui-fg-danger\`
+ - \`--bui-fg-warning-on-bg\` / \`--bui-fg-warning\`
+ - \`--bui-fg-success-on-bg\` / \`--bui-fg-success\`
+ - \`--bui-fg-info-on-bg\` / \`--bui-fg-info\`
+
+ The \`-on-bg\` variants are designed for text on colored backgrounds, while the base variants are for standalone status indicators with improved visibility and contrast.`,
+ migration: `For link colors, migrate to one of the following alternatives:
+
+\`\`\`diff
+.custom-link {
+- color: var(--bui-fg-link);
++ color: var(--bui-fg-info); /* For informational links */
++ /* or */
++ color: var(--bui-fg-primary); /* For standard text links */
+}
+\`\`\`
+
+For tint colors (backgrounds, foregrounds, borders), migrate to appropriate status or neutral colors:
+
+\`\`\`diff
+.info-section {
+- background: var(--bui-bg-tint);
++ background: var(--bui-bg-info); /* For informational sections */
++ /* or */
++ background: var(--bui-bg-neutral-1); /* For neutral emphasis */
+}
+\`\`\`
+
+If you're using status foreground colors on colored backgrounds, update to the new \`-on-bg\` tokens:
+
+\`\`\`diff
+.error-badge {
+- color: var(--bui-fg-danger);
++ color: var(--bui-fg-danger-on-bg);
+ background: var(--bui-bg-danger);
+}
+\`\`\``,
+ breaking: true,
+ commitSha: '110fec0',
+ },
+ {
+ components: ['full-page'],
+ version: '0.12.0',
+ prs: ['32822'],
+ description: `Added a new \`FullPage\` component that fills the remaining viewport height below the \`PluginHeader\`.
+
+ \`\`\`tsx
+
+
+ {/* content fills remaining height */}
+
+ \`\`\``,
+
+ commitSha: '644e303',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32825'],
+ description: `Fixed dark theme \`--bui-fg-secondary\` and \`--bui-fg-disabled\` tokens using black-based \`oklch(0% ...)\` instead of white-based \`oklch(100% ...)\`, making secondary and disabled text visible on dark backgrounds.`,
+
+ commitSha: '44877e4',
+ },
+ {
+ components: ['box'],
+ version: '0.12.0',
+ prs: ['32536'],
+ description: `Fixed Box component to forward HTML attributes to the underlying div element.`,
+
+ commitSha: '350c948',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32516'],
+ description: `Use node prefix on native imports`,
+
+ commitSha: '7455dae',
+ },
+ {
+ components: ['accordion'],
+ version: '0.12.0',
+ prs: ['32488'],
+ description: `Fixed nested Accordion icon state issue where the inner accordion's arrow icon would incorrectly show as expanded when only the outer accordion was expanded. The CSS selector now uses a direct parent selector to ensure the icon only responds to its own accordion's expanded state.`,
+
+ commitSha: 'c8ae765',
+ },
+ {
+ components: ['popover', 'tooltip'],
+ version: '0.12.0',
+ prs: ['32575'],
+ description: `Fixed CSS Module syntax to comply with Next.js 16 Turbopack validation by flattening nested dark theme selectors.`,
+
+ commitSha: '4d1b7f4',
+ },
+ {
+ components: ['button'],
+ version: '0.12.0',
+ prs: ['32554'],
+ description: `Added \`destructive\` prop to Button for dangerous actions like delete or remove. Works with all variants (primary, secondary, tertiary).`,
+
+ commitSha: '2c219b9',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32553'],
+ description: `Fixed \`useDefinition\` hook adding literal "undefined" class name when no className prop was passed.`,
+
+ commitSha: '5af9e14',
+ },
+ {
+ components: ['tag'],
+ version: '0.12.0',
+ prs: ['32742'],
+ description: `Allow \`ref\` as a prop on the \`Tag\` component`,
+
+ commitSha: '5c76d13',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32799'],
+ description: `Cleaned up \`useDefinition\` \`ownProps\` types to remove never-typed ghost properties from autocomplete.`,
+
+ commitSha: 'ab25658',
+ },
+ {
+ components: ['table'],
+ version: '0.12.0',
+ prs: ['32685'],
+ description: `Allow data to be passed directly to the \`useTable\` hook using the property \`data\` instead of \`getData()\` for mode \`"complete"\`.
+
+ This simplifies usage as data changes, rather than having to perform a \`useEffect\` when data changes, and then reloading the data. It also happens immediately, so stale data won't remain until a rerender (with an internal async state change), so less flickering.`,
+
+ commitSha: '741a98d',
+ },
+ {
+ components: ['table'],
+ version: '0.12.0',
+ prs: ['32684'],
+ description: `Fixed changing columns after first render from crashing. It now renders the table with the new column layout as columns change.`,
+
+ commitSha: 'a0fe1b2',
+ },
+ {
+ components: ['alert'],
+ version: '0.12.0',
+ prs: ['32520'],
+ description: `Added new \`Alert\` component with support for status variants (info, success, warning, danger), icons, loading states, and custom actions.
+
+ Updated status color tokens for improved contrast and consistency across light and dark themes:
+
+ - Added new \`--bui-bg-info\` and \`--bui-fg-info\` tokens for info status
+ - Updated \`--bui-bg-danger\`, \`--bui-fg-danger\` tokens
+ - Updated \`--bui-bg-warning\`, \`--bui-fg-warning\` tokens
+ - Updated \`--bui-bg-success\`, \`--bui-fg-success\` tokens`,
+
+ commitSha: '508bd1a',
+ },
+ {
+ components: [
+ 'tabs',
+ 'tab',
+ 'tag-group',
+ 'tag',
+ 'menu',
+ 'menu-item',
+ 'menu-autocomplete',
+ ],
+ version: '0.12.0',
+ prs: ['32373'],
+ description: `Fixed client-side navigation for container components by wrapping the container (not individual items) in RouterProvider. Components now conditionally provide routing context only when children have internal links, removing the Router context requirement when not needed. This also removes the need to wrap these components in MemoryRouter during tests when they are not using the \`href\` prop.
+
+ Additionally, when multiple tabs match the current URL via prefix matching, the tab with the most specific path (highest segment count) is now selected. For example, with URL \`/catalog/users/john\`, a tab with path \`/catalog/users\` is now selected over a tab with path \`/catalog\`.`,
+
+ commitSha: 'da30862',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32540'],
+ description: `Fixed an infinite render loop in Tabs when navigating to a URL that doesn't match any tab \`href\`.`,
+
+ commitSha: '092c453',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32643'],
+ description: `export PasswordField component`,
+
+ commitSha: 'becf851',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32843'],
+ description: `Migrated Accordion components to use \`useDefinition\` instead of \`useStyles\`, and added automatic background adaptation based on parent container context.`,
+
+ commitSha: 'becee36',
+ },
+ {
+ components: ['link', 'button-link', 'row'],
+ version: '0.12.0',
+ prs: ['32373'],
+ description: `Fixed components to not require a Router context when rendering without internal links.`,
+
+ commitSha: '5320aa8',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['31818'],
+ description: `Updated \`react-router-dom\` peer dependency to \`^6.30.2\` and explicitly disabled v7 future flags to suppress deprecation warnings.`,
+
+ commitSha: 'a7e0d50',
+ },
+ {
+ components: ['table'],
+ version: '0.12.0',
+ prs: ['32686'],
+ description: `The Table component now wraps the react-aria-components \`Table\` with a \`ResizableTableContainer\` only if any column has a width property set. This means that column widths can adapt to the content otherwise (if no width is explicitly set).`,
+
+ commitSha: '8c39412',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32537'],
+ description: `Bump react-aria-components to v1.14.0`,
+
+ commitSha: 'cb090b4',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32428'],
+ description: `Fixed React 17 compatibility by using \`useId\` from \`react-aria\` instead of the built-in React hook which is only available in React 18+.`,
+
+ commitSha: 'c429101',
+ },
+ {
+ components: ['switch'],
+ version: '0.12.0',
+ prs: ['32551'],
+ description: `Fixed Switch component disabled state styling to show \`not-allowed\` cursor and disabled text color.`,
+
+ commitSha: '74c5a76',
+ },
+ {
+ components: [],
+ version: '0.12.0',
+ prs: ['32761'],
+ description: `Migrated to use the standard \`backstage-cli package build\` for CSS bundling instead of a custom build script.`,
+
+ commitSha: '20131c5',
+ },
+];
diff --git a/docs-ui/src/utils/changelogs/v0.13.0.ts b/docs-ui/src/utils/changelogs/v0.13.0.ts
new file mode 100644
index 0000000000..8b4f93afda
--- /dev/null
+++ b/docs-ui/src/utils/changelogs/v0.13.0.ts
@@ -0,0 +1,527 @@
+import type { ChangelogProps } from '../types';
+
+export const changelog_0_13_0: ChangelogProps[] = [
+ {
+ components: [
+ 'box',
+ 'flex',
+ 'grid',
+ 'card',
+ 'accordion',
+ 'popover',
+ 'tooltip',
+ 'dialog',
+ 'menu',
+ ],
+ version: '0.13.0',
+ prs: ['33002'],
+ description: `**BREAKING**: Simplified the neutral background prop API for container components. The explicit \`neutral-1\`, \`neutral-2\`, \`neutral-3\`, and \`neutral-auto\` values have been removed from \`ProviderBg\`. They are replaced by a single \`'neutral'\` value that always auto-increments from the parent context, making it impossible to skip or pin to an explicit neutral level.`,
+ migration: `Replace any explicit \`bg="neutral-1"\`, \`bg="neutral-2"\`, \`bg="neutral-3"\`, or \`bg="neutral-auto"\` props with \`bg="neutral"\`. To achieve a specific neutral level in stories or tests, use nested containers — each additional \`bg="neutral"\` wrapper increments by one level.
+
+\`\`\`tsx
+// Before
+...
+
+// After
+
+ ...
+
+\`\`\``,
+ breaking: true,
+ commitSha: '768f09d',
+ },
+ {
+ components: ['popover', 'tooltip', 'menu', 'dialog'],
+ version: '0.13.0',
+ prs: ['32979'],
+ description: `**BREAKING**: Removed \`--bui-bg-popover\` CSS token. Popover, Tooltip, Menu, and Dialog now use \`--bui-bg-app\` for their outer shell and \`Box bg="neutral-1"\` for content areas, providing better theme consistency and eliminating a redundant token.`,
+ migration: `Replace any usage of \`--bui-bg-popover\` with \`--bui-bg-neutral-1\` (for content surfaces) or \`--bui-bg-app\` (for outer shells):
+
+\`\`\`diff
+- background: var(--bui-bg-popover);
++ background: var(--bui-bg-neutral-1);
+\`\`\``,
+ breaking: true,
+ commitSha: 'b42fcdc',
+ },
+ {
+ components: ['searchfield'],
+ version: '0.13.0',
+ prs: ['33050'],
+ description: `**BREAKING**: Data attributes rendered by components are now always lowercase. This affects CSS selectors targeting camelCase data attributes.`,
+ migration: `Update any custom CSS selectors that target camelCase data attributes to use lowercase instead:
+
+\`\`\`diff
+- [data-startCollapsed='true'] { ... }
++ [data-startcollapsed='true'] { ... }
+\`\`\``,
+ breaking: true,
+ commitSha: 'bd3a76e',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['33050'],
+ description: `**BREAKING**: Removed deprecated types \`ComponentDefinition\`, \`ClassNamesMap\`, \`DataAttributeValues\`, and \`DataAttributesMap\` from the public API. These were internal styling infrastructure types that have been replaced by the \`defineComponent\` system.`,
+ migration: `Remove any direct usage of these types. Component definitions now use \`defineComponent()\` and their shapes are not part of the public API contract.
+
+\`\`\`diff
+- import type { ComponentDefinition, ClassNamesMap } from '@backstage/ui';
+\`\`\`
+
+If you were reading \`definition.dataAttributes\`, use \`definition.propDefs\` instead — props with \`dataAttribute: true\` in \`propDefs\` are the equivalent.`,
+ breaking: true,
+ commitSha: '95702ab',
+ },
+ {
+ components: ['link', 'button-link', 'tabs', 'menu', 'tag-group', 'table'],
+ version: '0.13.0',
+ prs: ['33267'],
+ description: `**BREAKING**: Centralized client-side routing in \`BUIProvider\`. Components like Link, ButtonLink, Tabs, Menu, TagGroup, and Table now require a \`BUIProvider\` rendered inside a React Router context for client-side navigation to work.`,
+ migration: `This change requires updating \`@backstage/plugin-app\` and \`@backstage/core-app-api\` alongside \`@backstage/ui\`. If you only upgrade \`@backstage/ui\`, BUI components will fall back to full-page navigation.
+
+If you cannot upgrade all packages together, or if you have a custom app shell, add a \`BUIProvider\` inside your Router:
+
+\`\`\`diff
++ import { BUIProvider } from '@backstage/ui';
+
+
++
+
++
+
+\`\`\``,
+ breaking: true,
+ commitSha: '42f8c9b',
+ },
+ {
+ components: ['header'],
+ version: '0.13.0',
+ prs: ['33354'],
+ description: `**BREAKING**: Renamed internal CSS classes to match the \`Header\` component name.
+
+ **Migration:** If you are targeting these classes directly in your styles, update the following:
+
+ - \`bui-HeaderPage\` → \`bui-Header\`
+ - \`bui-HeaderPageContent\` → \`bui-HeaderContent\`
+ - \`bui-HeaderPageBreadcrumbs\` → \`bui-HeaderBreadcrumbs\`
+ - \`bui-HeaderPageTabsWrapper\` → \`bui-HeaderTabsWrapper\`
+ - \`bui-HeaderPageControls\` → \`bui-HeaderControls\``,
+ breaking: true,
+ commitSha: '17d6398',
+ },
+ {
+ components: ['checkbox'],
+ version: '0.13.0',
+ prs: ['33323'],
+ description: `Removed redundant \`selected\` and \`indeterminate\` props from the \`Checkbox\` component. Use the \`isSelected\` and \`isIndeterminate\` props instead, which are the standard React Aria props and already handle both the checkbox behaviour and the corresponding CSS data attributes.`,
+ migration: `Replace any usage of the \`selected\` and \`indeterminate\` props on \`Checkbox\` with the \`isSelected\` and \`isIndeterminate\` props. Note that the checked state and related CSS data attributes (such as \`data-selected\` and \`data-indeterminate\`) are now driven by React Aria, so any custom logic that previously inspected or set these via the old props should instead rely on the React Aria-managed state and attributes exposed through the new props.`,
+ breaking: true,
+ commitSha: '9d5f3ba',
+ },
+ {
+ components: ['list', 'list-row'],
+ version: '0.13.0',
+ prs: ['33358'],
+ description: `Added \`List\` and \`ListRow\` components. These provide a standalone, accessible list of interactive rows built on top of React Aria's \`GridList\` and \`GridListItem\` primitives. Rows support icons, descriptions, actions, menus, and single or multiple selection modes.`,
+
+ commitSha: '04d9d8d',
+ },
+ {
+ components: ['accordion'],
+ version: '0.13.0',
+ prs: ['32935'],
+ description: `Made Accordion a \`bg\` provider so nested components like Button auto-increment their background level. Updated \`useDefinition\` to resolve \`bg\` \`propDef\` defaults for provider components.`,
+
+ commitSha: 'a1f4bee',
+ },
+ {
+ components: ['card'],
+ version: '0.13.0',
+ prs: ['33116'],
+ description: `Added interactive support to the \`Card\` component. Pass \`onPress\` to make the entire card surface pressable, or \`href\` to make it navigate to a URL. A transparent overlay handles the interaction while nested buttons and links remain independently clickable.`,
+
+ commitSha: 'db92751',
+ },
+ {
+ components: ['link', 'button-link', 'tab', 'menu-item', 'tag', 'row'],
+ version: '0.13.0',
+ prs: ['33150'],
+ description: `Added analytics capabilities to the component library. Components with navigation behavior (Link, ButtonLink, Tab, MenuItem, Tag, Row) now fire analytics events on click when a \`BUIProvider\` is present.
+
+ New exports: \`BUIProvider\`, \`useAnalytics\`, \`getNodeText\`, and associated types (\`AnalyticsTracker\`, \`UseAnalyticsFn\`, \`BUIProviderProps\`, \`AnalyticsEventAttributes\`).
+
+ Components with analytics support now accept a \`noTrack\` prop to suppress event firing.`,
+
+ commitSha: '12d8afe',
+ },
+ {
+ components: ['table-root'],
+ version: '0.13.0',
+ prs: ['33322'],
+ description: `Added a \`loading\` prop and \`data-loading\` data attribute to \`TableRoot\`, allowing consumers to distinguish between stale data and initial loading states. Both \`stale\` and \`loading\` set \`aria-busy\` on the table.`,
+
+ commitSha: 'b838cc9',
+ },
+ {
+ components: ['table'],
+ version: '0.13.0',
+ prs: ['33322'],
+ description: `Improved the \`Table\` component loading state to show a skeleton UI with visible headers instead of plain "Loading..." text. The table now renders its full structure during loading, with animated skeleton rows in place of data. The loading state includes proper accessibility support with \`aria-busy\` on the table and screen reader announcements.`,
+
+ commitSha: '690786f',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['33078'],
+ description: `Fixed neutral-1 hover & pressed state in light mode.`,
+
+ commitSha: '58224d3',
+ },
+ {
+ components: [
+ 'avatar',
+ 'checkbox',
+ 'container',
+ 'dialog',
+ 'field-error',
+ 'field-label',
+ 'flex',
+ 'full-page',
+ 'grid',
+ 'header-page',
+ 'link',
+ 'menu',
+ 'password-field',
+ 'plugin-header',
+ 'popover',
+ 'radio-group',
+ 'searchfield',
+ 'select',
+ 'skeleton',
+ 'switch',
+ 'table',
+ 'table-pagination',
+ 'tabs',
+ 'tag-group',
+ 'text',
+ 'textfield',
+ 'toggle-button',
+ 'toggle-button-group',
+ 'tooltip',
+ 'visually-hidden',
+ ],
+ version: '0.13.0',
+ prs: ['33050'],
+ description: `Migrated all components from \`useStyles\` to \`useDefinition\` hook. Exported \`OwnProps\` types for each component, enabling better type composition for consumers.`,
+
+ commitSha: '95702ab',
+ },
+ {
+ components: ['card'],
+ version: '0.13.0',
+ prs: ['33151'],
+ description: `Fixed interactive cards so that CardBody can scroll when the card has a constrained height. Previously, the overlay element blocked scroll events.`,
+
+ commitSha: '430d5ed',
+ },
+ {
+ components: ['container'],
+ version: '0.13.0',
+ prs: ['32984'],
+ description: `Removed the \`transition\` on \`Container\` padding to prevent an unwanted animation when the viewport is resized.`,
+
+ commitSha: '4c2c350',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['32542'],
+ description: `Fixed --bui-fg-success token in light mode to be more accessible.`,
+
+ commitSha: 'e0b7eb0',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['33188'],
+ description: `Deprecated the \`HeaderPage\` component name in favor of \`Header\`. The old \`HeaderPage\`, \`HeaderPageProps\`, \`HeaderPageOwnProps\`, \`HeaderPageBreadcrumb\`, and \`HeaderPageDefinition\` exports are still available as deprecated aliases.`,
+
+ commitSha: 'ad7c883',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['33379'],
+ description: `Added documentation for the table cell wrapper requirement to TSDoc comments for \`Cell\`, \`CellText\`, \`CellProfile\`, \`ColumnConfig\`, and \`RowRenderFn\`.`,
+
+ commitSha: '0ebde15',
+ },
+ {
+ components: ['search-autocomplete', 'search-autocomplete-item'],
+ version: '0.13.0',
+ prs: ['33340'],
+ description: `Added \`SearchAutocomplete\` and \`SearchAutocompleteItem\` components for building accessible search-with-results patterns. Built on React Aria's Autocomplete with keyboard navigation and screen reader support. Designed for async/external search results with a configurable popover width.`,
+
+ commitSha: 'd9d2dd6',
+ },
+ {
+ components: ['checkbox'],
+ version: '0.13.0',
+ prs: ['33394'],
+ description: `Made Checkbox \`children\` optional and added a dev warning when neither a visible label, \`aria-label\`, nor \`aria-labelledby\` is provided. The label wrapper div is no longer rendered when there are no children, removing the unnecessary gap.`,
+
+ commitSha: 'a6b84e1',
+ },
+ {
+ components: ['dialog'],
+ version: '0.13.0',
+ prs: ['33352'],
+ description: `Fixed \`Dialog\` content overflowing when no \`height\` prop is set. The dialog now grows with its content and scrolls when content exceeds the viewport height.`,
+
+ commitSha: 'b99f6d5',
+ },
+ {
+ components: [
+ 'accordion',
+ 'button',
+ 'button-icon',
+ 'button-link',
+ 'card',
+ 'list',
+ 'menu',
+ 'select',
+ 'toggle-button-group',
+ ],
+ version: '0.13.0',
+ prs: ['33358'],
+ description: `Fixed focus ring styles to use React Aria's \`[data-focus-visible]\` data attribute instead of the native CSS \`:focus-visible\` pseudo-class. This ensures keyboard focus rings render reliably when focus is managed programmatically by React Aria (e.g. inside a GridList, Menu, or Select).`,
+
+ commitSha: '2f581de',
+ },
+ {
+ components: ['container', 'header'],
+ version: '0.13.0',
+ prs: ['33354'],
+ description: `Fixed incorrect bottom spacing caused by \`Container\` using \`padding-bottom\` for its default bottom spacing. Changed to \`margin-bottom\` and prevented it from applying when \`Container\` is used as the \`Header\` root element.`,
+
+ commitSha: '17d6398',
+ },
+ {
+ components: ['plugin-header'],
+ version: '0.13.0',
+ prs: ['33368'],
+ description: `Fixed \`PluginHeader\` to avoid triggering \`ResizeObserver loop completed with undelivered notifications\` warnings when used in layouts that react to the header height, such as pages that use \`FullPage\`.`,
+
+ commitSha: '2e5c651',
+ },
+ {
+ components: ['tabs', 'plugin-header'],
+ version: '0.13.0',
+ prs: ['33047'],
+ description: `Fixed tab \`matchStrategy\` matching to ignore query parameters and hash fragments in tab \`href\` values. Previously, tabs with query params in their \`href\` (e.g., \`/page?group=foo\`) would never show as active since matching compared the full \`href\` string against \`location.pathname\` which never includes query params.`,
+
+ commitSha: 'd4fa5b4',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['33363'],
+ description: `Fixed Table component to use current \`--bui-bg-neutral-1\` tokens instead of the removed \`--bui-bg-tint\` tokens, restoring row hover, selected, pressed, and disabled background colors.`,
+
+ commitSha: 'bc42b60',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['33378'],
+ description: `Fixed a bug in the \`useTable\` hook where the loading skeleton was never shown for \`complete\` mode when using \`getData\`. The initial data state was an empty array instead of \`undefined\`, causing the \`Table\` component to skip the loading state.`,
+
+ commitSha: '9314ff5',
+ },
+ {
+ components: ['table'],
+ version: '0.13.0',
+ prs: ['33256'],
+ description: `Fixed Table column headers overflowing and wrapping when there is not enough space. Headers now truncate with ellipsis instead.`,
+
+ commitSha: 'f42f4cc',
+ },
+ {
+ components: ['row'],
+ version: '0.13.0',
+ prs: ['33394'],
+ description: `Fixed Table row hover, selected, pressed, and disabled background states to use the correct neutral token level based on the container background.`,
+
+ commitSha: '1f9682b',
+ },
+ {
+ components: ['row'],
+ version: '0.13.0',
+ prs: ['33256'],
+ description: `Fixed Table rows showing a pointer cursor when not interactive. Rows now only show \`cursor: pointer\` when they have an \`href\`, are selectable, or are pressable.`,
+
+ commitSha: 'fbd5c5a',
+ },
+ {
+ components: ['row'],
+ version: '0.13.0',
+ prs: ['33353'],
+ description: `Fixed \`Table\` rows with external \`href\` values to open in a new tab by automatically applying \`target="_blank"\` and \`rel="noopener noreferrer"\`.`,
+
+ commitSha: '612c217',
+ },
+ {
+ components: ['table'],
+ version: '0.13.0',
+ prs: ['33394'],
+ description: `Updated Table selection checkboxes to use \`aria-label\` instead of empty fragment children, improving accessibility and removing the unnecessary label gap in the selection cells.`,
+
+ commitSha: '545129a',
+ },
+ {
+ components: ['button', 'button-icon', 'button-link'],
+ version: '0.13.0',
+ prs: ['33095'],
+ description: `Fixed handling of the \`style\` prop on \`Button\`, \`ButtonIcon\`, and \`ButtonLink\` so that it is now correctly forwarded to the underlying element instead of being silently dropped.`,
+
+ commitSha: '36987db',
+ },
+ {
+ components: ['link'],
+ version: '0.13.0',
+ prs: ['33050'],
+ description: `Fixed Link variant default from \`'body'\` to \`'body-medium'\` to match actual CSS selectors. The previous default did not correspond to a valid variant value.`,
+
+ commitSha: '95702ab',
+ },
+ {
+ components: ['menu', 'select'],
+ version: '0.13.0',
+ prs: ['33049'],
+ description: `Fixed scroll overflow in Menu and Select popover content when constrained by viewport height.`,
+
+ commitSha: '9027b10',
+ },
+ {
+ components: ['flex', 'grid', 'grid-item'],
+ version: '0.13.0',
+ prs: ['33136'],
+ description: `Added support for native HTML div attributes on the \`Flex\`, \`Grid\`, and \`Grid.Item\` components.`,
+
+ commitSha: '7960d54',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['33246'],
+ description: `Added \`virtualized\` prop to \`Table\` component for virtualized rendering of large datasets. Accepts \`true\` for default row height, \`{ rowHeight: number }\` for fixed height, or \`{ estimatedRowHeight: number }\` for variable height rows.`,
+
+ commitSha: '0559408',
+ },
+ {
+ components: ['menu', 'select'],
+ version: '0.13.0',
+ prs: ['32983'],
+ description: `Fixed focus-visible outline styles for Menu and Select components.`,
+
+ commitSha: '8909359',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['33150'],
+ description: `Fixed MenuItem \`onAction\` prop ordering so user-provided \`onAction\` handlers are chained rather than silently overwritten.`,
+
+ commitSha: '12d8afe',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['33389'],
+ description: `Pages created with \`PageBlueprint\` now render the plugin header by default in the new frontend system.`,
+
+ commitSha: 'aa29b50',
+ },
+ {
+ components: ['select'],
+ version: '0.13.0',
+ prs: ['33102'],
+ description: `The \`Select\` trigger now automatically adapts its background colour based on the parent background context.`,
+
+ commitSha: 'bb66b86',
+ },
+ {
+ components: ['plugin-header'],
+ version: '0.13.0',
+ prs: ['33085'],
+ description: `Merged the internal \`PluginHeaderToolbar\` component into \`PluginHeader\`, removing the separate component and its associated types (\`PluginHeaderToolbarOwnProps\`, \`PluginHeaderToolbarProps\`) and definition (\`PluginHeaderToolbarDefinition\`). This is an internal refactor with no changes to the public API of \`PluginHeader\`.`,
+
+ commitSha: '4105a78',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['33251'],
+ description: `Updated dependency \`globals\` to \`^17.0.0\`.`,
+
+ commitSha: '9599697',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['32971'],
+ description: `Improved type safety in \`useDefinition\` by centralizing prop resolution and strengthening the \`BgPropsConstraint\` to require that \`bg\` provider components declare \`children\` as a required prop in their OwnProps type.`,
+
+ commitSha: '0f462f8',
+ },
+ {
+ components: ['radio-group'],
+ version: '0.13.0',
+ prs: ['32983'],
+ description: `Added proper cursor styles for RadioGroup items.`,
+
+ commitSha: '8909359',
+ },
+ {
+ components: ['card'],
+ version: '0.13.0',
+ prs: ['33343'],
+ description: `Fixed \`Card\` interactive cards not firing the \`onPress\` handler when clicking the card surface.`,
+
+ commitSha: 'fcaac3b',
+ },
+ {
+ components: ['textfield', 'searchfield', 'password-field'],
+ version: '0.13.0',
+ prs: ['33050'],
+ description: `Fixed \`isRequired\` prop not being passed to the underlying React Aria field components in TextField, SearchField, and PasswordField. Previously, \`isRequired\` was consumed locally for the secondary label text but never forwarded, which meant the input elements lacked \`aria-required="true"\` and React Aria's built-in required validation was not activated.`,
+
+ commitSha: 'b303857',
+ },
+ {
+ components: ['searchfield', 'textfield'],
+ version: '0.13.0',
+ prs: ['33081'],
+ description: `\`SearchField\` and \`TextField\` now automatically adapt their background color based on the parent bg context, stepping up one neutral level (e.g. neutral-1 → neutral-2) when placed on a neutral background. \`TextField\` also gains a focus ring using the \`--bui-ring\` token.`,
+
+ commitSha: '934ac03',
+ },
+ {
+ components: [],
+ version: '0.13.0',
+ prs: ['33045'],
+ description: `Improved \`useBreakpoint\` performance by sharing a single set of \`matchMedia\` listeners across all component instances instead of creating independent listeners per hook call.`,
+
+ commitSha: 'cd3cb0f',
+ },
+ {
+ components: ['alert', 'container', 'dialog-body', 'field-label'],
+ version: '0.13.0',
+ prs: ['33095'],
+ description: `Extended \`AlertProps\`, \`ContainerProps\`, \`DialogBodyProps\`, and \`FieldLabelProps\` with native div element props to allow passing attributes like \`aria-*\` and \`data-*\`.`,
+
+ commitSha: '36987db',
+ },
+];
diff --git a/docs-ui/src/utils/changelogs/v0.9.0.ts b/docs-ui/src/utils/changelogs/v0.9.0.ts
index de5c40c5de..e0a3993d33 100644
--- a/docs-ui/src/utils/changelogs/v0.9.0.ts
+++ b/docs-ui/src/utils/changelogs/v0.9.0.ts
@@ -114,7 +114,7 @@ After:
'switch',
'skeleton',
'plugin-header',
- 'header-page',
+ 'header',
'tabs',
],
version: '0.9.0',
diff --git a/docs-ui/src/utils/data.ts b/docs-ui/src/utils/data.ts
index a1ef483785..bd37760ea4 100644
--- a/docs-ui/src/utils/data.ts
+++ b/docs-ui/src/utils/data.ts
@@ -62,13 +62,17 @@ export const components: Page[] = [
slug: 'plugin-header',
},
{
- title: 'HeaderPage',
- slug: 'header-page',
+ title: 'Header',
+ slug: 'header',
},
{
title: 'Link',
slug: 'link',
},
+ {
+ title: 'List',
+ slug: 'list',
+ },
{
title: 'Menu',
slug: 'menu',
@@ -85,6 +89,10 @@ export const components: Page[] = [
title: 'RadioGroup',
slug: 'radio-group',
},
+ {
+ title: 'SearchAutocomplete',
+ slug: 'search-autocomplete',
+ },
{
title: 'SearchField',
slug: 'search-field',
@@ -97,6 +105,10 @@ export const components: Page[] = [
title: 'Skeleton',
slug: 'skeleton',
},
+ {
+ title: 'Slider',
+ slug: 'slider',
+ },
{
title: 'Switch',
slug: 'switch',
@@ -138,3 +150,10 @@ export const components: Page[] = [
slug: 'visually-hidden',
},
];
+
+export const hooks: Page[] = [
+ {
+ title: 'useBreakpoint',
+ slug: 'use-breakpoint',
+ },
+];
diff --git a/docs-ui/src/utils/types.ts b/docs-ui/src/utils/types.ts
index ec762536c6..8e03a1df07 100644
--- a/docs-ui/src/utils/types.ts
+++ b/docs-ui/src/utils/types.ts
@@ -1,5 +1,8 @@
export type Component =
| 'accordion'
+ | 'accordion-group'
+ | 'accordion-panel'
+ | 'accordion-trigger'
| 'alert'
| 'avatar'
| 'box'
@@ -7,42 +10,93 @@ export type Component =
| 'button-icon'
| 'button-link'
| 'card'
+ | 'card-body'
+ | 'card-footer'
+ | 'card-header'
+ | 'cell'
+ | 'cell-profile'
+ | 'cell-text'
| 'checkbox'
| 'collapsible'
+ | 'column'
| 'container'
| 'datatable'
| 'dialog'
+ | 'dialog-body'
+ | 'dialog-footer'
+ | 'dialog-header'
+ | 'dialog-trigger'
+ | 'field-error'
+ | 'field-label'
| 'flex'
+ | 'full-page'
| 'grid'
- | 'plugin-header'
+ | 'grid-item'
+ | 'header'
| 'header-page'
| 'heading'
| 'icon'
| 'link'
+ | 'list'
+ | 'list-row'
| 'menu'
+ | 'menu-autocomplete'
+ | 'menu-autocomplete-listbox'
+ | 'menu-item'
+ | 'menu-list-box'
+ | 'menu-list-box-item'
+ | 'menu-section'
+ | 'menu-separator'
+ | 'menu-trigger'
| 'password-field'
+ | 'plugin-header'
+ | 'popover'
+ | 'radio'
| 'radio-group'
+ | 'row'
| 'scrollarea'
+ | 'search-autocomplete'
+ | 'search-autocomplete-item'
| 'searchfield'
| 'select'
| 'skeleton'
+ | 'submenu-trigger'
| 'switch'
+ | 'tab'
+ | 'tab-list'
+ | 'tab-panel'
| 'table'
+ | 'table-body'
+ | 'table-header'
+ | 'table-pagination'
+ | 'table-root'
| 'tabs'
+ | 'tag'
| 'tag-group'
| 'text'
| 'textfield'
+ | 'toggle-button'
+ | 'toggle-button-group'
| 'tooltip'
+ | 'tooltip-trigger'
| 'visually-hidden';
+export type Hook = 'use-breakpoint';
+
export type Version = `${number}.${number}.${number}`;
-export interface ChangelogProps {
- components: Component[];
+export type AtLeastOne = K extends string
+ ? Pick & Partial>
+ : never;
+
+export type ChangelogProps = {
description: string;
version: Version;
prs: string[];
breaking?: boolean;
commitSha?: string;
migration?: string;
-}
+} & AtLeastOne<{
+ components: Component[];
+ hooks: Hook[];
+}>;
diff --git a/docs-ui/yarn.lock b/docs-ui/yarn.lock
index 55b680f755..085b426dc6 100644
--- a/docs-ui/yarn.lock
+++ b/docs-ui/yarn.lock
@@ -336,14 +336,14 @@ __metadata:
linkType: hard
"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.34.4, @codemirror/view@npm:^6.35.0":
- version: 6.39.14
- resolution: "@codemirror/view@npm:6.39.14"
+ version: 6.39.16
+ resolution: "@codemirror/view@npm:6.39.16"
dependencies:
"@codemirror/state": "npm:^6.5.0"
crelt: "npm:^1.0.6"
style-mod: "npm:^4.1.0"
w3c-keyname: "npm:^2.2.4"
- checksum: 10/956e79758d97e9fc6f6fd9a21575148cb0126712e0a3f78dc03031ebed1a5787eae581eda140f1af6686a5822b3c9c3a67547d2421a16003d45a2c0b6f83ca5e
+ checksum: 10/199576febda2a91fe7676b8708627ed2e38d7e964ec8258331422fe7c7f89003eee2de7dec828e09c046de005742fd476cae6ceebc7bd994744f771253bfcbf3
languageName: node
linkType: hard
@@ -393,14 +393,14 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/config-array@npm:^0.21.1":
- version: 0.21.1
- resolution: "@eslint/config-array@npm:0.21.1"
+"@eslint/config-array@npm:^0.21.2":
+ version: 0.21.2
+ resolution: "@eslint/config-array@npm:0.21.2"
dependencies:
"@eslint/object-schema": "npm:^2.1.7"
debug: "npm:^4.3.1"
- minimatch: "npm:^3.1.2"
- checksum: 10/6eaa0435972f735ce52d581f355a0b616e50a9b8a73304a7015398096e252798b9b3b968a67b524eefb0fdeacc57c4d960f0ec6432abe1c1e24be815b88c5d18
+ minimatch: "npm:^3.1.5"
+ checksum: 10/148477ba995cf57fc725601916d5a7914aa249112d8bec2c3ac9122e2b2f540e6ef013ff4f6785346a4b565f09b20db127fa6f7322f5ffbdb3f1f8d2078a531c
languageName: node
linkType: hard
@@ -422,27 +422,27 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/eslintrc@npm:^3.3.1":
- version: 3.3.3
- resolution: "@eslint/eslintrc@npm:3.3.3"
+"@eslint/eslintrc@npm:^3.3.5":
+ version: 3.3.5
+ resolution: "@eslint/eslintrc@npm:3.3.5"
dependencies:
- ajv: "npm:^6.12.4"
+ ajv: "npm:^6.14.0"
debug: "npm:^4.3.2"
espree: "npm:^10.0.1"
globals: "npm:^14.0.0"
ignore: "npm:^5.2.0"
import-fresh: "npm:^3.2.1"
js-yaml: "npm:^4.1.1"
- minimatch: "npm:^3.1.2"
+ minimatch: "npm:^3.1.5"
strip-json-comments: "npm:^3.1.1"
- checksum: 10/b586a364ff15ce1b68993aefc051ca330b1fece15fb5baf4a708d00113f9a14895cffd84a5f24c5a97bd4b4321130ab2314f90aa462a250f6b859c2da2cba1f3
+ checksum: 10/edabb65693d82a88cac3b2cf932a0f825e986b5e0a21ef08782d07e3a61ad87d39db67cfd5aeb146fd5053e5e24e389dbe5649ab22936a71d633c7b32a7e6d86
languageName: node
linkType: hard
-"@eslint/js@npm:9.39.2":
- version: 9.39.2
- resolution: "@eslint/js@npm:9.39.2"
- checksum: 10/6b7f676746f3111b5d1b23715319212ab9297868a0fa9980d483c3da8965d5841673aada2d5653e85a3f7156edee0893a7ae7035211b4efdcb2848154bb947f2
+"@eslint/js@npm:9.39.4":
+ version: 9.39.4
+ resolution: "@eslint/js@npm:9.39.4"
+ checksum: 10/0a7ab4c4108cf2cadf66849ebd20f5957cc53052b88d8807d0b54e489dbf6ffcaf741e144e7f9b187c395499ce2e6ddc565dbfa4f60c6df455cf2b30bcbdc5a3
languageName: node
linkType: hard
@@ -928,25 +928,25 @@ __metadata:
languageName: node
linkType: hard
-"@next/env@npm:16.1.6":
- version: 16.1.6
- resolution: "@next/env@npm:16.1.6"
- checksum: 10/52f17ead23b3dc42e613be469b3a179c9c199f43ab42dfcba1f1bc25adba3d7818b4159ab1c797ddfa64a081c21cc1f753a997c14e164d8a88873e0774395d37
+"@next/env@npm:16.2.1":
+ version: 16.2.1
+ resolution: "@next/env@npm:16.2.1"
+ checksum: 10/c4f19f1767d7a1e8e9ff93cdee7e3b6a923d26d9d71f44124a797f03703ab9a18508b5ede997cc99d0307f2e0d0d1c426e9673a6c11ea10e170b87462a572236
languageName: node
linkType: hard
-"@next/eslint-plugin-next@npm:16.1.6":
- version: 16.1.6
- resolution: "@next/eslint-plugin-next@npm:16.1.6"
+"@next/eslint-plugin-next@npm:16.2.1":
+ version: 16.2.1
+ resolution: "@next/eslint-plugin-next@npm:16.2.1"
dependencies:
fast-glob: "npm:3.3.1"
- checksum: 10/74b427e005cd5f0ecf7b557fcbca1780f32b94c6e0fa3a774c20b79391ce660e6b52e2e70022c374f1473000de187188e58c812d53e4db440c3d8ee0848477be
+ checksum: 10/8892f8ea6b2a642d9284f05098827f6c15acf3c7e2ff592430080355f8eb3cec7a70ba7543960819d69eff4eba75430eb6ce2fee4c8918abf0d8a9366fa0a59a
languageName: node
linkType: hard
-"@next/mdx@npm:16.1.6":
- version: 16.1.6
- resolution: "@next/mdx@npm:16.1.6"
+"@next/mdx@npm:16.2.1":
+ version: 16.2.1
+ resolution: "@next/mdx@npm:16.2.1"
dependencies:
source-map: "npm:^0.7.0"
peerDependencies:
@@ -957,62 +957,62 @@ __metadata:
optional: true
"@mdx-js/react":
optional: true
- checksum: 10/830bc4c38f6c38c1420348e96a6f172c88bf32a2dc670f58593b3863bf8f0acd31c0880a231a59257c9f7acaf2a70a9e90e7fae6dd1a3dcf2e0ab3f643cdb1f5
+ checksum: 10/eed6747ac8dd048384eb0fd2cc5668bef3e2bc9c27c45a07c03dae8a2466638a26f46b440d6d27546c5f0fafc0df7dbacfe8f503f870e6e77a7c336d915500c4
languageName: node
linkType: hard
-"@next/swc-darwin-arm64@npm:16.1.6":
- version: 16.1.6
- resolution: "@next/swc-darwin-arm64@npm:16.1.6"
+"@next/swc-darwin-arm64@npm:16.2.1":
+ version: 16.2.1
+ resolution: "@next/swc-darwin-arm64@npm:16.2.1"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@next/swc-darwin-x64@npm:16.1.6":
- version: 16.1.6
- resolution: "@next/swc-darwin-x64@npm:16.1.6"
+"@next/swc-darwin-x64@npm:16.2.1":
+ version: 16.2.1
+ resolution: "@next/swc-darwin-x64@npm:16.2.1"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@next/swc-linux-arm64-gnu@npm:16.1.6":
- version: 16.1.6
- resolution: "@next/swc-linux-arm64-gnu@npm:16.1.6"
+"@next/swc-linux-arm64-gnu@npm:16.2.1":
+ version: 16.2.1
+ resolution: "@next/swc-linux-arm64-gnu@npm:16.2.1"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@next/swc-linux-arm64-musl@npm:16.1.6":
- version: 16.1.6
- resolution: "@next/swc-linux-arm64-musl@npm:16.1.6"
+"@next/swc-linux-arm64-musl@npm:16.2.1":
+ version: 16.2.1
+ resolution: "@next/swc-linux-arm64-musl@npm:16.2.1"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@next/swc-linux-x64-gnu@npm:16.1.6":
- version: 16.1.6
- resolution: "@next/swc-linux-x64-gnu@npm:16.1.6"
+"@next/swc-linux-x64-gnu@npm:16.2.1":
+ version: 16.2.1
+ resolution: "@next/swc-linux-x64-gnu@npm:16.2.1"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@next/swc-linux-x64-musl@npm:16.1.6":
- version: 16.1.6
- resolution: "@next/swc-linux-x64-musl@npm:16.1.6"
+"@next/swc-linux-x64-musl@npm:16.2.1":
+ version: 16.2.1
+ resolution: "@next/swc-linux-x64-musl@npm:16.2.1"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@next/swc-win32-arm64-msvc@npm:16.1.6":
- version: 16.1.6
- resolution: "@next/swc-win32-arm64-msvc@npm:16.1.6"
+"@next/swc-win32-arm64-msvc@npm:16.2.1":
+ version: 16.2.1
+ resolution: "@next/swc-win32-arm64-msvc@npm:16.2.1"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@next/swc-win32-x64-msvc@npm:16.1.6":
- version: 16.1.6
- resolution: "@next/swc-win32-x64-msvc@npm:16.1.6"
+"@next/swc-win32-x64-msvc@npm:16.2.1":
+ version: 16.2.1
+ resolution: "@next/swc-win32-x64-msvc@npm:16.2.1"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -1176,11 +1176,11 @@ __metadata:
linkType: hard
"@remixicon/react@npm:^4.6.0":
- version: 4.8.0
- resolution: "@remixicon/react@npm:4.8.0"
+ version: 4.9.0
+ resolution: "@remixicon/react@npm:4.9.0"
peerDependencies:
react: ">=18.2.0"
- checksum: 10/10241f2e07826ce7d595cf9687a35c96cc9cf9eb68a9431d7dfa1b9df479dbef302874d28c0502cee2a536cf34722de7c49ed12d10a2b071e4e42ba4a278c516
+ checksum: 10/3d8f1d86b2bb20ab5e44d15f18811e928b0886f7710eb7a1516afb9913ba72e46facec5dfee382825139d800bcbb6704c15d0c760d0f977c12257d4af8db3295
languageName: node
linkType: hard
@@ -1191,74 +1191,74 @@ __metadata:
languageName: node
linkType: hard
-"@shikijs/core@npm:3.22.0":
- version: 3.22.0
- resolution: "@shikijs/core@npm:3.22.0"
+"@shikijs/core@npm:3.23.0":
+ version: 3.23.0
+ resolution: "@shikijs/core@npm:3.23.0"
dependencies:
- "@shikijs/types": "npm:3.22.0"
+ "@shikijs/types": "npm:3.23.0"
"@shikijs/vscode-textmate": "npm:^10.0.2"
"@types/hast": "npm:^3.0.4"
hast-util-to-html: "npm:^9.0.5"
- checksum: 10/fc716bb042accd5061d0de142ee28c20b6e6d489f6516b901ee4db9ed5d802f59c9033106d8140c111a909b761d79523fced92c1367a6f97c4f0e0dad0f29bc9
+ checksum: 10/b692850d79d36f90cb92d517de60c3739e888a3ce0b49450090fa954166eae6655ae1884dbbb971e254b907644cc6aa7757fa84fc2437c45208e09143d4f75ab
languageName: node
linkType: hard
-"@shikijs/engine-javascript@npm:3.22.0":
- version: 3.22.0
- resolution: "@shikijs/engine-javascript@npm:3.22.0"
+"@shikijs/engine-javascript@npm:3.23.0":
+ version: 3.23.0
+ resolution: "@shikijs/engine-javascript@npm:3.23.0"
dependencies:
- "@shikijs/types": "npm:3.22.0"
+ "@shikijs/types": "npm:3.23.0"
"@shikijs/vscode-textmate": "npm:^10.0.2"
oniguruma-to-es: "npm:^4.3.4"
- checksum: 10/2fcadb897d0220ca70cc63d697e474ce13addc462a387249a852c882a58b9a1e7d6f9b42f694d324403c4acde8486dd54823228602f5ff4270e4298a43c10f51
+ checksum: 10/0ca854fb05a14a97fc83c6a54291af6b387fb1c319bd09426eeca3e0ed597bb0b81a79459838c65d0fadce012ba3f166bebb587828d0cc5511b943c743eae2be
languageName: node
linkType: hard
-"@shikijs/engine-oniguruma@npm:3.22.0":
- version: 3.22.0
- resolution: "@shikijs/engine-oniguruma@npm:3.22.0"
+"@shikijs/engine-oniguruma@npm:3.23.0":
+ version: 3.23.0
+ resolution: "@shikijs/engine-oniguruma@npm:3.23.0"
dependencies:
- "@shikijs/types": "npm:3.22.0"
+ "@shikijs/types": "npm:3.23.0"
"@shikijs/vscode-textmate": "npm:^10.0.2"
- checksum: 10/611654868bc2538ab4d7c3770ad2829a623fcf354ce5c09ba8fde29ecc106b5e4a6270de7d970da4524c1ad00d4f7ed3e5afef2be2ff0b94f8bb8541d0775320
+ checksum: 10/edd8983be86f6b055793813e80ecf31c3cefdd896f3742797ae03f2cbb9f467ac736c4b152d4a5c42dbd17da17d24ff798a15d37bcf6205d7f8cd8f44e2a11e0
languageName: node
linkType: hard
-"@shikijs/langs@npm:3.22.0":
- version: 3.22.0
- resolution: "@shikijs/langs@npm:3.22.0"
+"@shikijs/langs@npm:3.23.0":
+ version: 3.23.0
+ resolution: "@shikijs/langs@npm:3.23.0"
dependencies:
- "@shikijs/types": "npm:3.22.0"
- checksum: 10/091cdaeda7763e1170a6a289315a8371f07af541ad4d8fe17a247b45c4a3be82dc9d321a1e4589cdc8464a694692a5c8bfdd1909372b6ec52b0caa53d69e4a1f
+ "@shikijs/types": "npm:3.23.0"
+ checksum: 10/115b1afb9eb4eb300eb68b146e442f7e6d196878798c5b9d75dd53a6ef1e1c3b27e325353a10973e3fa47d88630e937b8a3cf3b37b6eef80bf2aec3d51657bb3
languageName: node
linkType: hard
-"@shikijs/themes@npm:3.22.0":
- version: 3.22.0
- resolution: "@shikijs/themes@npm:3.22.0"
+"@shikijs/themes@npm:3.23.0":
+ version: 3.23.0
+ resolution: "@shikijs/themes@npm:3.23.0"
dependencies:
- "@shikijs/types": "npm:3.22.0"
- checksum: 10/ba60849b4056ea4e243e2c2047382312e504d10f8e1ee09d73041d9de54bce30e28c79168406d8a8e3b7a7ab19d0f9c610e3469679f96145aa975460ac7e1407
+ "@shikijs/types": "npm:3.23.0"
+ checksum: 10/741987380445b788aea6cc1e03492bc06950f1a0461edf45083bd226889c5ba78c7ed1af9724afacab7d6471777f0c0e8871577183dfb2cfbceb255663374835
languageName: node
linkType: hard
"@shikijs/transformers@npm:^3.13.0":
- version: 3.22.0
- resolution: "@shikijs/transformers@npm:3.22.0"
+ version: 3.23.0
+ resolution: "@shikijs/transformers@npm:3.23.0"
dependencies:
- "@shikijs/core": "npm:3.22.0"
- "@shikijs/types": "npm:3.22.0"
- checksum: 10/782569a5a8a09dae18402162ad10bba36d8c4e4b6f01c613ccc8151071b06872c9bc32f06bb31147bb58b545b034cd5340d21b1c80c2862cef3983fac6eaef3c
+ "@shikijs/core": "npm:3.23.0"
+ "@shikijs/types": "npm:3.23.0"
+ checksum: 10/d3c86e75668254beda1d1bbd34a00995f684dffd4efdaf60edaa8284ecc9130255c7615c7bca9400a1c50724dd0cd0133469b6e75ffe2033613fd64dcfca5821
languageName: node
linkType: hard
-"@shikijs/types@npm:3.22.0":
- version: 3.22.0
- resolution: "@shikijs/types@npm:3.22.0"
+"@shikijs/types@npm:3.23.0":
+ version: 3.23.0
+ resolution: "@shikijs/types@npm:3.23.0"
dependencies:
"@shikijs/vscode-textmate": "npm:^10.0.2"
"@types/hast": "npm:^3.0.4"
- checksum: 10/c5abd9050d4645bbf9f6c9af1a3df31d24d823d21c232e47b171cc68bd4865adab20533503a5c97ffb9e87eae2f88e7a3835d64a0a8aa11a69dbfd45aabd58bf
+ checksum: 10/18b5703d445d53dd6782a3e2c7332009302c6c046f301d9cd99f1a26b9c8329b3e502b096894bffac61f78835c533827bab2ce78a39666ab87b78f8d7ca11f7b
languageName: node
linkType: hard
@@ -1534,9 +1534,9 @@ __metadata:
languageName: node
linkType: hard
-"@uiw/codemirror-extensions-basic-setup@npm:4.25.4":
- version: 4.25.4
- resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.25.4"
+"@uiw/codemirror-extensions-basic-setup@npm:4.25.8":
+ version: 4.25.8
+ resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.25.8"
dependencies:
"@codemirror/autocomplete": "npm:^6.0.0"
"@codemirror/commands": "npm:^6.0.0"
@@ -1553,13 +1553,13 @@ __metadata:
"@codemirror/search": ">=6.0.0"
"@codemirror/state": ">=6.0.0"
"@codemirror/view": ">=6.0.0"
- checksum: 10/8fa523af7cb4ea36db5263221e5a34ecbf18e57ee5c57fbb46f7bfd08ff08523b9fe2c5ae376117d98edaa1a904d1a2966123710e0d866a78ec2d1359955773c
+ checksum: 10/a8d83465f9f3393b6e95d98ae7f3616ad57f819bce64224831d3db19647524538fc013973074a63551afa69daad9a8ab05f2e5c7441db7e30e722495d7e991d3
languageName: node
linkType: hard
"@uiw/codemirror-themes@npm:^4.23.7":
- version: 4.25.4
- resolution: "@uiw/codemirror-themes@npm:4.25.4"
+ version: 4.25.8
+ resolution: "@uiw/codemirror-themes@npm:4.25.8"
dependencies:
"@codemirror/language": "npm:^6.0.0"
"@codemirror/state": "npm:^6.0.0"
@@ -1568,19 +1568,19 @@ __metadata:
"@codemirror/language": ">=6.0.0"
"@codemirror/state": ">=6.0.0"
"@codemirror/view": ">=6.0.0"
- checksum: 10/aab62e2c16a10dbde4592cef704d386c1a9e3caece4ff0eed85e9d9a5acdeaf3fd8717e2cb6a6795e12e890b8600956effb52d453aacbc618934e7ae8c3e59e3
+ checksum: 10/e9983b0f6e663ca200d36437b6b52b4061ce5ccefece6f738b15370a8a7ac6774e7139a82e9e28ae273692e25d0c0804693587ea0967e163a1c7ac8cf3859cd1
languageName: node
linkType: hard
"@uiw/react-codemirror@npm:^4.23.7":
- version: 4.25.4
- resolution: "@uiw/react-codemirror@npm:4.25.4"
+ version: 4.25.8
+ resolution: "@uiw/react-codemirror@npm:4.25.8"
dependencies:
"@babel/runtime": "npm:^7.18.6"
"@codemirror/commands": "npm:^6.1.0"
"@codemirror/state": "npm:^6.1.1"
"@codemirror/theme-one-dark": "npm:^6.0.0"
- "@uiw/codemirror-extensions-basic-setup": "npm:4.25.4"
+ "@uiw/codemirror-extensions-basic-setup": "npm:4.25.8"
codemirror: "npm:^6.0.0"
peerDependencies:
"@babel/runtime": ">=7.11.0"
@@ -1590,7 +1590,7 @@ __metadata:
codemirror: ">=6.0.0"
react: ">=17.0.0"
react-dom: ">=17.0.0"
- checksum: 10/679966cfa5cc2e7d5b7c8b40060018bebfc3a2d66492672c64e659e1c719cedd7a10ed7fb3aa014d77c42927581c4ebdf6760ab56568225781798b35dc13b8c6
+ checksum: 10/8c974e22dad1ad6231f33f7db42cd5b68caaf9bea545539b06b8a89dda3427eebadf47c8f48ee0d74cdf5a25000a8fcc02bac9fe560b624955eedf1f9bb47a85
languageName: node
linkType: hard
@@ -1754,15 +1754,15 @@ __metadata:
languageName: node
linkType: hard
-"ajv@npm:^6.12.4":
- version: 6.12.6
- resolution: "ajv@npm:6.12.6"
+"ajv@npm:^6.14.0":
+ version: 6.14.0
+ resolution: "ajv@npm:6.14.0"
dependencies:
fast-deep-equal: "npm:^3.1.1"
fast-json-stable-stringify: "npm:^2.0.0"
json-schema-traverse: "npm:^0.4.1"
uri-js: "npm:^4.2.2"
- checksum: 10/48d6ad21138d12eb4d16d878d630079a2bda25a04e745c07846a4ad768319533031e28872a9b3c5790fa1ec41aabdf2abed30a56e5a03ebc2cf92184b8ee306c
+ checksum: 10/c71f14dd2b6f2535d043f74019c8169f7aeb1106bafbb741af96f34fdbf932255c919ddd46344043d03b62ea0ccb319f83667ec5eedf612393f29054fe5ce4a5
languageName: node
linkType: hard
@@ -1956,12 +1956,12 @@ __metadata:
languageName: node
linkType: hard
-"baseline-browser-mapping@npm:^2.8.3, baseline-browser-mapping@npm:^2.9.0":
- version: 2.9.19
- resolution: "baseline-browser-mapping@npm:2.9.19"
+"baseline-browser-mapping@npm:^2.9.0, baseline-browser-mapping@npm:^2.9.19":
+ version: 2.10.8
+ resolution: "baseline-browser-mapping@npm:2.10.8"
bin:
- baseline-browser-mapping: dist/cli.js
- checksum: 10/8d7bbb7fe3d1ad50e04b127c819ba6d059c01ed0d2a7a5fc3327e23a8c42855fa3a8b510550c1fe1e37916147e6a390243566d3ef85bf6130c8ddfe5cc3db530
+ baseline-browser-mapping: dist/cli.cjs
+ checksum: 10/820972372c87c65c2e665134d70aa44d5722492fb907aa79170fec84086a75de4675f6a7b717cf0a31b4c4f71cd0289b056b71e32007de97a37973a501d31dcb
languageName: node
linkType: hard
@@ -2336,7 +2336,7 @@ __metadata:
"@lezer/highlight": "npm:^1.2.1"
"@mdx-js/loader": "npm:^3.1.0"
"@mdx-js/react": "npm:^3.1.0"
- "@next/mdx": "npm:16.1.6"
+ "@next/mdx": "npm:16.2.1"
"@octokit/rest": "npm:^22.0.1"
"@remixicon/react": "npm:^4.6.0"
"@shikijs/transformers": "npm:^3.13.0"
@@ -2348,10 +2348,10 @@ __metadata:
"@uiw/react-codemirror": "npm:^4.23.7"
clsx: "npm:^2.1.1"
eslint: "npm:^9"
- eslint-config-next: "npm:16.1.6"
+ eslint-config-next: "npm:16.2.1"
html-react-parser: "npm:^5.2.5"
motion: "npm:^12.4.1"
- next: "npm:16.1.6"
+ next: "npm:16.2.1"
next-mdx-remote-client: "npm:^2.1.2"
postcss: "npm:^8.5.6"
postcss-import: "npm:^16.1.1"
@@ -2629,11 +2629,11 @@ __metadata:
languageName: node
linkType: hard
-"eslint-config-next@npm:16.1.6":
- version: 16.1.6
- resolution: "eslint-config-next@npm:16.1.6"
+"eslint-config-next@npm:16.2.1":
+ version: 16.2.1
+ resolution: "eslint-config-next@npm:16.2.1"
dependencies:
- "@next/eslint-plugin-next": "npm:16.1.6"
+ "@next/eslint-plugin-next": "npm:16.2.1"
eslint-import-resolver-node: "npm:^0.3.6"
eslint-import-resolver-typescript: "npm:^3.5.2"
eslint-plugin-import: "npm:^2.32.0"
@@ -2648,7 +2648,7 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
- checksum: 10/94828d947fe88d921ca9e59931a31d111344bbf23bd8baad50de463b05b23a823c526cb60576443c6c818136d054779970369f049c5c6a6ef7e92fae12f888d7
+ checksum: 10/3622048ff1883f801c14a21205e9f92fd83172362d6d006fc7a32ea178687a9e11aff0db3aa5fbfb86b32541b03ac127f12da8da12e8fff4430f4757e0ceecfd
languageName: node
linkType: hard
@@ -2821,22 +2821,22 @@ __metadata:
linkType: hard
"eslint@npm:^9":
- version: 9.39.2
- resolution: "eslint@npm:9.39.2"
+ version: 9.39.4
+ resolution: "eslint@npm:9.39.4"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.8.0"
"@eslint-community/regexpp": "npm:^4.12.1"
- "@eslint/config-array": "npm:^0.21.1"
+ "@eslint/config-array": "npm:^0.21.2"
"@eslint/config-helpers": "npm:^0.4.2"
"@eslint/core": "npm:^0.17.0"
- "@eslint/eslintrc": "npm:^3.3.1"
- "@eslint/js": "npm:9.39.2"
+ "@eslint/eslintrc": "npm:^3.3.5"
+ "@eslint/js": "npm:9.39.4"
"@eslint/plugin-kit": "npm:^0.4.1"
"@humanfs/node": "npm:^0.16.6"
"@humanwhocodes/module-importer": "npm:^1.0.1"
"@humanwhocodes/retry": "npm:^0.4.2"
"@types/estree": "npm:^1.0.6"
- ajv: "npm:^6.12.4"
+ ajv: "npm:^6.14.0"
chalk: "npm:^4.0.0"
cross-spawn: "npm:^7.0.6"
debug: "npm:^4.3.2"
@@ -2855,7 +2855,7 @@ __metadata:
is-glob: "npm:^4.0.0"
json-stable-stringify-without-jsonify: "npm:^1.0.1"
lodash.merge: "npm:^4.6.2"
- minimatch: "npm:^3.1.2"
+ minimatch: "npm:^3.1.5"
natural-compare: "npm:^1.4.0"
optionator: "npm:^0.9.3"
peerDependencies:
@@ -2865,7 +2865,7 @@ __metadata:
optional: true
bin:
eslint: bin/eslint.js
- checksum: 10/53ff0e9c8264e7e8d40d50fdc0c0df0b701cfc5289beedfb686c214e3e7b199702f894bbd1bb48653727bb1ecbd1147cf5f555a4ae71e1daf35020cdc9072d9f
+ checksum: 10/de95093d710e62e8c7e753220d985587c40f4a05247ed4393ffb6e6cb43a60e825a47fc5b4263151bb2fc5871a206a31d563ccbabdceee1711072ae12db90adf
languageName: node
linkType: hard
@@ -3088,9 +3088,9 @@ __metadata:
linkType: hard
"flatted@npm:^3.2.9":
- version: 3.3.3
- resolution: "flatted@npm:3.3.3"
- checksum: 10/8c96c02fbeadcf4e8ffd0fa24983241e27698b0781295622591fc13585e2f226609d95e422bcf2ef044146ffacb6b68b1f20871454eddf75ab3caa6ee5f4a1fe
+ version: 3.4.2
+ resolution: "flatted@npm:3.4.2"
+ checksum: 10/a9e78fe5c2c1fcd98209a015ccee3a6caa953e01729778e83c1fe92e68601a63e1e69cd4e573010ca99eaf585a581b80ccf1018b99283e6cbc2117bcba1e030f
languageName: node
linkType: hard
@@ -3103,11 +3103,11 @@ __metadata:
languageName: node
linkType: hard
-"framer-motion@npm:^12.34.3":
- version: 12.34.3
- resolution: "framer-motion@npm:12.34.3"
+"framer-motion@npm:^12.35.2":
+ version: 12.35.2
+ resolution: "framer-motion@npm:12.35.2"
dependencies:
- motion-dom: "npm:^12.34.3"
+ motion-dom: "npm:^12.35.2"
motion-utils: "npm:^12.29.2"
tslib: "npm:^2.4.0"
peerDependencies:
@@ -3121,7 +3121,7 @@ __metadata:
optional: true
react-dom:
optional: true
- checksum: 10/4565dc7f35c631c07ade3b77b19c14946ca37cc1125176e4b8c3a6b7d6994207867f9d9befcb2885d5bc8b6b4ea82ff8ee2cfa869f676cc28d18656acb33ff88
+ checksum: 10/10af699ff1e35a166ef60ceab464479b81624ef74de3ec9e11b427f86bd7bf2c8c8a9f24fb0646288b2d4b0c1b219203da351821fc568c7b91c6821594af4a3f
languageName: node
linkType: hard
@@ -4475,12 +4475,12 @@ __metadata:
languageName: node
linkType: hard
-"minimatch@npm:^3.1.2":
- version: 3.1.2
- resolution: "minimatch@npm:3.1.2"
+"minimatch@npm:^3.1.2, minimatch@npm:^3.1.5":
+ version: 3.1.5
+ resolution: "minimatch@npm:3.1.5"
dependencies:
brace-expansion: "npm:^1.1.7"
- checksum: 10/e0b25b04cd4ec6732830344e5739b13f8690f8a012d73445a4a19fbc623f5dd481ef7a5827fde25954cd6026fede7574cc54dc4643c99d6c6b653d6203f94634
+ checksum: 10/b11a7ee5773cd34c1a0c8436cdbe910901018fb4b6cb47aa508a18d567f6efd2148507959e35fba798389b161b8604a2d704ccef751ea36bd4582f9852b7d63f
languageName: node
linkType: hard
@@ -4500,12 +4500,12 @@ __metadata:
languageName: node
linkType: hard
-"motion-dom@npm:^12.34.3":
- version: 12.34.3
- resolution: "motion-dom@npm:12.34.3"
+"motion-dom@npm:^12.35.2":
+ version: 12.35.2
+ resolution: "motion-dom@npm:12.35.2"
dependencies:
motion-utils: "npm:^12.29.2"
- checksum: 10/17abb91f87020f00470f886cced6c6ebd58b9d5848c9bb12eb38f029dc7077344cf6ac2ccbe0adf1a8996191c17a98abfe91eaa99a1723909d7dd25a4f2b1101
+ checksum: 10/dd009e58b178dd80b123a86199ae78ecd6b2fc6c8e03464b2daf43b4218dfcc36042ec0af8fad2c6c157198f56849f90dc033b58f46478b45fbaeaefcc2710ad
languageName: node
linkType: hard
@@ -4517,10 +4517,10 @@ __metadata:
linkType: hard
"motion@npm:^12.4.1":
- version: 12.34.3
- resolution: "motion@npm:12.34.3"
+ version: 12.35.2
+ resolution: "motion@npm:12.35.2"
dependencies:
- framer-motion: "npm:^12.34.3"
+ framer-motion: "npm:^12.35.2"
tslib: "npm:^2.4.0"
peerDependencies:
"@emotion/is-prop-valid": "*"
@@ -4533,7 +4533,7 @@ __metadata:
optional: true
react-dom:
optional: true
- checksum: 10/1465fdee135e792f753a8ac5eb3ebdfb547165686f7a896ba947ecf2d02443f11ced9b3933aec773d4531758a9159dd24e48bd2e02c0d94754649645eac5121c
+ checksum: 10/3d99a53816634cbee1b38ed8a9a5d88bafbd29eb3bc02e78fc741c604972b4b88d317cf374bba30a1486f727bb1657ef8826f83e669a3b04fd1ec3ef75bfb62d
languageName: node
linkType: hard
@@ -4587,24 +4587,24 @@ __metadata:
languageName: node
linkType: hard
-"next@npm:16.1.6":
- version: 16.1.6
- resolution: "next@npm:16.1.6"
+"next@npm:16.2.1":
+ version: 16.2.1
+ resolution: "next@npm:16.2.1"
dependencies:
- "@next/env": "npm:16.1.6"
- "@next/swc-darwin-arm64": "npm:16.1.6"
- "@next/swc-darwin-x64": "npm:16.1.6"
- "@next/swc-linux-arm64-gnu": "npm:16.1.6"
- "@next/swc-linux-arm64-musl": "npm:16.1.6"
- "@next/swc-linux-x64-gnu": "npm:16.1.6"
- "@next/swc-linux-x64-musl": "npm:16.1.6"
- "@next/swc-win32-arm64-msvc": "npm:16.1.6"
- "@next/swc-win32-x64-msvc": "npm:16.1.6"
+ "@next/env": "npm:16.2.1"
+ "@next/swc-darwin-arm64": "npm:16.2.1"
+ "@next/swc-darwin-x64": "npm:16.2.1"
+ "@next/swc-linux-arm64-gnu": "npm:16.2.1"
+ "@next/swc-linux-arm64-musl": "npm:16.2.1"
+ "@next/swc-linux-x64-gnu": "npm:16.2.1"
+ "@next/swc-linux-x64-musl": "npm:16.2.1"
+ "@next/swc-win32-arm64-msvc": "npm:16.2.1"
+ "@next/swc-win32-x64-msvc": "npm:16.2.1"
"@swc/helpers": "npm:0.5.15"
- baseline-browser-mapping: "npm:^2.8.3"
+ baseline-browser-mapping: "npm:^2.9.19"
caniuse-lite: "npm:^1.0.30001579"
postcss: "npm:8.4.31"
- sharp: "npm:^0.34.4"
+ sharp: "npm:^0.34.5"
styled-jsx: "npm:5.1.6"
peerDependencies:
"@opentelemetry/api": ^1.1.0
@@ -4643,7 +4643,7 @@ __metadata:
optional: true
bin:
next: dist/bin/next
- checksum: 10/00322378df865d4f00d232f8b1f42f4f0ad114e1d14f660d483b01ccd533459af6202e2f80067bf41a97aa0c99c3ae1d21a0aef2c081b0a9f456b978de4bf757
+ checksum: 10/319c0b18173a90e53b5e5ffafa8a8fecb7cc340b77728796743edd996c7ee7652201892bff60c32f6a3b75abdff1449b77f13f6fab8fd56d4f9da47cf0fb9299
languageName: node
linkType: hard
@@ -4916,13 +4916,13 @@ __metadata:
linkType: hard
"postcss@npm:^8.5.6":
- version: 8.5.6
- resolution: "postcss@npm:8.5.6"
+ version: 8.5.8
+ resolution: "postcss@npm:8.5.8"
dependencies:
nanoid: "npm:^3.3.11"
picocolors: "npm:^1.1.1"
source-map-js: "npm:^1.2.1"
- checksum: 10/9e4fbe97574091e9736d0e82a591e29aa100a0bf60276a926308f8c57249698935f35c5d2f4e80de778d0cbb8dcffab4f383d85fd50c5649aca421c3df729b86
+ checksum: 10/cbacbfd7f767e2c820d4bf09a3a744834dd7d14f69ff08d1f57b1a7defce9ae5efcf31981890d9697a972a64e9965de677932ef28e4c8ba23a87aad45b82c459
languageName: node
linkType: hard
@@ -5365,7 +5365,7 @@ __metadata:
languageName: node
linkType: hard
-"sharp@npm:^0.34.4":
+"sharp@npm:^0.34.5":
version: 0.34.5
resolution: "sharp@npm:0.34.5"
dependencies:
@@ -5466,18 +5466,18 @@ __metadata:
linkType: hard
"shiki@npm:^3.13.0":
- version: 3.22.0
- resolution: "shiki@npm:3.22.0"
+ version: 3.23.0
+ resolution: "shiki@npm:3.23.0"
dependencies:
- "@shikijs/core": "npm:3.22.0"
- "@shikijs/engine-javascript": "npm:3.22.0"
- "@shikijs/engine-oniguruma": "npm:3.22.0"
- "@shikijs/langs": "npm:3.22.0"
- "@shikijs/themes": "npm:3.22.0"
- "@shikijs/types": "npm:3.22.0"
+ "@shikijs/core": "npm:3.23.0"
+ "@shikijs/engine-javascript": "npm:3.23.0"
+ "@shikijs/engine-oniguruma": "npm:3.23.0"
+ "@shikijs/langs": "npm:3.23.0"
+ "@shikijs/themes": "npm:3.23.0"
+ "@shikijs/types": "npm:3.23.0"
"@shikijs/vscode-textmate": "npm:^10.0.2"
"@types/hast": "npm:^3.0.4"
- checksum: 10/f9977e75f666253cc679e74b774af2afdfa48d52c631ab1448f47a90cd97f2290bcf50356f5b0d01a282da7f9e9f7e1938d585b213688e0d842fcba4a785f870
+ checksum: 10/4c2751cac9dbcd61b6c80aed6c97ea4954b083c0cdf0d29fab71dbf90042c82b29f104c2843b24ea593e6d56608973b7c153dd3c05520ae001bc2f294651e398
languageName: node
linkType: hard
diff --git a/docs/.well-known/skills/app-frontend-system-migration/SKILL.md b/docs/.well-known/skills/app-frontend-system-migration/SKILL.md
new file mode 100644
index 0000000000..0bfbf0714c
--- /dev/null
+++ b/docs/.well-known/skills/app-frontend-system-migration/SKILL.md
@@ -0,0 +1,651 @@
+---
+name: app-frontend-system-migration
+description: Migrate a Backstage app from the old frontend system to the new one. Use this skill when converting an app to use the new extension-based frontend system, including the hybrid migration phase and the full migration of routes, sidebar, plugins, APIs, themes, and other app-level concerns.
+---
+
+# App Frontend System Migration Skill
+
+This skill helps migrate a Backstage app package (`packages/app`) from the old frontend system (`@backstage/app-defaults`) to the new extension-based frontend system (`@backstage/frontend-defaults`).
+
+The migration follows a two-phase approach: first get the app running in hybrid mode with compatibility helpers, then gradually remove legacy code until the app is fully on the new system.
+
+## Key Concepts
+
+- **Old system:** `createApp` from `@backstage/app-defaults`, plugins installed via `` elements in `FlatRoutes`, manual app shell with `AppRouter` + `Root`
+- **New system:** `createApp` from `@backstage/frontend-defaults`, plugins installed as `features`, extensions wired into an extension tree, no manual app shell
+- **Feature discovery:** The new system can automatically discover and install plugins from your app's dependencies — no manual imports needed. This is the default for new apps and should be enabled early in migration.
+- **Hybrid mode:** The new `createApp` with `convertLegacyAppRoot` and `convertLegacyAppOptions` from `@backstage/core-compat-api` to bridge old code
+
+## Feature Discovery
+
+Feature discovery is one of the biggest quality-of-life improvements in the new frontend system. Once enabled, any plugin added as a `package.json` dependency that exports a new-system plugin is automatically detected and installed — no code changes in `App.tsx` needed.
+
+### Enabling Feature Discovery
+
+Add this to your `app-config.yaml`:
+
+```yaml
+app:
+ packages: all
+```
+
+This is the **recommended default** for all apps using the new frontend system. Enable it as early as Phase 1.
+
+### Filtering Discovered Packages
+
+You can control which packages are discovered using `include` or `exclude` filters:
+
+```yaml
+# Only discover specific packages
+app:
+ packages:
+ include:
+ - '@backstage/plugin-catalog'
+ - '@backstage/plugin-scaffolder'
+```
+
+```yaml
+# Discover all except specific packages
+app:
+ packages:
+ exclude:
+ - '@backstage/plugin-techdocs'
+```
+
+### Disabling Individual Extensions
+
+Even with feature discovery enabled, you can disable specific extensions via config without removing the package:
+
+```yaml
+app:
+ extensions:
+ - page:techdocs: false
+ - nav-item:search: false
+```
+
+### How Discovery Works with Manual Imports
+
+Plugins that are both manually imported in `features` and auto-discovered are deduplicated — no conflicts. This means you can safely enable discovery while still explicitly importing plugins that need customization via `.withOverrides()`.
+
+### When NOT to Use Discovery
+
+Omit `app.packages` from config entirely (not `app.packages: none` — just leave it out) to disable discovery. You might do this if:
+
+- You need full control over which plugins are loaded
+- You're in early Phase 1 and want to introduce features one at a time
+- You're running in an environment where the `@backstage/cli` webpack integration isn't available
+
+Feature discovery requires that the app is built using `@backstage/cli`, which is the default for all Backstage apps.
+
+## Phase 1: Minimal Hybrid Migration
+
+### Step 1: Switch `createApp`
+
+Replace the import source for `createApp`:
+
+```typescript
+// OLD
+import { createApp } from '@backstage/app-defaults';
+
+// NEW
+import { createApp } from '@backstage/frontend-defaults';
+```
+
+### Step 2: Convert `createApp` options
+
+Use `convertLegacyAppOptions` to wrap legacy options (`apis`, `icons`, `featureFlags`, `components`, `themes`) as a feature:
+
+```tsx
+import { createApp } from '@backstage/frontend-defaults';
+import { convertLegacyAppOptions } from '@backstage/core-compat-api';
+
+const convertedOptionsModule = convertLegacyAppOptions({
+ apis,
+ icons: { alert: AlarmIcon },
+ featureFlags: [
+ {
+ name: 'scaffolder-next-preview',
+ description: 'Preview the new Scaffolder Next',
+ pluginId: '',
+ },
+ ],
+ components: {
+ SignInPage: props => (
+
+ ),
+ },
+});
+
+const app = createApp({
+ features: [convertedOptionsModule],
+});
+```
+
+### Step 3: Convert the app root
+
+Use `convertLegacyAppRoot` to convert the entire app element tree (routes, sidebar, root elements) into features:
+
+```tsx
+import { convertLegacyAppRoot } from '@backstage/core-compat-api';
+
+const convertedRootFeatures = convertLegacyAppRoot(
+ <>
+
+
+
+
+ {routes}
+
+ >,
+);
+
+const app = createApp({
+ features: [convertedOptionsModule, ...convertedRootFeatures],
+});
+
+export default app.createRoot();
+```
+
+Note: `app.createRoot()` now takes **no arguments** and returns a React **element** (not a component).
+
+### Step 4: Update `index.tsx`
+
+The default export is now an element, not a component:
+
+```typescript
+// OLD
+import App from './App';
+ReactDOM.createRoot(document.getElementById('root')!).render( );
+
+// NEW
+import app from './App';
+ReactDOM.createRoot(document.getElementById('root')!).render(app);
+```
+
+### Step 5: Update `App.test.tsx`
+
+Same change for the test file:
+
+```typescript
+import app from './App';
+
+const rendered = render(app);
+```
+
+## Phase 2: Full Migration
+
+Once the app works in hybrid mode, gradually remove legacy code and compatibility helpers.
+
+### Migrating `createApp` Options
+
+Legacy options become extensions. App-level extensions (themes, icons, sign-in page, translations) must be installed via `createFrontendModule` targeting `pluginId: 'app'`:
+
+```typescript
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+
+const app = createApp({
+ features: [
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [
+ lightTheme,
+ signInPage,
+ exampleIconBundle,
+ catalogTranslations,
+ ],
+ }),
+ ],
+});
+```
+
+#### APIs → `ApiBlueprint`
+
+In the new system, APIs are extensions that follow **ownership rules**. Understanding which `pluginId` to use when wrapping an API in a `createFrontendModule` is critical — using the wrong one will cause conflict errors at runtime.
+
+**Ownership rules:**
+
+- Each API has an **owner plugin**. This can be set explicitly via `pluginId` on the `ApiRef`, or inferred from the `ApiRef` ID string:
+ - Explicit `pluginId` on the ref (recommended) → that plugin owns it
+ - `core.*` ID → owned by the `app` plugin
+ - `plugin..*` ID → owned by that plugin (e.g. `plugin.catalog.starred-entities` is owned by `catalog`)
+ - Other ID prefixes → the prefix itself is the owner
+- **Only modules for the owning plugin can provide or override an API.** If plugin `A` tries to provide an API owned by plugin `B`, the system reports an `API_FACTORY_CONFLICT` error and rejects the override.
+- **Modules for the same plugin override the plugin's own factory.** This is how apps replace default implementations.
+
+The recommended way to create API refs in the new system uses the builder pattern with an explicit `pluginId`:
+
+```typescript
+import { createApiRef } from '@backstage/frontend-plugin-api';
+
+// Recommended: explicit pluginId makes ownership unambiguous
+const myApiRef = createApiRef().with({
+ id: 'plugin.my-plugin.my-api',
+ pluginId: 'my-plugin',
+});
+
+// Legacy form: ownership inferred from the id string pattern
+const legacyRef = createApiRef({ id: 'plugin.my-plugin.my-api' });
+```
+
+The builder form (`createApiRef().with(...)`) is preferred because the `pluginId` is explicit rather than parsed from the ID string. The `id` must still be globally unique across the app — the `pluginId` is ownership metadata, not a namespace prefix.
+
+**Practical impact for app migration:**
+
+Most APIs that were in the old `createApp({ apis: [...] })` are either core APIs (owned by `app`) or plugin-specific APIs. You need to group them into the right modules:
+
+```typescript
+import { createFrontendModule, ApiBlueprint } from '@backstage/frontend-plugin-api';
+
+// Core/app-level APIs → module for 'app'
+const appApisModule = createFrontendModule({
+ pluginId: 'app',
+ extensions: [
+ ApiBlueprint.make({
+ name: 'scm-integrations',
+ params: defineParams =>
+ defineParams({
+ api: scmIntegrationsApiRef,
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
+ }),
+ }),
+ ],
+});
+
+// Overriding a plugin's API → module for THAT plugin
+const catalogApiOverride = createFrontendModule({
+ pluginId: 'catalog',
+ extensions: [
+ ApiBlueprint.make({
+ params: defineParams =>
+ defineParams({
+ api: catalogApiRef, // id: 'plugin.catalog'
+ deps: { ... },
+ factory: ({ ... }) => new CustomCatalogClient({ ... }),
+ }),
+ }),
+ ],
+});
+
+const app = createApp({
+ features: [appApisModule, catalogApiOverride],
+});
+```
+
+**Common mistake:** Putting all API overrides in a single `createFrontendModule({ pluginId: 'app' })`. This only works for APIs owned by `app` (i.e. `core.*` APIs like `core.config`, `core.discovery`, etc.). Plugin-specific APIs like `plugin.catalog.*` or `plugin.scaffolder.*` must be overridden using a module with the matching `pluginId`.
+
+The old `createApp({ apis: [...] })` pattern didn't have these restrictions — any API could be overridden from the app. In the new system, the ownership model is stricter to prevent accidental conflicts between plugins.
+
+#### Sign-in Page → `SignInPageBlueprint`
+
+```tsx
+import { SignInPageBlueprint } from '@backstage/plugin-app-react';
+
+const signInPage = SignInPageBlueprint.make({
+ params: {
+ loader: async () => props =>
+ (
+
+ ),
+ },
+});
+```
+
+#### Themes → `ThemeBlueprint`
+
+```tsx
+import { ThemeBlueprint } from '@backstage/plugin-app-react';
+
+const customLightTheme = ThemeBlueprint.make({
+ name: 'custom-light',
+ params: {
+ theme: {
+ id: 'custom-light',
+ title: 'Light Theme',
+ variant: 'light',
+ icon: ,
+ Provider: ({ children }) => (
+
+ ),
+ },
+ },
+});
+```
+
+#### Icons → `IconBundleBlueprint`
+
+Icon bundles attach to the `app` plugin's icons input, so they must be installed via a module for `app`:
+
+```typescript
+import { IconBundleBlueprint } from '@backstage/plugin-app-react';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+
+const exampleIconBundle = IconBundleBlueprint.make({
+ name: 'example-bundle',
+ params: {
+ icons: { user: MyOwnUserIcon },
+ },
+});
+
+const app = createApp({
+ features: [
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [exampleIconBundle],
+ }),
+ ],
+});
+```
+
+#### Translations → `TranslationBlueprint`
+
+Translations attach to the `app` plugin's translations input. Note that `createTranslationMessages` takes a `messages` object with key-value pairs:
+
+```typescript
+import { TranslationBlueprint } from '@backstage/plugin-app-react';
+import { createTranslationMessages } from '@backstage/frontend-plugin-api';
+import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';
+
+const catalogTranslations = TranslationBlueprint.make({
+ name: 'catalog-overrides',
+ params: {
+ resource: createTranslationMessages({
+ ref: catalogTranslationRef,
+ messages: {
+ 'indexPage.title': 'Service directory',
+ 'indexPage.createButtonTitle': 'Register new service',
+ },
+ }),
+ },
+});
+
+const app = createApp({
+ features: [
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [catalogTranslations],
+ }),
+ ],
+});
+```
+
+For adding full language translations, use `createTranslationResource` instead:
+
+```typescript
+import { createTranslationResource } from '@backstage/frontend-plugin-api';
+
+const userSettingsTranslations = TranslationBlueprint.make({
+ name: 'user-settings-zh',
+ params: {
+ resource: createTranslationResource({
+ ref: userSettingsTranslationRef,
+ translations: {
+ zh: () => import('./userSettings-zh'),
+ },
+ }),
+ },
+});
+```
+
+### Migrating Root Elements
+
+Built-in elements like `AlertDisplay`, `OAuthRequestDialog`, and `VisitListener` are provided by the framework automatically. Remove them from `convertLegacyAppRoot`:
+
+```tsx
+// Before
+const convertedRootFeatures = convertLegacyAppRoot(
+ <>
+
+
+
+
+ {routes}
+
+ >,
+);
+
+// After
+const convertedRootFeatures = convertLegacyAppRoot(routes);
+```
+
+Custom root elements use `AppRootElementBlueprint`, and custom wrappers use `AppRootWrapperBlueprint` from `@backstage/plugin-app-react`.
+
+### Migrating the Sidebar
+
+Create a `NavContentBlueprint` extension to replace the legacy `Root` component:
+
+```tsx
+import { NavContentBlueprint } from '@backstage/plugin-app-react';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+
+const SidebarContent = NavContentBlueprint.make({
+ params: {
+ component: ({ navItems }) => {
+ const nav = navItems.withComponent(item => (
+ item.icon} to={item.href} text={item.title} />
+ ));
+
+ return (
+
+
+ } to="/search">
+
+
+
+ }>
+ {nav.take('page:catalog')}
+ {nav.take('page:scaffolder')}
+
+
+ {nav.rest({ sortBy: 'title' })}
+
+
+
+ );
+ },
+ },
+});
+
+export const navModule = createFrontendModule({
+ pluginId: 'app',
+ extensions: [SidebarContent],
+});
+```
+
+Nav items are auto-discovered from page extensions. Use `nav.take('page:')` to place specific items, and `nav.rest()` for the remainder. Items that are `take`n are excluded from `rest()`.
+
+### Migrating Routes
+
+Remove routes from `FlatRoutes` one at a time. With feature discovery enabled (the recommended default), this is the only step needed — the new plugin version is already discovered and waiting; it was simply overridden by the legacy route which had higher priority:
+
+```tsx
+// BEFORE: plugin page as a legacy route
+const routes = (
+
+ } />
+ } />
+
+);
+
+// AFTER: just remove the route — discovery handles the rest
+const routes = (
+
+ } />
+
+);
+```
+
+If you are **not** using feature discovery, you need to manually import and install the new plugin version:
+
+```typescript
+import scaffolderPlugin from '@backstage/plugin-scaffolder/alpha';
+
+const app = createApp({
+ features: [scaffolderPlugin, ...convertedRootFeatures],
+});
+```
+
+#### All-at-once rule for plugin routes
+
+Only one version of a plugin can be active in the app at a time. When legacy routes remain in `FlatRoutes`, `convertLegacyAppRoot` creates a plugin from them using the same plugin ID as the real plugin. This shadow plugin overrides the new-system version entirely. Because of this:
+
+- **All routes from a single plugin must be removed at the same time.** You cannot migrate one route of a multi-route plugin while keeping others in `FlatRoutes`. For example, if a plugin provides both `/foo` and `/foo/settings`, you must remove both routes together.
+- **Entity page content counts as part of the plugin.** Many plugins contribute both a top-level route (in `FlatRoutes`) _and_ entity page cards/content (in the entity pages). These are all part of the same plugin. If you remove the route from `FlatRoutes` but keep the entity page card as JSX in your entity pages, the old entity card JSX is now orphaned — and the new plugin may auto-provide its own version of that card, leading to duplicates or missing content.
+
+The practical consequence: when you migrate a plugin, remove _all_ of its legacy touchpoints — routes _and_ entity page extensions — at the same time.
+
+### Migrating Entity Pages
+
+Entity pages are typically the most complex part of the migration because they pull in content from many different plugins. The `entityPage` option in `convertLegacyAppRoot` provides a way to migrate them gradually.
+
+#### Setting up gradual entity page migration
+
+Pass your entity pages to `convertLegacyAppRoot`:
+
+```typescript
+const convertedRootFeatures = convertLegacyAppRoot(routes, { entityPage });
+```
+
+This converts your legacy entity page JSX tree into extensions. The structural pieces (`EntityLayout`, `EntitySwitch`) are preserved, while entity cards and content are converted into extensions that live alongside any auto-discovered new-system cards.
+
+#### Migrating the catalog plugin itself
+
+The catalog plugin is special because it owns both the `/catalog` route and the entity page route (`/catalog/:namespace/:kind/:name`). You must migrate both together:
+
+1. Remove the catalog routes from `FlatRoutes`:
+
+```tsx
+const routes = (
+
+ {/* Remove both catalog routes */}
+ {/* } /> */}
+ {/* }> */}
+ {/* {entityPage} */}
+ {/* */}
+ } />
+
+);
+```
+
+2. Install the catalog plugin explicitly (before the converted features so it takes priority):
+
+```typescript
+import catalogPlugin from '@backstage/plugin-catalog/alpha';
+
+const app = createApp({
+ features: [catalogPlugin, convertedOptionsModule, ...convertedRootFeatures],
+});
+```
+
+3. Pass `entityPage` to `convertLegacyAppRoot` (if not already done) so your existing entity page layout is preserved.
+
+#### Migrating individual plugins out of entity pages
+
+Once the catalog plugin itself is migrated, you can gradually remove legacy entity content from the entity pages. For each plugin that provides entity cards or content:
+
+1. **Remove the legacy JSX** from your entity page components (e.g. remove ` `, ` `, ` `)
+2. The new-system plugin auto-provides these as `EntityCardBlueprint` / `EntityContentBlueprint` extensions that are discovered automatically
+
+If you see **duplicate cards** after removing routes but before removing entity page JSX, that's expected — the new plugin is auto-providing cards while the legacy JSX still renders them. Remove the legacy JSX to resolve the duplication.
+
+#### Migrating entity page tabs
+
+Tabs in entity pages (the `EntityLayout.Route` entries) are provided by `EntityContentBlueprint` extensions in the new system. As you remove legacy entity content JSX, the tabs are automatically sourced from the new-system extensions. The order and grouping of tabs can be configured via `app-config.yaml`:
+
+```yaml
+app:
+ extensions:
+ - page:catalog/entity:
+ config:
+ groups:
+ - overview:
+ title: Overview
+ - documentation:
+ title: Docs
+```
+
+#### When is it done?
+
+Once all plugins contributing to entity pages have been migrated, the `entityPage` option can be removed from `convertLegacyAppRoot`, and the entity page component files in `packages/app/src/components/catalog/` can be deleted.
+
+### Migrating Route Bindings
+
+In the new system, plugins should define `defaultTarget` on their external route refs (e.g. `createExternalRouteRef({ defaultTarget: 'scaffolder.root' })`). When plugins set sensible defaults, most `bindRoutes` calls in the app become unnecessary — the routes resolve automatically when the target plugin is installed.
+
+Review your existing `bindRoutes` configuration and remove any bindings that are already covered by default targets in the plugins. For the remaining cases that need custom bindings, you can still use `bindRoutes` or configure them via static config:
+
+```yaml
+# app-config.yaml
+app:
+ routes:
+ bindings:
+ catalog.createComponent: scaffolder.root
+```
+
+## Dependencies
+
+| Purpose | Old Package | New Package |
+| --------------------- | ---------------------------- | -------------------------------- |
+| App creation | `@backstage/app-defaults` | `@backstage/frontend-defaults` |
+| Plugin/extension APIs | `@backstage/core-plugin-api` | `@backstage/frontend-plugin-api` |
+| App components | `@backstage/core-components` | `@backstage/ui` + CSS Modules |
+| Compatibility bridge | — | `@backstage/core-compat-api` |
+| App blueprints | — | `@backstage/plugin-app-react` |
+
+## Migration Checklist
+
+### Phase 1 (Hybrid)
+
+1. [ ] Add `@backstage/frontend-defaults` and `@backstage/core-compat-api` dependencies
+2. [ ] Switch `createApp` import to `@backstage/frontend-defaults`
+3. [ ] Enable feature discovery: add `app.packages: all` to `app-config.yaml`
+4. [ ] Wrap legacy options with `convertLegacyAppOptions`
+5. [ ] Wrap app element tree with `convertLegacyAppRoot`
+6. [ ] Change `app.createRoot()` to take no arguments
+7. [ ] Update `index.tsx` to render element instead of component
+8. [ ] Update `App.test.tsx`
+9. [ ] Verify app starts and works in hybrid mode
+
+### Phase 2 (Full Migration)
+
+1. [ ] Convert APIs to `ApiBlueprint` extensions
+2. [ ] Convert sign-in page to `SignInPageBlueprint`
+3. [ ] Convert themes to `ThemeBlueprint`
+4. [ ] Convert icons to `IconBundleBlueprint`
+5. [ ] Convert translations to `TranslationBlueprint`
+6. [ ] Migrate sidebar to `NavContentBlueprint`
+7. [ ] Remove built-in root elements (`AlertDisplay`, `OAuthRequestDialog`, etc.)
+8. [ ] Migrate routes from `FlatRoutes` to plugin features (one plugin at a time, removing all routes + entity content for each plugin together)
+9. [ ] Set up entity page migration with `convertLegacyAppRoot(routes, { entityPage })`
+10. [ ] Migrate catalog plugin: remove catalog routes from `FlatRoutes`, install `catalogPlugin` as a feature
+11. [ ] Gradually remove legacy entity card/content JSX as each contributing plugin is migrated
+12. [ ] Remove `entityPage` option and legacy entity page component files
+13. [ ] Remove `convertLegacyAppRoot` and `convertLegacyAppOptions` calls
+14. [ ] Remove `@backstage/app-defaults`, `@backstage/core-app-api` dependencies
+15. [ ] Run `yarn tsc` and `yarn lint` to verify
+
+## Troubleshooting
+
+- Install `@backstage/plugin-app-visualizer` to inspect the extension tree at `/visualizer`
+- Duplicate entity cards: remove legacy card JSX from entity pages — plugins auto-provide them
+- `Invalid element inside FlatRoutes`: push `FeatureFlagged`/`RequirePermissions` wrappers into plugin code instead of the route table
+
+## Reference
+
+- [App migration guide](https://backstage.io/docs/frontend-system/building-apps/migrating)
+- [Architecture overview](https://backstage.io/docs/frontend-system/architecture/index)
+- [Extension blueprints](https://backstage.io/docs/frontend-system/building-plugins/common-extension-blueprints)
+- [Installing plugins](https://backstage.io/docs/frontend-system/building-apps/installing-plugins)
diff --git a/docs/.well-known/skills/index.json b/docs/.well-known/skills/index.json
new file mode 100644
index 0000000000..e66c7ea791
--- /dev/null
+++ b/docs/.well-known/skills/index.json
@@ -0,0 +1,24 @@
+{
+ "skills": [
+ {
+ "name": "mui-to-bui-migration",
+ "description": "Migrate Backstage plugins from Material-UI (MUI) to Backstage UI (BUI). Use this skill when migrating components, updating imports, replacing styling patterns, or converting MUI components to their BUI equivalents.",
+ "files": ["SKILL.md"]
+ },
+ {
+ "name": "app-frontend-system-migration",
+ "description": "Migrate a Backstage app from the old frontend system to the new one. Use this skill when converting an app to use the new extension-based frontend system, including the hybrid migration phase and the full migration of routes, sidebar, plugins, APIs, themes, and other app-level concerns.",
+ "files": ["SKILL.md"]
+ },
+ {
+ "name": "plugin-new-frontend-system-support",
+ "description": "Add new frontend system support to an existing Backstage plugin while keeping the old system working. Use this skill for published or shared plugins that need to work in both old and new frontend system apps.",
+ "files": ["SKILL.md"]
+ },
+ {
+ "name": "plugin-full-frontend-system-migration",
+ "description": "Fully migrate a Backstage plugin to the new frontend system, dropping all old system support. Use this skill for internal plugins that only need to run in a single app, or when you are ready to remove backward compatibility entirely.",
+ "files": ["SKILL.md"]
+ }
+ ]
+}
diff --git a/docs/.well-known/skills/mui-to-bui-migration/SKILL.md b/docs/.well-known/skills/mui-to-bui-migration/SKILL.md
new file mode 100644
index 0000000000..94e2bcfdcd
--- /dev/null
+++ b/docs/.well-known/skills/mui-to-bui-migration/SKILL.md
@@ -0,0 +1,778 @@
+---
+name: mui-to-bui-migration
+description: Migrate Backstage plugins from Material-UI (MUI) to Backstage UI (BUI). Use this skill when migrating components, updating imports, replacing styling patterns, or converting MUI components to their BUI equivalents.
+---
+
+# MUI to BUI Migration Skill
+
+This skill helps migrate Backstage plugins from Material-UI (@material-ui/core, @material-ui/icons) to Backstage UI (
+@backstage/ui).
+
+## Prerequisites
+
+Before starting migration:
+
+1. Install the BUI package:
+
+ ```bash
+ yarn add @backstage/ui
+ ```
+
+2. Add the CSS import to your root file (typically `src/index.ts` or app entry point):
+ ```typescript
+ import '@backstage/ui/css/styles.css';
+ ```
+
+## Available BUI Components
+
+### Layout Components
+
+- `Box` - Basic layout container with CSS properties
+- `Container` - Centered content container with max-width
+- `Flex` - Flex layout component
+- `FullPage` - Full-page layout wrapper
+- `Grid` - CSS Grid-based layout (`Grid.Root`, `Grid.Item`)
+
+### UI Components
+
+- `Accordion` - Collapsible content panels (`Accordion`, `AccordionTrigger`, `AccordionPanel`, `AccordionGroup`)
+- `Alert` - Alert/notification banners (`status`, `title`, `description`)
+- `Avatar` - User/entity avatars
+- `Button` - Action buttons (`variant="primary"`, `variant="secondary"`, `variant="tertiary"`, `isDisabled`, `destructive`, `loading`)
+- `ButtonIcon` - Icon-only buttons (`icon`, `onPress`, `variant`)
+- `ButtonLink` - Link styled as button
+- `Card` - Content cards (`Card`, `CardHeader`, `CardBody`, `CardFooter`)
+- `Checkbox` - Checkbox input
+- `Dialog` - Modal dialogs (`DialogTrigger`, `Dialog`, `DialogHeader`, `DialogBody`, `DialogFooter`)
+- `FieldLabel` - Form field label with description and secondary label
+- `Header` - Page headers with breadcrumbs and tabs
+- `Link` - Navigation links
+- `List` - List component (`List`, `ListRow`)
+- `Menu` - Dropdown menus (`MenuTrigger`, `Menu`, `MenuItem`, `MenuSection`, `MenuSeparator`, `SubmenuTrigger`)
+- `PasswordField` - Password input field
+- `PluginHeader` - Plugin-level header with icon, title, tabs, and actions
+- `Popover` - Popover overlays
+- `RadioGroup` - Radio button groups (`RadioGroup`, `Radio`)
+- `SearchAutocomplete` - Search input with autocomplete popover (`SearchAutocomplete`, `SearchAutocompleteItem`)
+- `SearchField` - Search input
+- `Select` - Dropdown select (single and multiple selection modes)
+- `Skeleton` - Loading skeleton
+- `Switch` - Toggle switch
+- `Table` - Data tables (with `useTable` hook for data management)
+- `TablePagination` - Standalone pagination component
+- `Tabs` - Tab navigation (`Tabs`, `TabList`, `Tab`, `TabPanel`)
+- `Tag` - Tag/chip component (replaces MUI Chip)
+- `TagGroup` - Tag/chip groups
+- `Text` - Typography component (`variant`, `color`, `weight`, `truncate`)
+- `TextField` - Text input (`isRequired`, `onChange` receives string directly)
+- `ToggleButton` - Toggle buttons
+- `ToggleButtonGroup` - Grouped toggle buttons
+- `Tooltip` - Tooltip overlays (`TooltipTrigger`, `Tooltip` — both from `@backstage/ui`)
+- `VisuallyHidden` - Accessibility helper
+
+### Hooks
+
+- `useBreakpoint` - Responsive breakpoint hook
+- `useTable` - Table data management hook (supports `complete`, `offset`, and `cursor` pagination modes)
+
+## Migration Patterns
+
+### 1. Import Changes
+
+**Remove MUI imports:**
+
+```typescript
+// REMOVE these imports
+import { Box, Typography, Tooltip, Paper } from '@material-ui/core';
+import { makeStyles, Theme } from '@material-ui/core/styles';
+import SomeIcon from '@material-ui/icons/SomeIcon';
+```
+
+**Add BUI imports:**
+
+```typescript
+// ADD these imports
+import { Box, Flex, Text, Tooltip, Card } from '@backstage/ui';
+import { RiSomeIcon } from '@remixicon/react';
+import styles from './MyComponent.module.css';
+```
+
+### 2. Styling: `makeStyles` to CSS Modules
+
+Create a `.module.css` file alongside your component using BUI CSS variables.
+
+**Before (MUI `makeStyles`):**
+
+```typescript
+// MyComponent.tsx
+import {makeStyles, Theme} from '@material-ui/core/styles';
+
+const useStyles = makeStyles((theme: Theme) => ({
+ container: {
+ padding: theme.spacing(2),
+ backgroundColor: theme.palette.background.paper,
+ borderRadius: theme.shape.borderRadius,
+ },
+ title: {
+ marginBottom: theme.spacing(1),
+ color: theme.palette.text.primary,
+ },
+ listItem: {
+ display: 'flex',
+ alignItems: 'center',
+ },
+ icon: {
+ minWidth: 56,
+ color: theme.palette.text.secondary,
+ },
+}));
+
+function MyComponent() {
+ const classes = useStyles();
+ return (
+
+
Title < /Typography>
+ < div
+ className = {classes.listItem} >
+
+
+
+ < span > Content < /span>
+ < /div>
+ < /div>
+)
+ ;
+}
+```
+
+**After (CSS Modules with BUI variables):**
+
+```css
+/* MyComponent.module.css */
+@layer components {
+ .container {
+ padding: var(--bui-space-4);
+ background-color: var(--bui-bg-neutral-1);
+ border-radius: var(--bui-radius-2);
+ }
+
+ .title {
+ margin-bottom: var(--bui-space-2);
+ color: var(--bui-fg-primary);
+ }
+
+ .listItem {
+ display: flex;
+ align-items: center;
+ padding: var(--bui-space-2) 0;
+ }
+
+ .icon {
+ min-width: 56px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--bui-fg-secondary);
+ }
+}
+```
+
+```typescript
+// MyComponent.tsx
+import {Box, Text} from '@backstage/ui';
+import {RiSomeIcon} from '@remixicon/react';
+import styles from './MyComponent.module.css';
+
+function MyComponent() {
+ return (
+
+ Title < /Text>
+ < div
+ className = {styles.listItem} >
+
+
+ < /div>
+ < span > Content < /span>
+ < /div>
+ < /Box>
+)
+ ;
+}
+```
+
+### 3. Layout: Box with display to `Flex`
+
+**Before (MUI Box with display prop):**
+
+```typescript
+
+
+ {children}
+ < /Box>
+ < /Box>
+```
+
+**After (BUI `Flex` component):**
+
+```typescript
+
+
+{
+ children
+}
+
+< /Flex>
+```
+
+Note: BUI `Flex` uses `justify="between"` not `justify="space-between"`.
+
+### 4. Grid Layout
+
+**Before (MUI Grid):**
+
+```typescript
+
+
+ {content}
+ < /Grid>
+ < /Grid>
+```
+
+**After (BUI Grid):**
+
+```typescript
+
+
+{
+ content
+}
+
+< /Grid.Root>
+```
+
+### 5. Typography to Text
+
+**Before (MUI Typography):**
+
+```typescript
+ Heading < /Typography>
+ < Typography
+variant = "h6" > Subheading < /Typography>
+ < Typography
+variant = "body1" > Body
+text < /Typography>
+< Typography
+variant = "body2"
+color = "textSecondary" > Secondary
+text < /Typography>
+```
+
+**After (BUI Text):**
+
+```typescript
+ Heading < /Text>
+ < Text
+variant = "title-small" > Subheading < /Text>
+ < Text
+variant = "body-medium" > Body
+text < /Text>
+< Text
+variant = "body-small"
+color = "secondary" > Secondary
+text < /Text>
+```
+
+Valid Text variants: `title-large`, `title-medium`, `title-small`, `title-x-small`, `body-large`, `body-medium`,
+`body-small`, `body-x-small`
+
+### 6. Tooltip Pattern
+
+**Before (MUI Tooltip):**
+
+```typescript
+import {Tooltip, Typography} from '@material-ui/core';
+
+ Tooltip
+content < /Typography>}>
+< span > Hover
+me < /span>
+< /Tooltip>;
+```
+
+**After (BUI TooltipTrigger pattern):**
+
+```typescript
+import { Tooltip, TooltipTrigger, Text } from '@backstage/ui';
+
+
+ Hover me
+ Tooltip content
+ ;
+```
+
+### 7. Dialog Pattern
+
+**Before (MUI Dialog):**
+
+```typescript
+import {Dialog, DialogTitle, DialogActions, Button} from '@material-ui/core';
+
+
+Title < /DialogTitle>
+< DialogActions >
+ Cancel < /Button>
+ < Button
+onClick = {onConfirm}
+color = "primary" >
+ Confirm
+ < /Button>
+ < /DialogActions>
+ < /Dialog>;
+```
+
+**After (BUI Dialog):**
+
+```typescript
+import {
+ Dialog,
+ DialogTrigger,
+ DialogHeader,
+ DialogFooter,
+ Button,
+} from '@backstage/ui';
+
+
+
+{
+ if (!open) onClose();
+}
+}
+>
+Title < /DialogHeader>
+< DialogFooter >
+
+ Confirm
+ < /Button>
+ < Button
+onClick = {onClose}
+variant = "secondary"
+slot = "close" >
+ Cancel
+ < /Button>
+ < /DialogFooter>
+ < /Dialog>
+ < /DialogTrigger>;
+```
+
+### 8. Button Changes
+
+**Before (MUI Button):**
+
+```typescript
+
+ Submit
+ < /Button>
+ < IconButton
+onClick = {handleDelete}
+disabled = {!
+canDelete
+}>
+
+
+```
+
+**After (BUI Button):**
+
+```typescript
+
+ Submit
+ < /Button>
+ < ButtonIcon
+aria - label = "delete"
+isDisabled = {!
+canDelete
+}
+onPress = {handleDelete}
+icon = { < RiDeleteBinLine
+size = {16}
+/>}
+variant = "secondary"
+ / >
+```
+
+### 9. TextField Changes
+
+**Before (MUI TextField):**
+
+```typescript
+ setValue(e.target.value)}
+ fullWidth
+/>
+```
+
+**After (BUI TextField):**
+
+```typescript
+ setValue(newValue)} // receives string directly!
+/>
+```
+
+Note: BUI TextField `onChange` receives the string value directly, not an event object.
+
+### 10. Tabs Pattern
+
+**Before (MUI Tabs):**
+
+```typescript
+import {Tab} from '@material-ui/core';
+import {TabContext, TabList, TabPanel} from '@material-ui/lab';
+
+
+
+
+
+
+ < TabPanel
+value = "tab1" > Content
+1 < /TabPanel>
+< TabPanel
+value = "tab2" > Content
+2 < /TabPanel>
+< /TabContext>;
+```
+
+**After (BUI Tabs):**
+
+```typescript
+import {Tabs, TabList, Tab, TabPanel} from '@backstage/ui';
+
+
+
+ Tab
+1 < /Tab>
+< Tab
+id = "tab2" > Tab
+2 < /Tab>
+< /TabList>
+< TabPanel
+id = "tab1" > Content
+1 < /TabPanel>
+< TabPanel
+id = "tab2" > Content
+2 < /TabPanel>
+< /Tabs>;
+```
+
+### 11. Menu Pattern
+
+**Before (MUI Menu):**
+
+```typescript
+import {IconButton, Popover, MenuList, MenuItem} from '@material-ui/core';
+import MoreVertIcon from '@material-ui/icons/MoreVert';
+
+
+ < Popover
+open = {open}
+anchorEl = {anchorEl}
+onClose = {handleClose} >
+
+ Action < /MenuItem>
+ < /MenuList>
+ < /Popover>
+```
+
+**After (BUI Menu):**
+
+```typescript
+import {ButtonIcon, Menu, MenuItem, MenuTrigger} from '@backstage/ui';
+import {RiMore2Line} from '@remixicon/react';
+
+
+
+}
+variant = "secondary" / >
+
+ Action < /MenuItem>
+ < /Menu>
+ < /MenuTrigger>;
+```
+
+### 12. List to BUI List
+
+**Before (MUI List):**
+
+```typescript
+import { List, ListItem, ListItemIcon, ListItemText } from '@material-ui/core';
+
+
+
+
+
+
+
+
+
;
+```
+
+**After (BUI List):**
+
+```typescript
+import { List, ListRow } from '@backstage/ui';
+import { RiSomeIcon } from '@remixicon/react';
+
+
+ } description="Description">
+ Title
+
+
;
+```
+
+Note: `ListRow` supports `icon`, `description`, `menuItems`, and `customActions` props.
+
+### 13. Chip to Tag
+
+**Before (MUI Chip):**
+
+```typescript
+import { Chip } from '@material-ui/core';
+
+ ;
+```
+
+**After (BUI Tag):**
+
+```typescript
+import {Tag} from '@backstage/ui';
+
+ Category < /Tag>;
+```
+
+### 14. Alert Pattern
+
+**Before (MUI Alert):**
+
+```typescript
+import { Alert, AlertTitle } from '@material-ui/lab';
+
+
+ Error
+ Something went wrong.
+ ;
+```
+
+**After (BUI Alert):**
+
+```typescript
+import { Alert } from '@backstage/ui';
+
+ ;
+```
+
+Status mapping: `severity="error"` → `status="danger"`, `severity="warning"` → `status="warning"`,
+`severity="info"` → `status="info"`, `severity="success"` → `status="success"`.
+
+Set `icon` to `true` for automatic status icons, or pass a custom `ReactElement`.
+Use `loading` for a loading spinner, and `customActions` for action buttons.
+
+### 15. Icons: MUI Icons to Remix Icons
+
+**Before (MUI Icons):**
+
+```typescript
+import CloseIcon from '@material-ui/icons/Close';
+import SearchIcon from '@material-ui/icons/Search';
+
+
+
+```
+
+**After (Remix Icons):**
+
+```typescript
+import {RiCloseLine, RiSearchLine} from '@remixicon/react';
+
+
+
+```
+
+Common icon mappings:
+
+| MUI Icon | Remix Icon |
+| ----------------- | -------------------- |
+| `Close` | `RiCloseLine` |
+| `Search` | `RiSearchLine` |
+| `Settings` | `RiSettingsLine` |
+| `Add` | `RiAddLine` |
+| `Delete` | `RiDeleteBinLine` |
+| `Edit` | `RiEditLine` |
+| `Check` | `RiCheckLine` |
+| `Error` | `RiErrorWarningLine` |
+| `Warning` | `RiAlertLine` |
+| `Info` | `RiInformationLine` |
+| `ExpandMore` | `RiArrowDownSLine` |
+| `ExpandLess` | `RiArrowUpSLine` |
+| `ChevronRight` | `RiArrowRightSLine` |
+| `ChevronLeft` | `RiArrowLeftSLine` |
+| `Menu` | `RiMenuLine` |
+| `MoreVert` | `RiMore2Line` |
+| `Visibility` | `RiEyeLine` |
+| `VisibilityOff` | `RiEyeOffLine` |
+| `NewReleases` | `RiMegaphoneLine` |
+| `RecordVoiceOver` | `RiMegaphoneLine` |
+| `Description` | `RiFileTextLine` |
+
+Find more icons at: https://remixicon.com/
+
+## CSS Variable Reference
+
+### Spacing
+
+| MUI theme.spacing() | BUI CSS Variable |
+| -------------------- | -------------------- |
+| `theme.spacing(0.5)` | `var(--bui-space-1)` |
+| `theme.spacing(1)` | `var(--bui-space-2)` |
+| `theme.spacing(1.5)` | `var(--bui-space-3)` |
+| `theme.spacing(2)` | `var(--bui-space-4)` |
+| `theme.spacing(3)` | `var(--bui-space-6)` |
+| `theme.spacing(4)` | `var(--bui-space-8)` |
+
+### Colors
+
+| MUI theme.palette | BUI CSS Variable |
+| -------------------- | ------------------------------------------ |
+| `text.primary` | `var(--bui-fg-primary)` |
+| `text.secondary` | `var(--bui-fg-secondary)` |
+| `background.paper` | `var(--bui-bg-neutral-1)` |
+| `background.default` | `var(--bui-bg-app)` |
+| `primary.main` | `var(--bui-bg-solid)` or `var(--bui-ring)` |
+| `error.main` | `var(--bui-fg-danger)` |
+| `action.hover` | `var(--bui-bg-neutral-1-hover)` |
+| `divider` | `var(--bui-border-1)` |
+
+### Typography
+
+| Property | BUI CSS Variable |
+| ------------------- | -------------------------------- |
+| Font family | `var(--bui-font-regular)` |
+| Font size small | `var(--bui-font-size-1)` |
+| Font size medium | `var(--bui-font-size-2)` |
+| Font size large | `var(--bui-font-size-3)` |
+| Font weight regular | `var(--bui-font-weight-regular)` |
+| Font weight bold | `var(--bui-font-weight-bold)` |
+
+### Other
+
+| Property | BUI CSS Variable |
+| -------------------- | ------------------------ |
+| Border radius small | `var(--bui-radius-2)` |
+| Border radius medium | `var(--bui-radius-3)` |
+| Border radius full | `var(--bui-radius-full)` |
+| Link color | `var(--bui-fg-info)` |
+
+## Known Limitations
+
+Some Backstage APIs still require MUI-compatible icon types:
+
+- **NavItemBlueprint** (`@backstage/frontend-plugin-api`): The `icon` prop expects MUI `IconComponent` type. Remix icons
+ are not type-compatible.
+- **Timeline** (`@material-ui/lab`): No BUI equivalent exists.
+
+For these cases, keep using MUI components.
+
+## Migration Checklist
+
+When migrating a plugin:
+
+1. [ ] Add `@backstage/ui` dependency
+2. [ ] Add `@remixicon/react` dependency (if using icons)
+3. [ ] Add CSS import to root file
+4. [ ] Remove `@material-ui/core` imports (except components with no BUI equivalent)
+5. [ ] Remove `@material-ui/icons` imports
+6. [ ] Remove `@material-ui/lab` imports (Alert, Pagination now in BUI)
+7. [ ] Remove `makeStyles` and related imports
+8. [ ] Create `.module.css` files for component styles
+9. [ ] Replace `Typography` with `Text`
+10. [ ] Replace `Box display="flex"` with `Flex`
+11. [ ] Replace `Grid container/item` with `Grid.Root/Grid.Item`
+12. [ ] Replace `Paper` with `Card`
+13. [ ] Replace MUI `Dialog` with BUI `DialogTrigger` pattern
+14. [ ] Replace MUI `Tooltip` with BUI `TooltipTrigger` pattern (both from `@backstage/ui`)
+15. [ ] Replace MUI `Tabs` with BUI `Tabs`
+16. [ ] Replace MUI `Menu` with BUI `MenuTrigger` pattern
+17. [ ] Replace `Chip` with `Tag`
+18. [ ] Replace `IconButton` with `ButtonIcon`
+19. [ ] Replace MUI `Alert` with BUI `Alert`
+20. [ ] Replace MUI `List` with BUI `List` and `ListRow`
+21. [ ] Update `Button` props (`disabled` → `isDisabled`, `variant="contained"` → `variant="primary"`)
+22. [ ] Update `TextField` props (`required` → `isRequired`, `onChange` signature)
+23. [ ] Replace MUI icons with Remix icons
+24. [ ] Run `yarn tsc` to check for type errors
+25. [ ] Run `yarn build` to verify build
+26. [ ] Run `yarn lint` to check for missing dependencies
+27. [ ] Test component rendering and functionality
+
+## Reference
+
+- BUI Documentation: https://ui.backstage.io
+- Remix Icons: https://remixicon.com/
+- Example Migration PR: https://github.com/backstage/backstage/pull/31631
diff --git a/docs/.well-known/skills/plugin-full-frontend-system-migration/SKILL.md b/docs/.well-known/skills/plugin-full-frontend-system-migration/SKILL.md
new file mode 100644
index 0000000000..8f7ee3498b
--- /dev/null
+++ b/docs/.well-known/skills/plugin-full-frontend-system-migration/SKILL.md
@@ -0,0 +1,547 @@
+---
+name: plugin-full-frontend-system-migration
+description: Fully migrate a Backstage plugin to the new frontend system, dropping all old system support. Use this skill for internal plugins that only need to run in a single app, or when you are ready to remove backward compatibility entirely.
+---
+
+# Full Plugin Migration to the New Frontend System
+
+This skill helps fully migrate an existing Backstage plugin from the old frontend system to the new one. Unlike adding dual support (which keeps the old system working), this is a complete migration that removes all `@backstage/core-plugin-api` usage and makes the plugin work exclusively with the new frontend system.
+
+This is the preferred approach for internal plugins that are only used in a single app, since there is no need to maintain backward compatibility. It can also be used for published plugins when you're ready to drop old system support entirely.
+
+It is highly recommended to be on Backstage version 1.49.x or above before starting this, although not mandatory, you may face issues with some of the instructions below. This can be verified by looking in the `backstage.json` file in the root of the repository.
+
+## Key Differences from Dual Support
+
+| Aspect | Dual Support | Full Migration |
+| ---------------- | ------------------------------------------------- | ----------------------------------------------------------- |
+| Entry point | Old `src/plugin.ts` + new `src/alpha.tsx` | Single `src/plugin.tsx` |
+| Plugin creation | Both `createPlugin` and `createFrontendPlugin` | Only `createFrontendPlugin` |
+| Core dependency | Keeps `@backstage/core-plugin-api` | Removes it, uses only `@backstage/frontend-plugin-api` |
+| Route refs | Reuses `@backstage/core-plugin-api` refs directly | Uses `createRouteRef` from `@backstage/frontend-plugin-api` |
+| Page shell | Old pages keep `Page`/`Header`, NFS pages skip it | All pages rely on framework's `PageLayout`/`PluginHeader` |
+| Internal routing | May keep legacy `` trees in components | Replaced with `SubPageBlueprint` tabbed pages |
+| Compatibility | Not needed | Not needed |
+
+## Step 1: Migrate Route Refs
+
+Replace `createRouteRef` / `createSubRouteRef` / `createExternalRouteRef` imports:
+
+```typescript
+// OLD (src/routes.ts)
+import {
+ createRouteRef,
+ createSubRouteRef,
+ createExternalRouteRef,
+} from '@backstage/core-plugin-api';
+
+export const rootRouteRef = createRouteRef({ id: 'my-plugin' });
+export const detailsRouteRef = createSubRouteRef({
+ id: 'my-plugin-details',
+ parent: rootRouteRef,
+ path: '/details/:id',
+});
+export const externalDocsRouteRef = createExternalRouteRef({ id: 'docs' });
+
+// NEW (src/routes.ts)
+import {
+ createRouteRef,
+ createSubRouteRef,
+ createExternalRouteRef,
+} from '@backstage/frontend-plugin-api';
+
+export const rootRouteRef = createRouteRef();
+export const detailsRouteRef = createSubRouteRef({
+ path: '/details/:id',
+ parent: rootRouteRef,
+});
+export const externalDocsRouteRef = createExternalRouteRef({
+ defaultTarget: 'techdocs.docRoot',
+});
+```
+
+Key differences:
+
+- `createRouteRef()` no longer takes an `id` — the ID is derived from the extension
+- `createSubRouteRef` path must start with `/` and must not end with `/`
+- `createExternalRouteRef()` no longer takes an `id` or `optional` flag
+
+### Set Default Targets for External Route Refs
+
+When migrating external route refs, always set `defaultTarget` to the most common binding target. This removes the need for apps to explicitly bind routes via `bindRoutes` for standard plugin combinations:
+
+```typescript
+export const createComponentRouteRef = createExternalRouteRef({
+ defaultTarget: 'scaffolder.root',
+});
+
+export const viewTechDocRouteRef = createExternalRouteRef({
+ params: ['namespace', 'kind', 'name'],
+ defaultTarget: 'techdocs.docRoot',
+});
+
+export const catalogEntityRouteRef = createExternalRouteRef({
+ params: ['namespace', 'kind', 'name'],
+ defaultTarget: 'catalog.catalogEntity',
+});
+```
+
+The `defaultTarget` string uses the `.` format, where `routeName` matches a key in the target plugin's `routes` map. The default is only activated when the target plugin is installed — otherwise the route stays unbound and `useRouteRef` returns `undefined`.
+
+This is especially important for a full migration because in the old system, apps typically had explicit `bindRoutes` calls. With default targets, most of those bindings become unnecessary, improving the plug-and-play experience.
+
+## Step 2: Migrate the Plugin Definition
+
+Replace `src/plugin.ts` with a `createFrontendPlugin`-based definition:
+
+```tsx
+// NEW (src/plugin.tsx)
+import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
+import { RiToolsLine } from '@remixicon/react';
+import { rootRouteRef, externalDocsRouteRef } from './routes';
+import { myPage } from './extensions';
+import { myPluginApi } from './apis';
+
+export default createFrontendPlugin({
+ pluginId: 'my-plugin',
+ title: 'My Plugin',
+ icon: ,
+ info: {
+ packageJson: () => import('../package.json'),
+ },
+ routes: {
+ root: rootRouteRef,
+ },
+ externalRoutes: {
+ docs: externalDocsRouteRef,
+ },
+ extensions: [myPluginApi, myPage],
+});
+```
+
+For the plugin `icon`, prefer using [Remix Icons](https://remixicon.com/) from `@remixicon/react`. If the plugin already has an existing MUI icon, it can be kept with `fontSize="inherit"` (e.g. ` `), but for new icons Remix is the recommended choice.
+
+Since this is the only entry point now, export it as default from `src/index.ts` or update `package.json` exports accordingly. If the plugin was previously consumed via its main entry point, you can make the main entry point export the new plugin:
+
+```json
+{
+ "exports": {
+ ".": "./src/index.ts",
+ "./package.json": "./package.json"
+ }
+}
+```
+
+```typescript
+// src/index.ts
+export { default } from './plugin';
+export { rootRouteRef } from './routes';
+```
+
+## Step 3: Migrate API Factories to `ApiBlueprint`
+
+```typescript
+// OLD
+import {
+ createApiFactory,
+ discoveryApiRef,
+ fetchApiRef,
+} from '@backstage/core-plugin-api';
+
+export const myApiFactory = createApiFactory({
+ api: myPluginApiRef,
+ deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
+ factory: ({ discoveryApi, fetchApi }) =>
+ new MyPluginClient({ discoveryApi, fetchApi }),
+});
+
+// NEW (src/apis.ts)
+import {
+ ApiBlueprint,
+ discoveryApiRef,
+ fetchApiRef,
+} from '@backstage/frontend-plugin-api';
+import { myPluginApiRef } from './api';
+
+export const myPluginApi = ApiBlueprint.make({
+ params: defineParams =>
+ defineParams({
+ api: myPluginApiRef,
+ deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
+ factory: ({ discoveryApi, fetchApi }) =>
+ new MyPluginClient({ discoveryApi, fetchApi }),
+ }),
+});
+```
+
+Also update the API ref creation to the new builder pattern with explicit `pluginId`:
+
+```typescript
+// OLD
+import { createApiRef } from '@backstage/core-plugin-api';
+
+export const myPluginApiRef = createApiRef({
+ id: 'plugin.my-plugin.client',
+});
+
+// NEW (recommended builder pattern with explicit pluginId)
+import { createApiRef } from '@backstage/frontend-plugin-api';
+
+export const myPluginApiRef = createApiRef().with({
+ id: 'plugin.my-plugin.client',
+ pluginId: 'my-plugin',
+});
+```
+
+The builder form (`createApiRef().with(...)`) is preferred because ownership is explicit via `pluginId` rather than parsed from the ID string. The `id` must still be globally unique across the app — the `pluginId` is ownership metadata, not a namespace prefix.
+
+### API Ownership and Override Rules
+
+The new system enforces **API ownership** — only the owning plugin (or a module targeting it) can provide or override a given API. Ownership is determined by:
+
+1. The explicit `pluginId` on the `ApiRef` (if set via the builder pattern)
+2. Falling back to inference from the `ApiRef` ID string:
+ - `plugin..*` → owned by that plugin
+ - `core.*` → owned by the `app` plugin
+
+If app adopters want to replace your plugin's default API implementation, they must use a `createFrontendModule` with `pluginId` matching your plugin — they cannot override it from a different plugin or from a generic `app` module. This is a stricter model than the old system where any API could be overridden from the app's `apis` array.
+
+## Step 4: Migrate Pages to `PageBlueprint`
+
+### Simple Page (No Sub-Routes)
+
+```tsx
+// src/extensions.tsx
+import { PageBlueprint } from '@backstage/frontend-plugin-api';
+import { rootRouteRef } from './routes';
+
+export const myPage = PageBlueprint.make({
+ params: {
+ path: '/my-plugin',
+ routeRef: rootRouteRef,
+ loader: () => import('./components/MyPage').then(m => ),
+ },
+});
+```
+
+The `MyPage` component should **not** include `Page`, `Header`, or `PageWithHeader` from `@backstage/core-components`. The framework's `PageLayout` renders `PluginHeader` automatically.
+
+The `title` and `icon` params on `PageBlueprint` are only needed if they should differ from the plugin's own `title` and `icon` (set in `createFrontendPlugin`). If omitted, the plugin-level values are used.
+
+### Page with `Header` for Custom Actions
+
+If your page needs a subtitle or action buttons below the framework header, use `Header` from `@backstage/ui`:
+
+```tsx
+// src/components/MyPage/MyPage.tsx
+import { Header } from '@backstage/ui';
+import { Content } from '@backstage/core-components';
+
+export function MyPage() {
+ return (
+ <>
+
+
+ Help text
+ >
+ }
+ />
+
+
+
+ >
+ );
+}
+```
+
+### Page Without Header
+
+For pages that manage their own layout entirely (e.g. home page, dashboards), set `noHeader: true`:
+
+```tsx
+export const myPage = PageBlueprint.make({
+ params: {
+ path: '/my-plugin',
+ routeRef: rootRouteRef,
+ noHeader: true,
+ loader: () => import('./components/MyPage').then(m => ),
+ },
+});
+```
+
+## Step 5: Replace Internal Routing with Sub-Pages
+
+This is one of the biggest changes in a full migration. Old plugins often use React Router `` trees inside a router component to handle internal navigation. The new system replaces this with `SubPageBlueprint` for tabbed sub-pages.
+
+### Old Pattern: Internal Router
+
+```tsx
+// OLD — plugin owns its own routing
+import { Route, Routes } from 'react-router-dom';
+
+export function MyPluginRouter() {
+ return (
+
+
+
+
+
+ } />
+ } />
+
+
+
+ );
+}
+```
+
+### New Pattern: `PageBlueprint` + `SubPageBlueprint`
+
+```tsx
+// src/extensions.tsx
+import {
+ PageBlueprint,
+ SubPageBlueprint,
+} from '@backstage/frontend-plugin-api';
+
+// Parent page WITHOUT a loader — uses built-in tabbed rendering
+export const myPluginPage = PageBlueprint.make({
+ params: {
+ path: '/my-plugin',
+ routeRef: rootRouteRef,
+ },
+});
+
+export const overviewSubPage = SubPageBlueprint.make({
+ name: 'overview',
+ params: {
+ path: 'overview',
+ title: 'Overview',
+ loader: () =>
+ import('./components/OverviewPage').then(m => ),
+ },
+});
+
+export const settingsSubPage = SubPageBlueprint.make({
+ name: 'settings',
+ params: {
+ path: 'settings',
+ title: 'Settings',
+ loader: () =>
+ import('./components/SettingsPage').then(m => ),
+ },
+});
+```
+
+How this works:
+
+- `PageBlueprint` **without a `loader`** automatically renders its sub-pages as tabs
+- The first sub-page becomes the default (index redirect)
+- Each `SubPageBlueprint` gets a tab in the header with its `title`
+- Sub-page `path` values are **relative** (no leading `/`)
+- Sub-page components render **content only** — no `Page`, `Header`, or `HeaderTabs`
+
+If the sub-page content needs padding, use `Container` from `@backstage/ui` as a wrapper inside the component.
+
+### When NOT to Use Sub-Pages
+
+Not all internal routing maps to tabs. Use `SubPageBlueprint` when:
+
+- The sub-routes represent top-level tabs/sections of the plugin
+- Users navigate between them via the header
+
+Keep internal routing within a `PageBlueprint` `loader` when:
+
+- Routes are detail/drill-down pages (e.g. `/my-plugin/items/:id`)
+- The routing is deeply nested or dynamic
+
+In those cases, use a `PageBlueprint` **with** a `loader` that handles its own `Routes`:
+
+```tsx
+export const myPage = PageBlueprint.make({
+ params: {
+ path: '/my-plugin',
+ routeRef: rootRouteRef,
+ loader: () => import('./components/Router').then(m => ),
+ },
+});
+```
+
+## Step 6: Update Hooks and Imports
+
+Replace all `@backstage/core-plugin-api` imports with `@backstage/frontend-plugin-api`:
+
+```typescript
+// OLD
+import { useApi, useRouteRef, configApiRef } from '@backstage/core-plugin-api';
+
+// NEW
+import {
+ useApi,
+ useRouteRef,
+ configApiRef,
+} from '@backstage/frontend-plugin-api';
+```
+
+### `useRouteRef` Behavior Change
+
+In the new system, `useRouteRef` may return `undefined` for external route refs that aren't bound. Handle this:
+
+```typescript
+// OLD — throws if not bound
+const docsLink = useRouteRef(externalDocsRouteRef);
+// Always a function
+
+// NEW — returns undefined if not bound
+const docsLink = useRouteRef(externalDocsRouteRef);
+if (docsLink) {
+ // render link
+}
+```
+
+### Common Import Mappings
+
+| Old Import (`@backstage/core-plugin-api`) | New Import (`@backstage/frontend-plugin-api`) |
+| ----------------------------------------- | --------------------------------------------------- |
+| `createPlugin` | `createFrontendPlugin` |
+| `createRouteRef` | `createRouteRef` |
+| `createSubRouteRef` | `createSubRouteRef` |
+| `createExternalRouteRef` | `createExternalRouteRef` |
+| `createApiRef` | `createApiRef` |
+| `createApiFactory` | `ApiBlueprint.make` |
+| `useApi` | `useApi` |
+| `useRouteRef` | `useRouteRef` |
+| `configApiRef` | `configApiRef` |
+| `discoveryApiRef` | `discoveryApiRef` |
+| `fetchApiRef` | `fetchApiRef` |
+| `identityApiRef` | `identityApiRef` |
+| `storageApiRef` | `storageApiRef` |
+| `analyticsApiRef` | `analyticsApiRef` |
+| `createRoutableExtension` | `PageBlueprint.make` |
+| `createComponentExtension` | Depends on context — blueprint or `createExtension` |
+
+## Step 7: Remove Old System Code
+
+1. Delete `src/plugin.ts` (old `createPlugin`)
+2. Delete any `createRoutableExtension` / `createComponentExtension` usage
+3. Remove `Page`, `Header`, `PageWithHeader` wrapping from page components
+4. Remove `HeaderTabs` if replaced by `SubPageBlueprint` tabs
+5. Remove internal ``/`` trees if replaced by sub-pages
+6. Remove `@backstage/core-plugin-api` from `package.json` `dependencies`
+7. Remove `@backstage/core-compat-api` from `package.json` `dependencies` if present
+
+## Step 8: Update Page Components for BUI
+
+With the full migration, page components should use `@backstage/ui` components and patterns. See the `mui-to-bui-migration` skill for detailed component migration guidance.
+
+Key page-level changes:
+
+- Replace `PageWithHeader` / `Page` + `Header` with framework-provided `PluginHeader` (automatic via `PageLayout`)
+- Use `Header` from `@backstage/ui` for optional subtitle/custom actions
+- Use `Content` from `@backstage/core-components` for page body padding (this is still used even in NFS pages)
+- Replace `ContentHeader` with `Header`'s `customActions` prop
+- Replace `HeaderTabs` with `SubPageBlueprint` (tabs are rendered by the framework)
+
+## Real Example: Auth Plugin (Fully Migrated)
+
+The `@backstage/plugin-auth` plugin is a fully migrated example with no `@backstage/core-plugin-api` dependency:
+
+```tsx
+// plugins/auth/src/routes.ts
+import { createRouteRef } from '@backstage/frontend-plugin-api';
+
+export const rootRouteRef = createRouteRef();
+
+// plugins/auth/src/plugin.tsx
+import {
+ createFrontendPlugin,
+ PageBlueprint,
+} from '@backstage/frontend-plugin-api';
+import { rootRouteRef } from './routes';
+
+export const AuthPage = PageBlueprint.make({
+ params: {
+ path: '/oauth2',
+ routeRef: rootRouteRef,
+ loader: () => import('./components/Router').then(m => ),
+ },
+});
+
+export default createFrontendPlugin({
+ pluginId: 'auth',
+ extensions: [AuthPage],
+ routes: {
+ root: rootRouteRef,
+ },
+});
+```
+
+## Real Example: Scaffolder Sub-Pages
+
+The scaffolder plugin demonstrates the sub-page pattern (though it still has dual support — the pattern itself is what a full migration targets):
+
+```tsx
+// PageBlueprint WITHOUT loader — framework renders tabs
+export const scaffolderPage = PageBlueprint.make({
+ params: {
+ path: '/create',
+ routeRef: rootRouteRef,
+ },
+});
+
+// Sub-pages with content only
+export const templatesSubPage = SubPageBlueprint.make({
+ name: 'templates',
+ params: {
+ path: 'templates',
+ title: 'Templates',
+ loader: () => import('./TemplatesPage').then(m => ),
+ },
+});
+
+export const tasksSubPage = SubPageBlueprint.make({
+ name: 'tasks',
+ params: {
+ path: 'tasks',
+ title: 'Tasks',
+ loader: () => import('./TasksPage').then(m => ),
+ },
+});
+```
+
+## Migration Checklist
+
+1. [ ] Migrate route refs to `@backstage/frontend-plugin-api` (`createRouteRef`, `createSubRouteRef`, `createExternalRouteRef`)
+2. [ ] Replace `createPlugin` with `createFrontendPlugin`
+3. [ ] Convert all API factories to `ApiBlueprint` extensions
+4. [ ] Convert pages to `PageBlueprint`
+5. [ ] Replace internal tab routing with `SubPageBlueprint` where appropriate
+6. [ ] Remove `Page`/`Header`/`PageWithHeader` from page components
+7. [ ] Add `Header` from `@backstage/ui` where subtitle/custom actions are needed
+8. [ ] Replace `HeaderTabs` with `SubPageBlueprint` tabs
+9. [ ] Update all `@backstage/core-plugin-api` imports to `@backstage/frontend-plugin-api`
+10. [ ] Handle `useRouteRef` possibly returning `undefined`
+11. [ ] Remove `src/plugin.ts` (old system entry point)
+12. [ ] Remove `src/alpha.tsx` if it existed (merge into main entry)
+13. [ ] Remove `@backstage/core-plugin-api` from `package.json` dependencies
+14. [ ] Remove `@backstage/core-compat-api` from `package.json` dependencies
+15. [ ] Update `package.json` exports (remove `./alpha` if merged into main)
+16. [ ] Run `yarn tsc` to check for type errors
+17. [ ] Run `yarn lint` to check for missing dependencies
+18. [ ] Run `yarn build:api-reports` to update API reports (if the project uses API reports)
+19. [ ] Test in a new-system app (`packages/app`)
+
+## Reference
+
+- [Plugin migration guide](https://backstage.io/docs/frontend-system/building-plugins/migrating)
+- [Extension blueprints](https://backstage.io/docs/frontend-system/building-plugins/common-extension-blueprints)
+- [Utility APIs](https://backstage.io/docs/frontend-system/utility-apis/creating)
+- MUI to BUI migration: `mui-to-bui-migration` skill
diff --git a/docs/.well-known/skills/plugin-new-frontend-system-support/SKILL.md b/docs/.well-known/skills/plugin-new-frontend-system-support/SKILL.md
new file mode 100644
index 0000000000..2912215327
--- /dev/null
+++ b/docs/.well-known/skills/plugin-new-frontend-system-support/SKILL.md
@@ -0,0 +1,427 @@
+---
+name: plugin-new-frontend-system-support
+description: Add new frontend system support to an existing Backstage plugin while keeping the old system working. Use this skill for published or shared plugins that need to work in both old and new frontend system apps.
+---
+
+# Adding New Frontend System Support to an Existing Plugin
+
+This skill helps add new frontend system (NFS) support to an existing Backstage plugin while keeping the old system fully functional. The result is a plugin that works in both old and new apps via a dual entry point pattern.
+
+This is the preferred approach for published plugins or plugins that are used by external parties, since it avoids forcing consumers to migrate their app before they are ready.
+
+It is highly recommended to be on Backstage version 1.49.x or above before starting this, although not mandatory, you may face issues with some of the instructions below. This can be verified by looking in the `backstage.json` file in the root of the repository.
+
+## Key Concepts
+
+- **Dual entry point:** The plugin keeps its existing `src/plugin.ts` (old system) and adds a new `src/alpha.tsx` (new system)
+- **Old system:** `createPlugin` from `@backstage/core-plugin-api`, pages via `createRoutableExtension`, routes defined in the app
+- **New system:** `createFrontendPlugin` from `@backstage/frontend-plugin-api`, pages via `PageBlueprint`, routes owned by the plugin
+- **Dual header pattern:** Old system uses `Page`/`Header`/`PageWithHeader` from `@backstage/core-components`; new system relies on the framework's `PageLayout` which renders `PluginHeader` from `@backstage/ui` — so NFS page components should NOT include their own page shell
+
+## Step 1: Create the Alpha Entry Point
+
+Create `src/alpha.tsx` (or `src/alpha/index.ts` for larger plugins) with a `createFrontendPlugin` default export:
+
+```tsx
+// src/alpha.tsx
+import {
+ createFrontendPlugin,
+ PageBlueprint,
+} from '@backstage/frontend-plugin-api';
+import { RiToolsLine } from '@remixicon/react';
+import { rootRouteRef } from './routes';
+
+const myPage = PageBlueprint.make({
+ params: {
+ path: '/my-plugin',
+ routeRef: rootRouteRef,
+ loader: () => import('./components/MyPage').then(m => ),
+ },
+});
+
+export default createFrontendPlugin({
+ pluginId: 'my-plugin',
+ title: 'My Plugin',
+ icon: ,
+ extensions: [myPage],
+ routes: {
+ root: rootRouteRef,
+ },
+ externalRoutes: {
+ // same external routes as the old plugin
+ },
+});
+```
+
+For the plugin `icon`, prefer using [Remix Icons](https://remixicon.com/) from `@remixicon/react`. If the plugin already has an existing MUI icon, it can be kept with `fontSize="inherit"` (e.g. ` `), but for new icons Remix is the recommended choice.
+
+The `title` and `icon` params on `PageBlueprint` are only needed if they should differ from the plugin's own `title` and `icon` (set in `createFrontendPlugin`). If omitted, the plugin-level values are used.
+
+For larger plugins, organize into `src/alpha/plugin.tsx`, `src/alpha/pages.tsx`, `src/alpha/extensions.tsx`, etc., and re-export from `src/alpha/index.ts`.
+
+## Step 2: Update `package.json` Exports
+
+Add the `./alpha` subpath export and its `typesVersions` entry:
+
+```json
+{
+ "exports": {
+ ".": "./src/index.ts",
+ "./alpha": "./src/alpha.tsx",
+ "./package.json": "./package.json"
+ },
+ "typesVersions": {
+ "*": {
+ "alpha": ["src/alpha.tsx"],
+ "package.json": ["package.json"]
+ }
+ }
+}
+```
+
+Apps import the new plugin as:
+
+```typescript
+import myPlugin from '@backstage/plugin-my-plugin/alpha';
+```
+
+## Step 3: Implement the Dual Header Pattern
+
+The critical difference between old and new system page components is the **page shell**. In the old system, each page renders its own `Page` + `Header` (or `PageWithHeader`) wrapper. In the new system, the framework's `PageLayout` provides the header via `PluginHeader` automatically — so the NFS page component must **not** include its own page shell.
+
+### Pattern A: Separate Components (Recommended for Simple Pages)
+
+Create two exported components — one for each system:
+
+```tsx
+// src/components/MyPage/MyPage.tsx
+import {
+ Content,
+ PageWithHeader,
+ ContentHeader,
+ SupportButton,
+} from '@backstage/core-components';
+import { Header } from '@backstage/ui';
+
+// Used by the OLD system — includes the full page shell
+export function MyPage() {
+ return (
+
+
+
+ Some help text
+
+
+
+
+ );
+}
+
+// Used by the NEW system — no page shell, just content
+// The framework's PageLayout/PluginHeader provides the title and header
+export function NfsMyPage() {
+ return (
+ <>
+ Some help text}
+ />
+
+
+
+ >
+ );
+}
+```
+
+Key differences in the NFS variant:
+
+- **No `Page`/`PageWithHeader`** — the framework provides the outer page shell
+- **`Header` from `@backstage/ui`** is optional — use it only if you need a subtitle or custom actions below the framework header
+- **No `ContentHeader`** — actions move to `Header`'s `customActions` prop
+- The shared ` ` component contains the actual page body
+
+### Forwarding Customization Props
+
+If the old system exports a page component with props for customization (e.g. ` `), the NFS variant should accept the same props. Export the NFS variant with the same component name from the `./alpha` entry point, so that app adopters can customize it the same way:
+
+```tsx
+// src/components/MyPage/MyPage.tsx
+export interface MyPageProps {
+ actions?: ReactNode;
+ filters?: ReactNode;
+}
+
+// Old system — exported from src/index.ts
+export function MyPage(props: MyPageProps) {
+ return (
+
+
+
+
+
+ );
+}
+
+// NFS variant — exported from src/alpha.tsx
+export function NfsMyPage(props: MyPageProps) {
+ return (
+
+
+
+ );
+}
+```
+
+The NFS variant is then wired into the `PageBlueprint` loader, and the component itself is re-exported from `./alpha` so adopters can use `.withOverrides()` to pass custom props:
+
+```typescript
+// src/alpha.tsx
+export { NfsMyPage as MyPage } from './components/MyPage';
+```
+
+This way, the old `MyPage` is available from the main entry point, and the same name `MyPage` is available from `./alpha` — both accepting the same props for customization.
+
+### Pattern B: Header Variant Prop (Recommended for Complex Pages)
+
+For pages with significant shared logic, use a `headerVariant` prop pattern:
+
+```tsx
+// src/components/MyPage/MyPage.tsx
+import { Content, PageWithHeader } from '@backstage/core-components';
+
+function MyPageContent(
+ props: MyPageProps & { headerVariant: 'legacy' | 'bui' },
+) {
+ const { headerVariant, ...rest } = props;
+
+ // ... shared page logic, data fetching, etc.
+
+ const pageContent = {/* shared page body */} ;
+
+ if (headerVariant === 'bui') {
+ return pageContent;
+ }
+
+ return (
+
+ {pageContent}
+
+ );
+}
+
+// Old system export
+export const MyPage = (props: MyPageProps) => (
+
+);
+
+// New system export
+export const NfsMyPage = (props: MyPageProps) => (
+
+);
+```
+
+### Pattern C: Content-Only Sub-Pages (For Tabbed Plugins)
+
+When using `SubPageBlueprint` for tabbed pages, sub-page loaders should render only the content — the parent `PageBlueprint` provides the header and tabs:
+
+```tsx
+// src/alpha/extensions.tsx
+import {
+ PageBlueprint,
+ SubPageBlueprint,
+} from '@backstage/frontend-plugin-api';
+
+export const myPluginPage = PageBlueprint.make({
+ params: {
+ path: '/my-plugin',
+ routeRef: rootRouteRef,
+ },
+});
+
+export const overviewSubPage = SubPageBlueprint.make({
+ name: 'overview',
+ params: {
+ path: 'overview',
+ title: 'Overview',
+ loader: () =>
+ import('../components/OverviewPage').then(m => ),
+ },
+});
+
+export const settingsSubPage = SubPageBlueprint.make({
+ name: 'settings',
+ params: {
+ path: 'settings',
+ title: 'Settings',
+ loader: () =>
+ import('../components/SettingsPage').then(m => ),
+ },
+});
+```
+
+Note: when using `SubPageBlueprint`, omit the `loader` from `PageBlueprint` to use the built-in tabbed sub-page rendering. The `PageBlueprint` without a `loader` creates a parent page that renders sub-pages as tabs automatically. If the sub-page content needs padding, use `Container` from `@backstage/ui` as a wrapper inside the component.
+
+## Step 4: Migrate APIs to `ApiBlueprint`
+
+APIs that were part of the old `createPlugin({ apis: [...] })` become `ApiBlueprint` extensions added to the plugin's `extensions` array.
+
+### API Ownership
+
+In the new system, each API has an **owner plugin** that controls who can provide or override it. Ownership can be set explicitly via `pluginId` on the `ApiRef` (recommended), or inferred from the `ApiRef` ID string pattern:
+
+- Explicit `pluginId` on the ref → that plugin owns it
+- `plugin..*` ID → owned by that plugin
+- `core.*` ID → owned by the `app` plugin
+
+The recommended way to define API refs in the new system uses the builder pattern with an explicit `pluginId`:
+
+```typescript
+// In your -react package
+import { createApiRef } from '@backstage/frontend-plugin-api';
+
+export const myPluginApiRef = createApiRef().with({
+ id: 'plugin.my-plugin.client',
+ pluginId: 'my-plugin',
+});
+```
+
+When your plugin provides an `ApiBlueprint` in its `extensions` array, the extension is automatically namespaced under your plugin — so the ownership is correct by default:
+
+```typescript
+// src/alpha/apis.ts
+import {
+ ApiBlueprint,
+ discoveryApiRef,
+ fetchApiRef,
+} from '@backstage/frontend-plugin-api';
+import { myPluginApiRef } from '@internal/plugin-my-plugin-react';
+import { MyPluginClient } from '../api';
+
+export const myPluginApi = ApiBlueprint.make({
+ params: defineParams =>
+ defineParams({
+ api: myPluginApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ fetchApi: fetchApiRef,
+ },
+ factory: ({ discoveryApi, fetchApi }) =>
+ new MyPluginClient({ discoveryApi, fetchApi }),
+ }),
+});
+```
+
+Add the API extension to the plugin's `extensions` array.
+
+App adopters who want to override your plugin's API must do so using a `createFrontendModule` targeting your plugin's ID — they cannot override it from a module for a different plugin.
+
+## Step 5: Route Refs
+
+### Reusing Existing Route Refs
+
+Route refs defined using `createRouteRef` from `@backstage/core-plugin-api` can be used directly in the new system — no conversion needed. They work when passed to `createFrontendPlugin`'s `routes`/`externalRoutes` and to `PageBlueprint`'s `routeRef` param:
+
+```tsx
+// routes.ts — keep using your existing route refs from @backstage/core-plugin-api
+import { createRouteRef } from '@backstage/core-plugin-api';
+export const rootRouteRef = createRouteRef({ id: 'my-plugin' });
+
+// alpha.tsx — pass them directly, no conversion needed
+const myPage = PageBlueprint.make({
+ params: {
+ path: '/my-plugin',
+ routeRef: rootRouteRef,
+ loader: () => import('./MyPage').then(m => ),
+ },
+});
+```
+
+There is no need for `convertLegacyRouteRef` or `compatWrapper` from `@backstage/core-compat-api` — these are no longer required for plugin migration.
+
+### Default Targets for External Route Refs
+
+When adding new-system support, set `defaultTarget` on your external route refs so that apps don't need explicit route bindings for common cases. The target string uses the `.` format, matching the `routes` map of the target plugin. The default is only used when the target plugin is actually installed — otherwise the route remains unbound.
+
+```typescript
+// routes.ts
+import { createExternalRouteRef } from '@backstage/core-plugin-api';
+
+export const viewTechDocRouteRef = createExternalRouteRef({
+ id: 'view-techdoc',
+ optional: true,
+ params: ['namespace', 'kind', 'name'],
+ defaultTarget: 'techdocs.docRoot',
+});
+
+export const createComponentRouteRef = createExternalRouteRef({
+ id: 'create-component',
+ optional: true,
+ defaultTarget: 'scaffolder.root',
+});
+```
+
+This significantly improves the out-of-the-box experience — plugins with sensible defaults "just work" when installed without requiring the app to configure `bindRoutes`.
+
+### `useRouteRef` Behavior Difference
+
+In the new system, `useRouteRef` from `@backstage/frontend-plugin-api` may return `undefined` for unbound external routes. Legacy `useRouteRef` from `@backstage/core-plugin-api` throws an error instead. When writing NFS components, handle the `undefined` case.
+
+## Step 6: Translations
+
+If the plugin uses translations, the translation ref should be exported from the main entry point (`src/index.ts`). There is no need to re-export it from `./alpha` — consumers import translation refs from the main entry point regardless of which frontend system they use.
+
+The same applies to other refs like API refs and route refs: keep them exported from the main entry point (or the `-react` package) and avoid duplicating exports in `./alpha`.
+
+## Real Examples from the Backstage Repo
+
+### Catalog Plugin (Dual Entry Point)
+
+- Old: `plugins/catalog/src/plugin.ts` — `createPlugin` with `createRoutableExtension`
+- New: `plugins/catalog/src/alpha/plugin.tsx` — `createFrontendPlugin` with `PageBlueprint`
+- Header split: `plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx`
+ - `BaseCatalogPage` (old) uses `PageWithHeader` + `ContentHeader`
+ - `NfsBaseCatalogPage` (new) uses `Header` from `@backstage/ui` + `Content`
+
+### Scaffolder Plugin (Sub-Pages)
+
+- Old: `plugins/scaffolder/src/plugin.tsx` — single `ScaffolderPage` with internal routing
+- New: `plugins/scaffolder/src/alpha/extensions.tsx` — `PageBlueprint` (no loader) + multiple `SubPageBlueprint` entries for templates, tasks, actions, editor
+- Sub-page loaders wrap content in `` only — no page shell
+
+### Notifications Plugin (Header Variant Pattern)
+
+- `plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx`
+- Uses `headerVariant: 'legacy' | 'bui'` prop
+- `NfsNotificationsPage` returns content only (no `PageWithHeader`)
+- `NotificationsPage` wraps in `PageWithHeader`
+
+### API Docs Plugin (Simple Dual Page)
+
+- `plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx`
+- `DefaultApiExplorerPage` (old) uses `PageWithHeader` + `ContentHeader`
+- `NfsApiExplorerPage` (new) uses `Header` + `Content`
+
+## Migration Checklist
+
+1. [ ] Create `src/alpha.tsx` (or `src/alpha/` directory) with `createFrontendPlugin`
+2. [ ] Add `./alpha` to `package.json` `exports` and `typesVersions`
+3. [ ] Create `PageBlueprint` for each top-level page
+4. [ ] Create `SubPageBlueprint` for tabbed sub-pages (if applicable)
+5. [ ] Convert API factories to `ApiBlueprint` extensions
+6. [ ] Implement NFS page variants without page shell (`Page`/`Header`/`PageWithHeader`)
+7. [ ] Use `Header` from `@backstage/ui` for subtitle/custom actions in NFS pages
+8. [ ] Wire route refs (existing `@backstage/core-plugin-api` refs work directly, no conversion needed)
+9. [ ] Ensure translation refs and API refs are exported from the main entry point (not duplicated in `./alpha`)
+10. [ ] Add `@backstage/frontend-plugin-api` to `package.json` dependencies
+11. [ ] Add `@backstage/ui` to dependencies (if using `Header`)
+12. [ ] Run `yarn tsc` to check for type errors
+13. [ ] Run `yarn lint` to check for missing dependencies
+14. [ ] Test in both old app (`packages/app-legacy`) and new app (`packages/app`)
+15. [ ] Run `yarn build:api-reports` to update API reports (if the project uses API reports)
+
+## Reference
+
+- [Plugin migration guide](https://backstage.io/docs/frontend-system/building-plugins/migrating)
+- [Extension blueprints](https://backstage.io/docs/frontend-system/building-plugins/common-extension-blueprints)
+- [Utility APIs](https://backstage.io/docs/frontend-system/utility-apis/creating)
diff --git a/docs/ai/mcp-actions.md b/docs/ai/mcp-actions.md
new file mode 100644
index 0000000000..498cd42df2
--- /dev/null
+++ b/docs/ai/mcp-actions.md
@@ -0,0 +1,256 @@
+---
+id: mcp-actions
+title: MCP Actions Backend
+description: The MCP Actions Backend exposes actions registered with the Actions Registry as MCP tools.
+---
+
+The MCP Actions Backend exposes [Actions](../backend-system/core-services/actions.md) registered with the [Actions Registry](../backend-system/core-services/actions-registry.md) as MCP tools.
+
+## Installation
+
+This plugin is installed via the `@backstage/plugin-mcp-actions-backend` package. To add it to your backend package, run the following command:
+
+```bash title="From your root directory"
+yarn --cwd packages/backend add @backstage/plugin-mcp-actions-backend
+```
+
+Then, add the plugin to your backend:
+
+```ts title="packages/backend/src/index.ts"
+const backend = createBackend();
+// ...
+backend.add(import('@backstage/plugin-mcp-actions-backend'));
+// ...
+backend.start();
+```
+
+## Actions Configuration
+
+Populate the `pluginSources` configuration with the list of plugins you want exposed as MCP tools like so:
+
+```yaml
+backend:
+ actions:
+ pluginSources:
+ - 'catalog'
+ - 'my-custom-plugin'
+```
+
+For details on filtering actions, see the [filtering actions documentation](../backend-system/core-services/actions.md#filtering-actions).
+
+## Single MCP Server Name & Description
+
+You can configure the name and description of your Backstage MCP server with the following config:
+
+```yaml title="app-config.yaml"
+mcpActions:
+ name: 'My MCP Server' # defaults to "backstage"
+ description: 'Tools for interacting with My MCP Server' # optional
+```
+
+## Namespaced Tool Names
+
+By default, MCP tool names include the plugin ID prefix to avoid collisions across plugins. For example, an action registered as `greet-user` by `my-custom-plugin` is exposed as `my-custom-plugin.greet-user`.
+
+You can disable this if you need the short names for backward compatibility:
+
+```yaml title="app-config.yaml"
+mcpActions:
+ namespacedToolNames: false
+```
+
+## Multiple MCP Servers
+
+By default, the plugin serves a single MCP server at `/api/mcp-actions/v1` that exposes all available actions. You can split actions into multiple focused servers by configuring `mcpActions.servers`, where each key becomes a separate MCP server endpoint.
+
+```yaml title="app-config.yaml"
+mcpActions:
+ servers:
+ catalog:
+ name: 'Backstage Catalog'
+ description: 'Tools for interacting with the software catalog'
+ filter:
+ include:
+ - id: 'catalog:*'
+ scaffolder:
+ name: 'Backstage Scaffolder'
+ description: 'Tools for creating new software from templates'
+ filter:
+ include:
+ - id: 'scaffolder:*'
+```
+
+This creates two MCP server endpoints:
+
+- `http://localhost:7007/api/mcp-actions/v1/catalog`
+- `http://localhost:7007/api/mcp-actions/v1/scaffolder`
+
+Each server uses include filter rules with glob patterns on action IDs to control which actions are exposed. For example, `id: 'catalog:*'` matches all actions registered by the catalog plugin.
+
+When `mcpActions.servers` is not configured, the plugin behaves exactly as before with a single server at `/api/mcp-actions/v1`.
+
+### Filter Rules
+
+Include and exclude filter rules support glob patterns on action IDs and attribute matching. Exclude rules take precedence over include rules. When include rules are specified, actions must match at least one include rule to be exposed.
+
+```yaml title="app-config.yaml"
+mcpActions:
+ servers:
+ catalog:
+ name: 'Backstage Catalog'
+ filter:
+ include:
+ - id: 'catalog:*'
+ exclude:
+ - attributes:
+ destructive: true
+```
+
+## Authentication Configuration
+
+By default, the Backstage backend requires authentication for all requests.
+
+### External Access with Static Tokens
+
+:::warning
+This is meant to be a temporary workaround until device authentication is completed.
+:::
+
+Configure external access with static tokens in your app configuration:
+
+```yaml title="app-config.yaml"
+backend:
+ auth:
+ externalAccess:
+ - type: static
+ options:
+ token: ${MCP_TOKEN}
+ subject: mcp-clients
+ accessRestrictions:
+ - plugin: mcp-actions
+ - plugin: catalog
+```
+
+Generate a secure token:
+
+```bash
+node -p 'require("crypto").randomBytes(24).toString("base64")'
+```
+
+Set the `MCP_TOKEN` environment variable and configure your MCP client to send:
+
+```http
+Authorization: Bearer
+```
+
+For more details about external access tokens and service-to-service authentication, see the
+[Service-to-Service Auth documentation](../auth/service-to-service-auth.md).
+
+### Experimental: Dynamic Client Registration
+
+:::warning
+This feature is highly experimental and only works with the New Frontend System. Proceed with caution.
+:::
+
+You can configure the auth-backend and install the auth frontend plugin to enable **Dynamic Client Registration** with MCP clients. This means you do not need to manually configure a token in your MCP client settings. Instead, a client can request a token on your behalf. When adding the MCP server to an MCP client like Cursor or Claude, a popup requiring your approval will open in your Backstage instance (powered by the auth plugin).
+
+**Requirements:**
+
+- The `@backstage/plugin-auth-backend` plugin must be configured.
+- The new `@backstage/plugin-auth` frontend plugin must be configured.
+
+**Installation:**
+
+1. Install the `@backstage/plugin-auth` frontend plugin:
+
+ ```bash
+ yarn --cwd packages/app add @backstage/plugin-auth
+ ```
+
+2. If you use [feature discovery](../frontend-system/architecture/10-app.md#feature-discovery) the plugin will be added automatically, if you prefer explicit registration, register the plugin as a feature like this:
+
+ ```tsx title="packages/app/src/App.tsx"
+ import authPlugin from '@backstage/plugin-auth';
+
+ const app = createApp({
+ features: [
+ // ...other features
+ authPlugin,
+ ],
+ });
+ ```
+
+3. Enable the feature:
+
+ ```yaml title="app-config.yaml"
+ auth:
+ experimentalDynamicClientRegistration:
+ enabled: true
+
+ # Optional: limit valid callback URLs for added security
+ allowedRedirectUriPatterns:
+ - cursor://*
+ ```
+
+## Configuring MCP Clients
+
+The MCP server supports both **Server-Sent Events (SSE)** and **Streamable HTTP** protocols.
+
+:::warning
+The SSE protocol is deprecated and will be removed in a future release.
+:::
+
+### Endpoints
+
+- **Streamable HTTP:** `http://localhost:7007/api/mcp-actions/v1`
+- **SSE (deprecated):** `http://localhost:7007/api/mcp-actions/v1/sse`
+
+```json
+{
+ "mcpServers": {
+ "backstage-actions": {
+ "url": "http://localhost:7007/api/mcp-actions/v1",
+ "headers": {
+ "Authorization": "Bearer ${MCP_TOKEN}"
+ }
+ }
+ }
+}
+```
+
+The `${MCP_TOKEN}` environment variable would be an [external access static token](#external-access-with-static-tokens).
+
+### Multiple Servers
+
+When `mcpActions.servers` is configured, each server key becomes part of the URL. For example, with servers named `catalog` and `scaffolder`:
+
+- `http://localhost:7007/api/mcp-actions/v1/catalog`
+- `http://localhost:7007/api/mcp-actions/v1/scaffolder`
+
+```json
+{
+ "mcpServers": {
+ "backstage-catalog": {
+ "url": "http://localhost:7007/api/mcp-actions/v1/catalog",
+ "headers": {
+ "Authorization": "Bearer ${MCP_TOKEN}"
+ }
+ },
+ "backstage-scaffolder": {
+ "url": "http://localhost:7007/api/mcp-actions/v1/scaffolder",
+ "headers": {
+ "Authorization": "Bearer ${MCP_TOKEN}"
+ }
+ }
+ }
+}
+```
+
+## Metrics
+
+The MCP Actions Backend emits metrics for the following operations:
+
+- `mcp.server.operation.duration`: The duration taken to process an individual MCP operation
+- `mcp.server.session.duration`: The duration of the MCP session from the perspective of the server
+
+See the [OpenTelemetry tutorial](../tutorials/setup-opentelemetry.md) to learn how to make these metrics available.
diff --git a/docs/ai/skills.md b/docs/ai/skills.md
new file mode 100644
index 0000000000..b983fe6421
--- /dev/null
+++ b/docs/ai/skills.md
@@ -0,0 +1,93 @@
+---
+id: skills
+title: AI Skills
+description: Reusable AI skills for common Backstage development tasks.
+---
+
+Backstage publishes a set of curated _AI skills_ — self-contained guidance files that teach an AI coding assistant how to perform common Backstage engineering tasks. Skills are published to a [well-known endpoint](https://backstage.io/.well-known/skills/) on `backstage.io` and can be installed into your repository with the [`skills.sh`](https://skills.sh/) tool.
+
+## Installing Skills
+
+You need [Node.js](https://nodejs.org/) to run `npx`.
+
+```bash
+npx skills add https://backstage.io
+```
+
+This command reads the published index from `https://backstage.io/.well-known/skills/index.json` and allows you to select which of the available skills you want to install into your repository.
+
+### Where skills are installed
+
+`skills.sh` copies skill files into your repository under a directory it manages (typically `.github/skills/` or a similar location depending on your configuration). Refer to the [`skills.sh` documentation](https://skills.sh/) for details on target paths and how to customize them.
+
+After installation, you can modify the installed files to adapt them to your project's conventions. Subsequent updates from `npx skills add` will offer to merge upstream changes.
+
+### Using Skills with Your AI Assistant
+
+Once a skill is installed in your repository, attach or reference the relevant `SKILL.md` file when starting a task with your AI coding assistant. Most AI assistants in editors such as VS Code will automatically pick up instruction files that are committed to your repository.
+
+For example, when migrating MUI imports in a plugin, include the `mui-to-bui-migration` skill so the assistant follows the correct component mapping and import patterns.
+
+## Contributing New Skills
+
+Skills are authored in the Backstage monorepo at `docs/.well-known/skills/`. Each skill lives in its own subdirectory and must include a `SKILL.md` file as the primary entry point.
+
+### Skill directory layout
+
+```text
+docs/.well-known/skills/
+ index.json # Published index of all skills
+ /
+ SKILL.md # Primary skill entry point (required)
+
+```
+
+### Writing a SKILL.md
+
+A `SKILL.md` file must include a YAML front matter block with the following fields:
+
+```markdown
+---
+name:
+description:
+---
+
+# Skill Title
+
+Introductory paragraph explaining when and why to use this skill.
+
+...
+```
+
+The `name` must match the directory name. The `description` is shown to users when they browse or install skills and should be one to two sentences describing the task the skill covers.
+
+Keep skills focused on a single, well-defined task. A skill that tries to cover too many scenarios is harder to use effectively. Prefer concrete, step-by-step guidance, working code examples, and explicit notes about common pitfalls.
+
+### Registering the skill in the index
+
+Add an entry to `docs/.well-known/skills/index.json`:
+
+```json
+{
+ "skills": [
+ {
+ "name": "",
+ "description": "",
+ "files": ["SKILL.md"]
+ }
+ ]
+}
+```
+
+If your skill includes additional supporting files, list each one in the `files` array.
+
+### Review process
+
+All changes to skills go through the standard Backstage pull request process. When authoring or reviewing a skill, consider:
+
+- **Accuracy** — Does the skill reflect current Backstage APIs and conventions?
+- **Completeness** — Does it cover the most common cases a developer will encounter?
+- **Safety** — Does it avoid patterns that could introduce security or correctness issues?
+- **Scope** — Is the skill focused on a single task, or should it be split?
+
+Skills are part of the published Backstage documentation surface, so they follow the same contribution guidelines as the rest of the docs. See [CONTRIBUTING.md](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) for the full contribution process.
diff --git a/docs/ai/well-known-actions.md b/docs/ai/well-known-actions.md
new file mode 100644
index 0000000000..40b38f1773
--- /dev/null
+++ b/docs/ai/well-known-actions.md
@@ -0,0 +1,31 @@
+---
+id: well-known-actions
+title: Well-known Actions
+description: This section lists a number of well-known actions that are part of the Actions Registry.
+---
+
+This section lists a number of well-known [Actions](../backend-system/core-services/actions.md) registered with the [Actions Registry](../backend-system/core-services/actions-registry.md).
+
+## Actions
+
+This is a (non-exhaustive) list of actions that are known to be part of the Actions Registry. Entries are in the format: "`action-name` (Action Title): Shortened Action Description"
+
+### Auth
+
+- `auth.who-am-i` (Who Am I): Returns the catalog entity and user info for the currently authenticated user. This action requires user credentials and cannot be used with service or unauthenticated credentials.
+
+### Catalog
+
+- `catalog.get-catalog-entity` (Get Catalog Entity): This allows you to get a single entity from the software catalog.
+- `catalog.query-catalog-entities` (Query Catalog Entities): Query entities from the Backstage Software Catalog using predicate filters.
+- `catalog.register-entity` (Register entity in the Catalog): Registers one or more entities in the Backstage catalog by creating a Location entity that points to a remote `catalog-info.yaml` file.
+- `catalog.unregister-entity` (Unregister entity from the Catalog): Unregisters a Location entity and all entities it owns from the Backstage catalog.
+- `catalog.validate-entity` (Validate Catalog Entity): This action can be used to validate `catalog-info.yaml` file contents meant to be used with the software catalog.
+
+### Scaffolder
+
+- `scaffolder.dry-run-template` (Dry Run Scaffolder Template): Dry-runs a scaffolder template to validate it without making changes. Returns success with execution logs, or errors for validation failures.
+- `scaffolder.list-scaffolder-actions` (List Scaffolder Actions): Lists all installed Scaffolder actions.
+- `scaffolder.list-scaffolder-tasks` (List Scaffolder Tasks): This allows you to list scaffolder tasks that have been created.
+- `scaffolder.execute-template` (Execute Scaffolder Template): Executes a Scaffolder template with its template ref and input parameter values.
+- `scaffolder.get-scaffolder-task-logs` (Get Scaffolder Task Logs): This allows you to fetch the logs of a given scaffolder task.
diff --git a/docs/api/deprecations.md b/docs/api/deprecations.md
index 84c0300543..136d70778e 100644
--- a/docs/api/deprecations.md
+++ b/docs/api/deprecations.md
@@ -83,7 +83,7 @@ migrate to your own custom API.
First, you'll need to define a new Utility API reference. If you're only using
the API for sign-in, you can put the definition in `packages/app/src/apis.ts`.
-However, if you need to access your auth API inside plugins you you'll need to
+However, if you need to access your auth API inside plugins you'll need to
export it from a common package. If you don't already have one, we recommend
creating `@internal/apis` and from there exporting the API reference.
diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md
index 5235a14ddf..c79eab123b 100644
--- a/docs/api/utility-apis.md
+++ b/docs/api/utility-apis.md
@@ -18,23 +18,23 @@ plugins to communicate during their entire life cycle.
## Consuming APIs
-Each Utility API is tied to an [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)
+Each Utility API is tied to an [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html)
instance, which is a global singleton object without any additional state or
functionality, its only purpose is to reference Utility APIs.
-[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)s are created using
-[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html), which is exported
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html)s are created using
+[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.createApiRef.html), which is exported
by [`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html). There are also
many predefined Utility APIs in
[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html), and they're all
exported with a name of the pattern `*ApiRef`, for example
-[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html).
+[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.errorApiRef.html).
To access one of the Utility APIs inside a React component, use the
-[`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html) hook exported by
+[`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.useApi.html) hook exported by
[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html), or the
-[`withApis`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.withApis.html) HOC if you prefer class
+[`withApis`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.withApis.html) HOC if you prefer class
components. For example, the
-[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html) can be accessed like this:
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ErrorApi.html) can be accessed like this:
```tsx
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
@@ -52,9 +52,9 @@ export const MyComponent = () => {
```
Note that there is no explicit type given for
-[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html). This is because the
-[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html) has the type
-embedded, and [`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html) is able to infer
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ErrorApi.html). This is because the
+[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.errorApiRef.html) has the type
+embedded, and [`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.useApi.html) is able to infer
the type.
Also note that consuming Utility APIs is not limited to plugins; it can be done
@@ -67,15 +67,15 @@ requirement is that they are beneath the `AppProvider` in the react tree.
### API Factories
APIs are registered in the form of
-[`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) instances, which encapsulate
+[`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiFactory.html) instances, which encapsulate
the process of instantiating an API. It is a collection of three things: the
-[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) of the API to instantiate, a
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html) of the API to instantiate, a
list of all required dependencies, and a factory function that returns a new API
instance.
For example, this is the default
-[`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) for the
-[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html):
+[`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiFactory.html) for the
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ErrorApi.html):
```ts
createApiFactory({
@@ -89,25 +89,25 @@ createApiFactory({
});
```
-In this example, the [`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html)
+In this example, the [`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.errorApiRef.html)
is our API, which encapsulates the
-[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html) type. The
-[`alertApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.alertApiRef.html) is our single
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ErrorApi.html) type. The
+[`alertApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.alertApiRef.html) is our single
dependency, which we give the name `alertApi`, and is then passed on to the
factory function, which returns an implementation of the
-[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html).
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ErrorApi.html).
-The [`createApiFactory`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiFactory.html)
+The [`createApiFactory`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.createApiFactory.html)
function is a thin wrapper that enables TypeScript type inference. You may
notice that there are no type annotations in the above example, and that is
because we're able to infer all types from the
-[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)s. TypeScript will make sure
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html)s. TypeScript will make sure
that the return value of the `factory` function matches the type embedded in
-`api`'s [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html), in this case the
-[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html). It will also match the
+`api`'s [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html), in this case the
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ErrorApi.html). It will also match the
types between the `deps` and the parameters of the `factory` function, again
using the type embedded within the
-[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)s.
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html)s.
## Registering API Factories
@@ -120,8 +120,8 @@ app, and the app itself.
Starting with the Backstage core library, it provides implementations for all of
the core APIs. The core APIs are the ones exported by
[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html), such as the
-[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html) and
-[`configApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.configApiRef.html).
+[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.errorApiRef.html) and
+[`configApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.configApiRef.html).
The core APIs are loaded for any app created with
[`createApp`](https://backstage.io/api/stable/functions/_backstage_app-defaults.createApp.html) from
@@ -133,7 +133,7 @@ there is no step that needs to be taken to include these APIs in an app.
In addition to the core APIs, plugins can define and export their own APIs.
While doing so, they should usually also provide default implementations of their
own APIs; for example, the `catalog` plugin exports `catalogApiRef` and also
-supplies a default [`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) of
+supplies a default [`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiFactory.html) of
that API using the `CatalogClient`. There is one restriction to plugin-provided
API Factories: plugins may not supply factories for core APIs; trying to do so
will cause the app to refuse to start.
@@ -227,16 +227,16 @@ const app = createApp({
```
Note that the above line will cause an error if `IgnoreErrorApi` does not fully
-implement the [`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html), as it is
+implement the [`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ErrorApi.html), as it is
checked by the type embedded in the
-[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html) at compile time.
+[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.errorApiRef.html) at compile time.
## Defining custom Utility APIs
Plugins are free to define their own Utility APIs. Simply define the TypeScript
interface for the API and create an
-[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) using
-[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html) exported from
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html) using
+[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.createApiRef.html) exported from
[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html). Also, be sure to
provide at least one implementation of the API and to declare a default factory
for the API in [`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html).
@@ -244,16 +244,16 @@ for the API in [`createPlugin`](https://backstage.io/api/stable/functions/_backs
Custom Utility APIs can be either public or private, which is up to the plugin to choose. Private APIs do not expose an external API surface, and it's therefore possible to make breaking changes to the API without affecting other users of the plugin. If an API is made public, however, it opens up for other plugins to make use of the API, and it also makes it possible for users for your plugin to override the API in the app. It is, however, important to maintain backward compatibility of public APIs, as you may otherwise break apps that are using your plugin.
To make an API public, simply export the
-[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) of the API, and any associated
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html) of the API, and any associated
types. To make an API private, just avoid exporting the
-[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html), but still be sure to supply a
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html), but still be sure to supply a
default factory to [`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html).
Private APIs are useful for plugins that want to depend on other APIs outside of
React components, but not have to expose an entire API surface to maintain. When
using private APIs, it is fine to use the `typeof` of an implementing class as
the type parameter passed to
-[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html), while public APIs
+[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.createApiRef.html), while public APIs
should always define a separate TypeScript interface type.
Plugins may depend on APIs from other plugins, both in React components and as
@@ -262,13 +262,13 @@ dependencies between plugins.
## Architecture
-The [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) instances mentioned above
+The [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html) instances mentioned above
provide a point of indirection between consumers and producers of Utility APIs.
It allows for plugins and components to depend on APIs in a type-safe way,
without having a direct reference to a concrete implementation of the APIs. The
Apps are also given a lot of flexibility in what implementations to provide. As
long as they adhere to the contract established by an
-[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html), they are free to choose any
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html), they are free to choose any
implementation they want.
The figure below shows the relationship between
diff --git a/docs/assets/uiguide/backstage-website-registered-catalog-view.png b/docs/assets/uiguide/backstage-website-registered-catalog-view.png
new file mode 100644
index 0000000000..9263eff0c7
Binary files /dev/null and b/docs/assets/uiguide/backstage-website-registered-catalog-view.png differ
diff --git a/docs/assets/uiguide/catalog-filter-options.png b/docs/assets/uiguide/catalog-filter-options.png
new file mode 100644
index 0000000000..f2a85ab192
Binary files /dev/null and b/docs/assets/uiguide/catalog-filter-options.png differ
diff --git a/docs/assets/uiguide/catalog-graph.png b/docs/assets/uiguide/catalog-graph.png
new file mode 100644
index 0000000000..59e290b0f0
Binary files /dev/null and b/docs/assets/uiguide/catalog-graph.png differ
diff --git a/docs/assets/uiguide/component-name-updated.png b/docs/assets/uiguide/component-name-updated.png
new file mode 100644
index 0000000000..baf7c94b3f
Binary files /dev/null and b/docs/assets/uiguide/component-name-updated.png differ
diff --git a/docs/assets/uiguide/confirm-unregister-entity.png b/docs/assets/uiguide/confirm-unregister-entity.png
new file mode 100644
index 0000000000..89a565ff35
Binary files /dev/null and b/docs/assets/uiguide/confirm-unregister-entity.png differ
diff --git a/docs/assets/uiguide/curve-monotone.png b/docs/assets/uiguide/curve-monotone.png
new file mode 100644
index 0000000000..52275ec780
Binary files /dev/null and b/docs/assets/uiguide/curve-monotone.png differ
diff --git a/docs/assets/uiguide/curve-step-before.png b/docs/assets/uiguide/curve-step-before.png
new file mode 100644
index 0000000000..a9aa4c8e6f
Binary files /dev/null and b/docs/assets/uiguide/curve-step-before.png differ
diff --git a/docs/assets/uiguide/details-for-registered-backstage-website.png b/docs/assets/uiguide/details-for-registered-backstage-website.png
new file mode 100644
index 0000000000..00d55026f2
Binary files /dev/null and b/docs/assets/uiguide/details-for-registered-backstage-website.png differ
diff --git a/docs/assets/uiguide/details-system-entity.png b/docs/assets/uiguide/details-system-entity.png
new file mode 100644
index 0000000000..c667421603
Binary files /dev/null and b/docs/assets/uiguide/details-system-entity.png differ
diff --git a/docs/assets/uiguide/enter-name-of-new-component.png b/docs/assets/uiguide/enter-name-of-new-component.png
new file mode 100644
index 0000000000..fc6a11a360
Binary files /dev/null and b/docs/assets/uiguide/enter-name-of-new-component.png differ
diff --git a/docs/assets/uiguide/enter-url-of-component.png b/docs/assets/uiguide/enter-url-of-component.png
new file mode 100644
index 0000000000..29fecb7bfd
Binary files /dev/null and b/docs/assets/uiguide/enter-url-of-component.png differ
diff --git a/docs/assets/uiguide/entities-owned-by-me.png b/docs/assets/uiguide/entities-owned-by-me.png
new file mode 100644
index 0000000000..e0dda5e680
Binary files /dev/null and b/docs/assets/uiguide/entities-owned-by-me.png differ
diff --git a/docs/assets/uiguide/entity-actions.png b/docs/assets/uiguide/entity-actions.png
new file mode 100644
index 0000000000..3c725758e4
Binary files /dev/null and b/docs/assets/uiguide/entity-actions.png differ
diff --git a/docs/assets/uiguide/error-creating-new-component.png b/docs/assets/uiguide/error-creating-new-component.png
new file mode 100644
index 0000000000..8c7da95159
Binary files /dev/null and b/docs/assets/uiguide/error-creating-new-component.png differ
diff --git a/docs/assets/uiguide/error-message-catalog-info-file-deleted.png b/docs/assets/uiguide/error-message-catalog-info-file-deleted.png
new file mode 100644
index 0000000000..7526ba65aa
Binary files /dev/null and b/docs/assets/uiguide/error-message-catalog-info-file-deleted.png differ
diff --git a/docs/assets/uiguide/example-website-relationships.png b/docs/assets/uiguide/example-website-relationships.png
new file mode 100644
index 0000000000..b4fa6e33c7
Binary files /dev/null and b/docs/assets/uiguide/example-website-relationships.png differ
diff --git a/docs/assets/uiguide/filter-by-name.png b/docs/assets/uiguide/filter-by-name.png
new file mode 100644
index 0000000000..ca0336eadc
Binary files /dev/null and b/docs/assets/uiguide/filter-by-name.png differ
diff --git a/docs/assets/uiguide/max-depth-1.png b/docs/assets/uiguide/max-depth-1.png
new file mode 100644
index 0000000000..d353b8ae23
Binary files /dev/null and b/docs/assets/uiguide/max-depth-1.png differ
diff --git a/docs/assets/uiguide/max-depth-infinite.png b/docs/assets/uiguide/max-depth-infinite.png
new file mode 100644
index 0000000000..0f72cccb84
Binary files /dev/null and b/docs/assets/uiguide/max-depth-infinite.png differ
diff --git a/docs/assets/uiguide/new-tutorial-component-in-software-catalog.png b/docs/assets/uiguide/new-tutorial-component-in-software-catalog.png
new file mode 100644
index 0000000000..51ee73c524
Binary files /dev/null and b/docs/assets/uiguide/new-tutorial-component-in-software-catalog.png differ
diff --git a/docs/assets/uiguide/portal-with-annotations.png b/docs/assets/uiguide/portal-with-annotations.png
new file mode 100644
index 0000000000..a8c9bc535b
Binary files /dev/null and b/docs/assets/uiguide/portal-with-annotations.png differ
diff --git a/docs/assets/uiguide/review-select-import.png b/docs/assets/uiguide/review-select-import.png
new file mode 100644
index 0000000000..bd4a0acd7c
Binary files /dev/null and b/docs/assets/uiguide/review-select-import.png differ
diff --git a/docs/assets/uiguide/select-advanced-options.png b/docs/assets/uiguide/select-advanced-options.png
new file mode 100644
index 0000000000..27fe7c7b16
Binary files /dev/null and b/docs/assets/uiguide/select-advanced-options.png differ
diff --git a/docs/assets/uiguide/select-create-for-new-component.png b/docs/assets/uiguide/select-create-for-new-component.png
new file mode 100644
index 0000000000..9fbc612ac0
Binary files /dev/null and b/docs/assets/uiguide/select-create-for-new-component.png differ
diff --git a/docs/assets/uiguide/select-create-to-make-component.png b/docs/assets/uiguide/select-create-to-make-component.png
new file mode 100644
index 0000000000..e86e9398ca
Binary files /dev/null and b/docs/assets/uiguide/select-create-to-make-component.png differ
diff --git a/docs/assets/uiguide/select-delete-entity.png b/docs/assets/uiguide/select-delete-entity.png
new file mode 100644
index 0000000000..6874c5e83d
Binary files /dev/null and b/docs/assets/uiguide/select-delete-entity.png differ
diff --git a/docs/assets/uiguide/select-edit-icon-for-component.png b/docs/assets/uiguide/select-edit-icon-for-component.png
new file mode 100644
index 0000000000..04d693259d
Binary files /dev/null and b/docs/assets/uiguide/select-edit-icon-for-component.png differ
diff --git a/docs/assets/uiguide/select-example-website.png b/docs/assets/uiguide/select-example-website.png
new file mode 100644
index 0000000000..f8f72699eb
Binary files /dev/null and b/docs/assets/uiguide/select-example-website.png differ
diff --git a/docs/assets/uiguide/select-register-existing-component.png b/docs/assets/uiguide/select-register-existing-component.png
new file mode 100644
index 0000000000..8d3f5c7af4
Binary files /dev/null and b/docs/assets/uiguide/select-register-existing-component.png differ
diff --git a/docs/assets/uiguide/select-review-create-component.png b/docs/assets/uiguide/select-review-create-component.png
new file mode 100644
index 0000000000..5acce27ec6
Binary files /dev/null and b/docs/assets/uiguide/select-review-create-component.png differ
diff --git a/docs/assets/uiguide/select-unregister-entity-from-three-dots.png b/docs/assets/uiguide/select-unregister-entity-from-three-dots.png
new file mode 100644
index 0000000000..959ec3f544
Binary files /dev/null and b/docs/assets/uiguide/select-unregister-entity-from-three-dots.png differ
diff --git a/docs/assets/uiguide/select-view-graph.png b/docs/assets/uiguide/select-view-graph.png
new file mode 100644
index 0000000000..79886a82b4
Binary files /dev/null and b/docs/assets/uiguide/select-view-graph.png differ
diff --git a/docs/assets/uiguide/setup-nodejs-cat-owner.png b/docs/assets/uiguide/setup-nodejs-cat-owner.png
new file mode 100644
index 0000000000..5e8effec72
Binary files /dev/null and b/docs/assets/uiguide/setup-nodejs-cat-owner.png differ
diff --git a/docs/assets/uiguide/simplify-off-merge-relations-off.png b/docs/assets/uiguide/simplify-off-merge-relations-off.png
new file mode 100644
index 0000000000..d999a7a5c7
Binary files /dev/null and b/docs/assets/uiguide/simplify-off-merge-relations-off.png differ
diff --git a/docs/assets/uiguide/simplify-off-merge-relations-on.png b/docs/assets/uiguide/simplify-off-merge-relations-on.png
new file mode 100644
index 0000000000..1003f2dd3d
Binary files /dev/null and b/docs/assets/uiguide/simplify-off-merge-relations-on.png differ
diff --git a/docs/assets/uiguide/simplify-on-merge-relations-off.png b/docs/assets/uiguide/simplify-on-merge-relations-off.png
new file mode 100644
index 0000000000..60cbe0107e
Binary files /dev/null and b/docs/assets/uiguide/simplify-on-merge-relations-off.png differ
diff --git a/docs/assets/uiguide/simplify-on-merge-relations-on.png b/docs/assets/uiguide/simplify-on-merge-relations-on.png
new file mode 100644
index 0000000000..d72489b50e
Binary files /dev/null and b/docs/assets/uiguide/simplify-on-merge-relations-on.png differ
diff --git a/docs/assets/uiguide/successful-create-new-component.png b/docs/assets/uiguide/successful-create-new-component.png
new file mode 100644
index 0000000000..658189b7d2
Binary files /dev/null and b/docs/assets/uiguide/successful-create-new-component.png differ
diff --git a/docs/assets/uiguide/tutorial-catalog-info-yaml-file.png b/docs/assets/uiguide/tutorial-catalog-info-yaml-file.png
new file mode 100644
index 0000000000..39ae896cbd
Binary files /dev/null and b/docs/assets/uiguide/tutorial-catalog-info-yaml-file.png differ
diff --git a/docs/assets/uiguide/tutorial-component-open-in-catalog.png b/docs/assets/uiguide/tutorial-component-open-in-catalog.png
new file mode 100644
index 0000000000..107974c437
Binary files /dev/null and b/docs/assets/uiguide/tutorial-component-open-in-catalog.png differ
diff --git a/docs/assets/uiguide/updated-component-name-in-ui.png b/docs/assets/uiguide/updated-component-name-in-ui.png
new file mode 100644
index 0000000000..7b5a9e5648
Binary files /dev/null and b/docs/assets/uiguide/updated-component-name-in-ui.png differ
diff --git a/docs/auth/auth0/provider.md b/docs/auth/auth0/provider.md
index b836bf01f6..1a560d4f62 100644
--- a/docs/auth/auth0/provider.md
+++ b/docs/auth/auth0/provider.md
@@ -5,13 +5,6 @@ sidebar_label: Auth0
description: Adding Auth0 as an authentication provider in Backstage
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage
-[version 1.24](../../releases/v1.24.0.md). If you are still on the old backend
-system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/auth0/provider--old.md)
-instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
The Backstage `core-plugin-api` package comes with an Auth0 authentication
provider that can authenticate users using OAuth.
diff --git a/docs/auth/bitbucketServer/provider.md b/docs/auth/bitbucketServer/provider.md
index 89b8be3ff6..0beaa08f18 100644
--- a/docs/auth/bitbucketServer/provider.md
+++ b/docs/auth/bitbucketServer/provider.md
@@ -5,13 +5,6 @@ sidebar_label: Bitbucket Server
description: Adding Bitbucket Server OAuth as an authentication provider in Backstage
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage
-[version 1.24](../../releases/v1.24.0.md). If you are still on the old backend
-system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/bitbucketServer/provider--old.md)
-instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
The Backstage `core-plugin-api` package comes with a Bitbucket Server authentication provider that can authenticate
users using Bitbucket Server. This does **NOT** work with Bitbucket Cloud.
diff --git a/docs/auth/guest/provider.md b/docs/auth/guest/provider.md
index 5072aabe50..f2ebfff8d6 100644
--- a/docs/auth/guest/provider.md
+++ b/docs/auth/guest/provider.md
@@ -19,10 +19,6 @@ This provider should only ever be enabled for `development`. To prevent unauthor
### Backend
-:::note
-This will only work with the new backend system. There is no support for this in the old backend.
-:::
-
Add the `@backstage/plugin-auth-backend-module-guest-provider` to your backend installation.
```sh title="From your Backstage root directory"
diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md
index 0d4b50dcbe..ef77e69c22 100644
--- a/docs/auth/identity-resolver.md
+++ b/docs/auth/identity-resolver.md
@@ -4,13 +4,6 @@ title: Sign-in Identities and Resolvers
description: An introduction to Backstage user identities and sign-in resolvers
---
-:::info
-This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage
-[version 1.24](../releases/v1.24.0.md). If you are still on the old backend
-system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/identity-resolver--old.md)
-instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
-:::
-
By default, every Backstage auth provider is configured only for the use-case of
access delegation. This enables Backstage to request resources and actions from
external systems on behalf of the user, for example re-triggering a build in CI.
@@ -406,50 +399,6 @@ async signInResolver({ profile }, ctx) {
}
```
-## Reducing the size of issued tokens
-
-By default the auth backend will issue user identity tokens that include the
-ownership references of the user in the `ent` claim of the JWT payload. This is
-done to make it easier and more efficient for consumers of the token to resolve
-ownership of the user. However, depending on the shape of your organization and
-how you resolve ownership claims, these tokens can grow quite large.
-
-To address this, the auth backend now supports the configuration flag
-`auth.omitIdentityTokenOwnershipClaim` that causes the `ent` claim to be omitted
-from the token. This can be set to `true` in the `app-config.yaml` file.
-
-```yaml title="in app-config.yaml"
-auth:
- omitIdentityTokenOwnershipClaim: true
-```
-
-When this flag is set, the `ent` claim will no longer be present in the token,
-and consumers of the token will need to call the `/v1/userinfo` endpoint on the
-auth backend to fetch the ownership references of the user. However, there's usually no
-action required for consumers. Clients will still receive the full set
-of claims during authentication, and any plugin backends will already need to
-use the
-[`UserInfoService`](../backend-system/core-services/user-info.md) to
-access the ownership references from user credentials, which already calls the
-user info endpoint if necessary.
-
-When enabling this flag, it is important that any custom sign-in resolvers directly return the result of the sign-in method. For example, the following would not work:
-
-```ts
-const { token } = await ctx.issueToken({
- claims: { sub: entityRef, ent: [entityRef] },
-});
-return { token }; // WARNING: This will not work
-```
-
-Instead, the sign-in resolver should directly return the result:
-
-```ts
-return ctx.issueToken({
- claims: { sub: entityRef, ent: [entityRef] },
-});
-```
-
##### Using the `dangerouslyAllowSignInWithoutUserInCatalog` Option
Another way to bypass this requirement is to enable the `dangerouslyAllowSignInWithoutUserInCatalog` option for resolvers.
diff --git a/docs/auth/index--old.md b/docs/auth/index--old.md
new file mode 100644
index 0000000000..6ba50f7eea
--- /dev/null
+++ b/docs/auth/index--old.md
@@ -0,0 +1,549 @@
+---
+id: index--old
+title: Authentication in Backstage
+description: Introduction to authentication in Backstage
+---
+
+:::info
+This documentation is written for the old frontend system. If you are on the [new frontend system](../frontend-system/index.md) you may want to read [its own article](./index.md) instead.
+:::
+
+The authentication system in Backstage serves two distinct purposes: sign-in and
+identification of users, as well as delegating access to third-party resources. It is possible to
+configure Backstage to have any number of authentication providers, but only
+one of these will typically be used for sign-in, with the rest being used to provide
+access to external resources.
+
+:::note Note
+
+Identity management and the Sign-In page in Backstage will block external access by default, without setting `backend.auth.dangerouslyDisableDefaultAuthPolicy` in configuration. Even so, the frontend bundle is not protected from external access, protecting it requires the use of the [experimental public entry point](https://backstage.io/docs/tutorials/enable-public-entry/). You can learn more about this in the [Threat Model](../overview/threat-model.md#operator-responsibilities).
+
+:::
+
+## Built-in Authentication Providers
+
+Backstage comes with many common authentication providers in the core library:
+
+- [Auth0](auth0/provider.md)
+- [Atlassian](atlassian/provider.md)
+- [Azure](microsoft/provider.md)
+- [Azure Easy Auth](microsoft/azure-easyauth.md)
+- [Bitbucket](bitbucket/provider.md)
+- [Bitbucket Server](bitbucketServer/provider.md)
+- [Cloudflare Access](cloudflare/provider.md)
+- [GitHub](github/provider.md)
+- [GitLab](gitlab/provider.md)
+- [Google](google/provider.md)
+- [Google IAP](google/gcp-iap-auth.md)
+- [Okta](okta/provider.md)
+- [OAuth 2 Custom Proxy](oauth2-proxy/provider.md)
+- [OneLogin](onelogin/provider.md)
+- [OpenShift](openshift/provider.md)
+- [VMware Cloud](vmware-cloud/provider.md)
+
+These built-in providers handle the authentication flow for a particular service, including required scopes, callbacks, etc. These providers are each added to a
+Backstage app in a similar way.
+
+## Configuring Authentication Providers
+
+Each built-in provider has a configuration block under the `auth` section of
+`app-config.yaml`. For example, the GitHub provider:
+
+```yaml
+auth:
+ environment: development
+ providers:
+ github:
+ development:
+ clientId: ${AUTH_GITHUB_CLIENT_ID}
+ clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
+```
+
+See the documentation for a particular provider to see what configuration is
+needed.
+
+The `providers` key may have several authentication providers if multiple
+authentication methods are supported. Each provider may also have configuration
+for different authentication environments (development, production, etc). This
+allows a single auth backend to serve multiple environments, such as running a
+local frontend against a deployed backend. The provider configuration matching
+the local `auth.environment` setting will be selected.
+
+## Sign-In Configuration
+
+Using an authentication provider for sign-in is something you need to configure
+both in the frontend app as well as the `auth` backend plugin. For information
+on how to configure the backend app, see [Sign-in Identities and Resolvers](./identity-resolver.md).
+The rest of this section will focus on how to configure sign-in for the frontend app.
+
+Sign-in is configured by providing a custom `SignInPage` app component. It will be
+rendered before any other routes in the app and is responsible for providing the
+identity of the current user. The `SignInPage` can render any number of pages and
+components, or just blank space with logic running in the background. In the end, however, it must provide a valid Backstage user identity through the `onSignInSuccess`
+callback prop, at which point the rest of the app is rendered.
+
+If you want to, you can use the `SignInPage` component that is provided by `@backstage/core-components`,
+which takes either a `provider` or `providers` (array) prop of `SignInProviderConfig` definitions.
+
+The following example for GitHub shows the additions needed to `packages/app/src/App.tsx`,
+and can be adapted to any of the built-in providers:
+
+```tsx title="packages/app/src/App.tsx"
+/* highlight-add-start */
+import { githubAuthApiRef } from '@backstage/core-plugin-api';
+import { SignInPage } from '@backstage/core-components';
+/* highlight-add-end */
+
+const app = createApp({
+ /* highlight-add-start */
+ components: {
+ SignInPage: props => (
+
+ ),
+ },
+ /* highlight-add-end */
+ // ..
+});
+```
+
+:::note Note
+
+You can configure sign-in to use a redirect flow with no pop-up by adding
+`enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml`
+
+:::
+
+### Using Multiple Providers
+
+You can also use the `providers` prop to enable multiple sign-in methods, for example to allow guest access:
+
+```tsx title="packages/app/src/App.tsx"
+const app = createApp({
+ /* highlight-add-start */
+ components: {
+ SignInPage: props => (
+
+ ),
+ },
+ /* highlight-add-end */
+ // ..
+});
+```
+
+### Conditionally Render Sign In Provider
+
+In the above example, you have both Guest and GitHub sign-in options; this is helpful for non-production, but in Production you will most likely not want to offer Guest access. You can easily use information from your config to help conditionally render the provider:
+
+```tsx title="packages/app/src/App.tsx"
+import {
+ configApiRef,
+ githubAuthApiRef,
+ useApi,
+} from '@backstage/core-plugin-api';
+
+const app = createApp({
+ components: {
+ SignInPage: props => {
+ const configApi = useApi(configApiRef);
+ if (configApi.getString('auth.environment') === 'development') {
+ return (
+
+ );
+ }
+ return (
+
+ );
+ },
+ },
+ // ..
+});
+```
+
+## Sign-In with Proxy Providers
+
+Some auth providers are so-called "proxy" providers, meaning they're meant to be used
+behind an authentication proxy. Examples of these are
+[Amazon Application Load Balancer](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md),
+[Azure EasyAuth](./microsoft/azure-easyauth.md),
+[Cloudflare Access](./cloudflare/provider.md),
+[Google Identity-Aware Proxy](./google/gcp-iap-auth.md)
+and [OAuth2 Proxy](./oauth2-proxy/provider.md).
+
+When using a proxy provider, you'll end up wanting to use a different sign-in page, as
+there is no need for further user interaction once you've signed in towards the proxy.
+All the sign-in page needs to do is call the `/refresh` endpoint of the auth providers
+to get the existing session, which is exactly what the `ProxiedSignInPage` does. The only
+thing you need to do to configure the `ProxiedSignInPage` is to pass the ID of the provider like this:
+
+```tsx title="packages/app/src/App.tsx"
+import { ProxiedSignInPage } from '@backstage/core-components';
+
+const app = createApp({
+ components: {
+ SignInPage: props => ,
+ },
+ // ..
+});
+```
+
+If the provider in auth backend expects additional headers such as `x-provider-token`, there is now a way to configure that in `ProxiedSignInPage` using the optional `headers` prop.
+
+Example:
+
+```tsx
+
+```
+
+Headers can also be returned in an async manner:
+
+```tsx
+ {
+ const someValue = await someFn();
+ return { 'x-some-key': someValue };
+ }}
+ /* highlight-end */
+/>
+```
+
+A downside of this method is that it can be cumbersome to set up for local development.
+As a workaround for this, it's possible to dynamically select the sign-in page based on
+what environment the app is running in and then use a different sign-in method for local
+development, if one is needed at all. Depending on the exact setup, one might choose to
+select the sign-in method based on the `process.env.NODE_ENV` environment variable,
+by checking the `hostname` of the current location, or by accessing the configuration API
+to read a configuration value. For example:
+
+```tsx title="packages/app/src/App.tsx"
+const app = createApp({
+ components: {
+ SignInPage: props => {
+ const configApi = useApi(configApiRef);
+ if (configApi.getString('auth.environment') === 'development') {
+ return (
+
+ );
+ }
+ return ;
+ },
+ },
+ // ..
+});
+```
+
+When using multiple auth providers like this, it's important that you configure the different
+sign-in resolvers so that they resolve to the same identity regardless of the method used.
+
+## Scaffolder Configuration (Software Templates)
+
+If you want to use the authentication capabilities of the [Repository Picker](../features/software-templates/writing-templates.md#the-repository-picker) inside your software templates, you will need to configure the [`ScmAuthApi`](https://backstage.io/api/stable/interfaces/_backstage_integration-react.ScmAuthApi.html) alongside your authentication provider. It is an API used to authenticate towards different SCM systems in a generic way, based on what resource is being accessed.
+
+To set it up, you'll need to add an API factory entry to `packages/app/src/apis.ts`. The example below sets up the `ScmAuthApi` for an already configured GitLab authentication provider:
+
+```ts title="packages/app/src/apis.ts"
+createApiFactory({
+ api: scmAuthApiRef,
+ deps: {
+ gitlabAuthApi: gitlabAuthApiRef,
+ },
+ factory: ({ gitlabAuthApi }) => ScmAuth.forGitlab(gitlabAuthApi),
+});
+```
+
+In case you are using a custom authentication providers, you might need to add a [custom `ScmAuthApi` implementation](./index.md#custom-scmauthapi-implementation).
+
+## For Plugin Developers
+
+The Backstage frontend core APIs provide a set of Utility APIs for plugin developers
+to use, both to access the user identity as well as third-party resources.
+
+### Identity for Plugin Developers
+
+For plugin developers, there is one main touchpoint for accessing the user identity: the
+`IdentityApi` exported by `@backstage/core-plugin-api` via the `identityApiRef`.
+
+The `IdentityApi` gives access to the signed-in user's identity in the frontend.
+It provides access to the user's entity reference, lightweight profile information, and
+a Backstage token that identifies the user when making authenticated calls within Backstage.
+
+When making calls to backend plugins, we recommend that the `FetchApi` is used, which
+is exported via the `fetchApiRef` from `@backstage/core-plugin-api`. The `FetchApi` will
+automatically include a Backstage token in the request, meaning there is no need
+to interact directly with the `IdentityApi`.
+
+### Accessing Third Party Resources
+
+A common pattern for talking to third-party services in Backstage is
+user-to-server requests, where short-lived OAuth Access Tokens are requested by
+plugins to authenticate calls to external services. These calls can be made
+either directly to the services or through a backend plugin or service.
+
+By relying on user-to-server calls, we keep the coupling between the frontend and
+backend low and provide a much lower barrier for plugins to make use of third
+party services. This is in comparison to, for example, a session-based system
+where access tokens are stored server-side. Such a solution would require a much
+deeper coupling between the auth backend plugin, its session storage, and other
+backend plugins or separate services. A goal of Backstage is to make it as easy
+as possible to create new plugins, and an auth solution based on user-to-server
+OAuth helps in that regard.
+
+The method with which frontend plugins request access to third-party services is
+through [Utility APIs](../api/utility-apis.md) for each service provider. These
+are all suffixed with `*AuthApiRef`, for example `githubAuthApiRef`. For a
+full list of providers, see the
+[@backstage/core-plugin-api](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.index.html#alertapiref) reference.
+
+## Custom Authentication Provider
+
+There are generic authentication providers for OAuth2 and SAML. These can reduce
+the amount of code needed to implement a custom authentication provider that
+adheres to these standards.
+
+Backstage uses [Passport](http://www.passportjs.org/) under the hood, which has
+a wide library of authentication strategies for different providers. See
+[Add authentication provider](add-auth-provider.md) for details on adding a new
+Passport-supported authentication method.
+
+## Custom ScmAuthApi Implementation
+
+The default `ScmAuthApi` provides integrations for `github`, `gitlab`, `azure` and `bitbucket` and is created by the following code in `packages/app/src/apis.ts`:
+
+```ts
+ScmAuth.createDefaultApiFactory();
+```
+
+If you require only a subset of these integrations, then you will need a custom implementation of the [`ScmAuthApi`](https://backstage.io/api/stable/interfaces/_backstage_integration-react.ScmAuthApi.html). It is an API used to authenticate different SCM systems generically, based on what resource is being accessed, and is used for example, by the Scaffolder (Software Templates) and Catalog Import plugins.
+
+The first step is to remove the code that creates the default providers.
+
+```ts title="packages/app/src/apis.ts"
+import {
+ ScmIntegrationsApi,
+ scmIntegrationsApiRef,
+ /* highlight-add-next-line */
+ ScmAuth,
+} from '@backstage/integration-react';
+
+export const apis: AnyApiFactory[] = [
+ /* highlight-add-next-line */
+ ScmAuth.createDefaultApiFactory(),
+ // ...
+];
+```
+
+Then replace it with something like this, which will create an `ApiFactory` with only a GitHub provider.
+
+```ts title="packages/app/src/apis.ts"
+export const apis: AnyApiFactory[] = [
+ createApiFactory({
+ api: scmAuthApiRef,
+ deps: {
+ githubAuthApi: githubAuthApiRef,
+ },
+ factory: ({ githubAuthApi }) =>
+ ScmAuth.merge(
+ ScmAuth.forGithub(githubAuthApi),
+ ),
+ });
+```
+
+If you use any custom authentication integrations, a new provider can be added to the `ApiFactory`.
+
+The first step is to create a new authentication ref, which follows the naming convention of `xxxAuthApiRef`. The example below is for a new GitHub enterprise integration which can be defined either inside the app itself if it's only used for this purpose or inside a common internal package for APIs, such as `@internal/apis`:
+
+```ts
+const gheAuthApiRef: ApiRef =
+ createApiRef({
+ id: 'internal.auth.ghe',
+ });
+```
+
+This new API ref will only work if you define an API factory for it. For example:
+
+```ts
+createApiFactory({
+ api: gheAuthApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ configApi: configApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
+ GithubAuth.create({
+ configApi,
+ discoveryApi,
+ oauthRequestApi,
+ provider: { id: 'ghe', title: 'GitHub Enterprise', icon: () => null },
+ defaultScopes: ['read:user'],
+ environment: configApi.getOptionalString('auth.environment'),
+ }),
+});
+```
+
+The new API ref is then used to add a new provider to the ApiFactory:
+
+```ts
+createApiFactory({
+ api: scmAuthApiRef,
+ deps: {
+ gheAuthApi: gheAuthApiRef,
+ githubAuthApi: githubAuthApiRef,
+ },
+ factory: ({ githubAuthApi, gheAuthApi }) =>
+ ScmAuth.merge(
+ ScmAuth.forGithub(githubAuthApi),
+ ScmAuth.forGithub(gheAuthApi, {
+ host: 'ghe.example.com',
+ }),
+ ),
+});
+```
+
+Finally, you also need to add and configure another provider to the `auth-backend` using the provider ID, which in this example is `ghe`:
+
+```ts
+import { providers } from '@backstage/plugin-auth-backend';
+
+// Add the following options to `createRouter` in packages/backend/src/plugins/auth.ts
+providerFactories: {
+ ghe: providers.github.create(),
+},
+```
+
+You can leverage the `authProvidersExtensionPoint` for this:
+
+```ts
+// your-auth-plugin-module.ts
+export const gheAuth = createBackendModule({
+ // This ID must be exactly "auth" because that's the plugin it targets
+ pluginId: 'auth',
+ // This ID must be unique, but can be anything
+ moduleId: 'ghe-auth-provider',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ providers: authProvidersExtensionPoint,
+ logger: coreServices.logger,
+ },
+ async init({ providers, logger }) {
+ providers.registerProvider({
+ // This ID must match the actual provider config, e.g. addressing
+ // auth.providers.ghe means that this must be "ghe".
+ providerId: 'ghe',
+ factory: createOAuthProviderFactory({
+ authenticator: githubAuthenticator,
+ signInResolverFactories: {
+ ...commonSignInResolvers,
+ },
+ }),
+ });
+ },
+ });
+ },
+});
+
+// backend index.ts
+backend.add(gheAuth);
+```
+
+## Configuring token issuers
+
+By default, the Backstage authentication backend generates and manages its own signing keys automatically for any issued
+Backstage tokens. However, these keys have a short lifetime and do not persist after instance restarts.
+
+Alternatively, users can provide their own public and private key files to sign issued tokens. This is beneficial in
+scenarios where the token verification implementation aggressively caches the list of keys, and doesn't attempt to fetch
+new ones even if they encounter an unknown key id. To enable this feature add the following configuration to your config
+file:
+
+```yaml
+auth:
+ keyStore:
+ provider: 'static'
+ static:
+ keys:
+ # Must be declared at least once and the first one will be used for signing
+ - keyId: 'primary'
+ publicKeyFile: /path/to/public.key
+ privateKeyFile: /path/to/private.key
+ algorithm: # Optional, algorithm used to generate the keys, defaults to ES256
+ # More keys can be added so with future key rotations caches already know about it
+ - keyId: ...
+```
+
+The private key should be stored in the PKCS#8 format. The public key should be stored in the SPKI format.
+You can generate the public/private key pair, using openssl and the ES256 algorithm by performing the following
+steps:
+
+Generate a private key using the ES256 algorithm
+
+```sh
+openssl ecparam -name prime256v1 -genkey -out private.ec.key
+```
+
+Convert it to PKCS#8 format
+
+```sh
+openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in private.ec.key -out private.key
+```
+
+Extract the public key
+
+```sh
+openssl ec -inform PEM -outform PEM -pubout -in private.key -out public.key
+```
diff --git a/docs/auth/index.md b/docs/auth/index.md
index fb85eb999b..8a2ce1db90 100644
--- a/docs/auth/index.md
+++ b/docs/auth/index.md
@@ -4,11 +4,11 @@ title: Authentication in Backstage
description: Introduction to authentication in Backstage
---
-The authentication system in Backstage serves two distinct purposes: sign-in and
-identification of users, as well as delegating access to third-party resources. It is possible to
-configure Backstage to have any number of authentication providers, but only
-one of these will typically be used for sign-in, with the rest being used to provide
-access to external resources.
+:::info
+This documentation is written for [the new frontend system](../frontend-system/index.md). If you are on the old frontend system you may want to read [its own article](./index--old.md) instead.
+:::
+
+The authentication system in Backstage serves two distinct purposes: sign-in and identification of users, as well as delegating access to third-party resources. It is possible to configure Backstage to have any number of authentication providers, but only one of these will typically be used for sign-in, with the rest being used to provide access to external resources.
:::note Note
@@ -37,13 +37,11 @@ Backstage comes with many common authentication providers in the core library:
- [OpenShift](openshift/provider.md)
- [VMware Cloud](vmware-cloud/provider.md)
-These built-in providers handle the authentication flow for a particular service, including required scopes, callbacks, etc. These providers are each added to a
-Backstage app in a similar way.
+These built-in providers handle the authentication flow for a particular service, including required scopes, callbacks, etc. These providers are each added to a Backstage app in a similar way.
## Configuring Authentication Providers
-Each built-in provider has a configuration block under the `auth` section of
-`app-config.yaml`. For example, the GitHub provider:
+Each built-in provider has a configuration block under the `auth` section of `app-config.yaml`. For example, the GitHub provider:
```yaml
auth:
@@ -55,66 +53,66 @@ auth:
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
```
-See the documentation for a particular provider to see what configuration is
-needed.
+See the documentation for a particular provider to see what configuration is needed.
-The `providers` key may have several authentication providers if multiple
-authentication methods are supported. Each provider may also have configuration
-for different authentication environments (development, production, etc). This
-allows a single auth backend to serve multiple environments, such as running a
-local frontend against a deployed backend. The provider configuration matching
-the local `auth.environment` setting will be selected.
+The `providers` key may have several authentication providers if multiple authentication methods are supported. Each provider may also have configuration for different authentication environments (development, production, etc). This allows a single auth backend to serve multiple environments, such as running a local frontend against a deployed backend. The provider configuration matching the local `auth.environment` setting will be selected.
## Sign-In Configuration
-Using an authentication provider for sign-in is something you need to configure
-both in the frontend app as well as the `auth` backend plugin. For information
-on how to configure the backend app, see [Sign-in Identities and Resolvers](./identity-resolver.md).
-The rest of this section will focus on how to configure sign-in for the frontend app.
+Using an authentication provider for sign-in is something you need to configure both in the frontend app as well as the `auth` backend plugin. For information on how to configure the backend app, see [Sign-in Identities and Resolvers](./identity-resolver.md). The rest of this section will focus on how to configure sign-in for the frontend app.
-Sign-in is configured by providing a custom `SignInPage` app component. It will be
-rendered before any other routes in the app and is responsible for providing the
-identity of the current user. The `SignInPage` can render any number of pages and
-components, or just blank space with logic running in the background. In the end, however, it must provide a valid Backstage user identity through the `onSignInSuccess`
-callback prop, at which point the rest of the app is rendered.
+Sign-in is configured by providing a custom `SignInPage` app component. It will be rendered before any other routes in the app and is responsible for providing the identity of the current user. The `SignInPage` can render any number of pages and components, or just blank space with logic running in the background. In the end, however, it must provide a valid Backstage user identity through the `onSignInSuccess` callback prop, at which point the rest of the app is rendered.
-If you want to, you can use the `SignInPage` component that is provided by `@backstage/core-components`,
-which takes either a `provider` or `providers` (array) prop of `SignInProviderConfig` definitions.
+If you want to, you can use the `SignInPage` component that is provided by `@backstage/core-components`, which takes either a `provider` or `providers` (array) prop of `SignInProviderConfig` definitions.
-The following example for GitHub shows the additions needed to `packages/app/src/App.tsx`,
-and can be adapted to any of the built-in providers:
+The following example for GitHub shows the additions needed to `packages/app/src/App.tsx`, and can be adapted to any of the built-in providers:
```tsx title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/frontend-defaults';
+import catalogPlugin from '@backstage/plugin-catalog/alpha';
+import { navModule } from './modules/nav';
+
/* highlight-add-start */
import { githubAuthApiRef } from '@backstage/core-plugin-api';
+import { SignInPageBlueprint } from '@backstage/plugin-app-react';
import { SignInPage } from '@backstage/core-components';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+
+const signInPage = SignInPageBlueprint.make({
+ params: {
+ loader: async () => props =>
+ (
+
+ ),
+ },
+});
/* highlight-add-end */
-const app = createApp({
- /* highlight-add-start */
- components: {
- SignInPage: props => (
-
- ),
- },
- /* highlight-add-end */
- // ..
+export default createApp({
+ features: [
+ catalogPlugin,
+ navModule,
+ /* highlight-add-start */
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [signInPage],
+ }),
+ /* highlight-add-end */
+ ],
});
```
:::note Note
-You can configure sign-in to use a redirect flow with no pop-up by adding
-`enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml`
+You can configure sign-in to use a redirect flow with no pop-up by adding `enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml`
:::
@@ -123,26 +121,44 @@ You can configure sign-in to use a redirect flow with no pop-up by adding
You can also use the `providers` prop to enable multiple sign-in methods, for example to allow guest access:
```tsx title="packages/app/src/App.tsx"
-const app = createApp({
- /* highlight-add-start */
- components: {
- SignInPage: props => (
-
- ),
+import { githubAuthApiRef } from '@backstage/core-plugin-api';
+import { SignInPageBlueprint } from '@backstage/plugin-app-react';
+import { SignInPage } from '@backstage/core-components';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+
+const signInPage = SignInPageBlueprint.make({
+ params: {
+ loader: async () => props =>
+ (
+
+ ),
},
- /* highlight-add-end */
- // ..
+});
+
+export default createApp({
+ features: [
+ catalogPlugin,
+ navModule,
+ /* highlight-add-start */
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [signInPage],
+ }),
+ /* highlight-add-end */
+ ],
});
```
@@ -156,10 +172,14 @@ import {
githubAuthApiRef,
useApi,
} from '@backstage/core-plugin-api';
+import { SignInPageBlueprint } from '@backstage/plugin-app-react';
+import { SignInPage } from '@backstage/core-components';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
-const app = createApp({
- components: {
- SignInPage: props => {
+const signInPage = SignInPageBlueprint.make({
+ params: {
+ /* highlight-add-start */
+ loader: async () => props => {
const configApi = useApi(configApiRef);
if (configApi.getString('auth.environment') === 'development') {
return (
@@ -177,47 +197,64 @@ const app = createApp({
/>
);
}
+
return (
);
},
+ /* highlight-add-end */
},
- // ..
+});
+
+export default createApp({
+ features: [
+ catalogPlugin,
+ navModule,
+ /* highlight-add-start */
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [signInPage],
+ }),
+ /* highlight-add-end */
+ ],
});
```
## Sign-In with Proxy Providers
-Some auth providers are so-called "proxy" providers, meaning they're meant to be used
-behind an authentication proxy. Examples of these are
-[Amazon Application Load Balancer](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md),
-[Azure EasyAuth](./microsoft/azure-easyauth.md),
-[Cloudflare Access](./cloudflare/provider.md),
-[Google Identity-Aware Proxy](./google/gcp-iap-auth.md)
-and [OAuth2 Proxy](./oauth2-proxy/provider.md).
+Some auth providers are so-called "proxy" providers, meaning they're meant to be used behind an authentication proxy. Examples of these are [Amazon Application Load Balancer](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md), [Azure EasyAuth](./microsoft/azure-easyauth.md), [Cloudflare Access](./cloudflare/provider.md), [Google Identity-Aware Proxy](./google/gcp-iap-auth.md) and [OAuth2 Proxy](./oauth2-proxy/provider.md).
-When using a proxy provider, you'll end up wanting to use a different sign-in page, as
-there is no need for further user interaction once you've signed in towards the proxy.
-All the sign-in page needs to do is call the `/refresh` endpoint of the auth providers
-to get the existing session, which is exactly what the `ProxiedSignInPage` does. The only
-thing you need to do to configure the `ProxiedSignInPage` is to pass the ID of the provider like this:
+When using a proxy provider, you'll end up wanting to use a different sign-in page, as there is no need for further user interaction once you've signed in towards the proxy. All the sign-in page needs to do is call the `/refresh` endpoint of the auth providers to get the existing session, which is exactly what the `ProxiedSignInPage` does. The only thing you need to do to configure the `ProxiedSignInPage` is to pass the ID of the provider like this:
```tsx title="packages/app/src/App.tsx"
+import { SignInPageBlueprint } from '@backstage/plugin-app-react';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
import { ProxiedSignInPage } from '@backstage/core-components';
-const app = createApp({
- components: {
- SignInPage: props => ,
+const signInPage = SignInPageBlueprint.make({
+ params: {
+ loader: async () => props =>
+ ,
},
- // ..
+});
+
+export default createApp({
+ features: [
+ catalogPlugin,
+ navModule,
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [signInPage],
+ }),
+ ],
});
```
@@ -249,18 +286,20 @@ Headers can also be returned in an async manner:
/>
```
-A downside of this method is that it can be cumbersome to set up for local development.
-As a workaround for this, it's possible to dynamically select the sign-in page based on
-what environment the app is running in and then use a different sign-in method for local
-development, if one is needed at all. Depending on the exact setup, one might choose to
-select the sign-in method based on the `process.env.NODE_ENV` environment variable,
-by checking the `hostname` of the current location, or by accessing the configuration API
-to read a configuration value. For example:
+A downside of this method is that it can be cumbersome to set up for local development. As a workaround for this, it's possible to dynamically select the sign-in page based on what environment the app is running in and then use a different sign-in method for local development, if one is needed at all. Depending on the exact setup, one might choose to select the sign-in method based on the `process.env.NODE_ENV` environment variable, by checking the `hostname` of the current location, or by accessing the configuration API to read a configuration value. For example:
```tsx title="packages/app/src/App.tsx"
-const app = createApp({
- components: {
- SignInPage: props => {
+import { configApiRef, useApi } from '@backstage/core-plugin-api';
+import { SignInPageBlueprint } from '@backstage/plugin-app-react';
+import { ProxiedSignInPage, SignInPage } from '@backstage/core-components';
+import {
+ createFrontendModule,
+ googleAuthApiRef,
+} from '@backstage/frontend-plugin-api';
+
+const signInPage = SignInPageBlueprint.make({
+ params: {
+ loader: async () => props => {
const configApi = useApi(configApiRef);
if (configApi.getString('auth.environment') === 'development') {
return (
@@ -275,93 +314,55 @@ const app = createApp({
/>
);
}
+
return ;
},
},
- // ..
+});
+
+export default createApp({
+ features: [
+ catalogPlugin,
+ navModule,
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [signInPage],
+ }),
+ ],
});
```
-When using multiple auth providers like this, it's important that you configure the different
-sign-in resolvers so that they resolve to the same identity regardless of the method used.
-
-## Scaffolder Configuration (Software Templates)
-
-If you want to use the authentication capabilities of the [Repository Picker](../features/software-templates/writing-templates.md#the-repository-picker) inside your software templates, you will need to configure the [`ScmAuthApi`](https://backstage.io/api/stable/interfaces/_backstage_integration-react.ScmAuthApi.html) alongside your authentication provider. It is an API used to authenticate towards different SCM systems in a generic way, based on what resource is being accessed.
-
-To set it up, you'll need to add an API factory entry to `packages/app/src/apis.ts`. The example below sets up the `ScmAuthApi` for an already configured GitLab authentication provider:
-
-```ts title="packages/app/src/apis.ts"
-createApiFactory({
- api: scmAuthApiRef,
- deps: {
- gitlabAuthApi: gitlabAuthApiRef,
- },
- factory: ({ gitlabAuthApi }) => ScmAuth.forGitlab(gitlabAuthApi),
-});
-```
-
-In case you are using a custom authentication providers, you might need to add a [custom `ScmAuthApi` implementation](./index.md#custom-scmauthapi-implementation).
+When using multiple auth providers like this, it's important that you configure the different sign-in resolvers so that they resolve to the same identity regardless of the method used.
## For Plugin Developers
-The Backstage frontend core APIs provide a set of Utility APIs for plugin developers
-to use, both to access the user identity as well as third-party resources.
+The Backstage frontend core APIs provide a set of Utility APIs for plugin developers to use, both to access the user identity as well as third-party resources.
### Identity for Plugin Developers
-For plugin developers, there is one main touchpoint for accessing the user identity: the
-`IdentityApi` exported by `@backstage/core-plugin-api` via the `identityApiRef`.
+For plugin developers, there is one main touchpoint for accessing the user identity: the `IdentityApi` exported by `@backstage/core-plugin-api` via the `identityApiRef`.
-The `IdentityApi` gives access to the signed-in user's identity in the frontend.
-It provides access to the user's entity reference, lightweight profile information, and
-a Backstage token that identifies the user when making authenticated calls within Backstage.
+The `IdentityApi` gives access to the signed-in user's identity in the frontend. It provides access to the user's entity reference, lightweight profile information, and a Backstage token that identifies the user when making authenticated calls within Backstage.
-When making calls to backend plugins, we recommend that the `FetchApi` is used, which
-is exported via the `fetchApiRef` from `@backstage/core-plugin-api`. The `FetchApi` will
-automatically include a Backstage token in the request, meaning there is no need
-to interact directly with the `IdentityApi`.
+When making calls to backend plugins, we recommend that the `FetchApi` is used, which is exported via the `fetchApiRef` from `@backstage/core-plugin-api`. The `FetchApi` will automatically include a Backstage token in the request, meaning there is no need to interact directly with the `IdentityApi`.
### Accessing Third Party Resources
-A common pattern for talking to third-party services in Backstage is
-user-to-server requests, where short-lived OAuth Access Tokens are requested by
-plugins to authenticate calls to external services. These calls can be made
-either directly to the services or through a backend plugin or service.
+A common pattern for talking to third-party services in Backstage is user-to-server requests, where short-lived OAuth Access Tokens are requested by plugins to authenticate calls to external services. These calls can be made either directly to the services or through a backend plugin or service.
-By relying on user-to-server calls, we keep the coupling between the frontend and
-backend low and provide a much lower barrier for plugins to make use of third
-party services. This is in comparison to, for example, a session-based system
-where access tokens are stored server-side. Such a solution would require a much
-deeper coupling between the auth backend plugin, its session storage, and other
-backend plugins or separate services. A goal of Backstage is to make it as easy
-as possible to create new plugins, and an auth solution based on user-to-server
-OAuth helps in that regard.
+By relying on user-to-server calls, we keep the coupling between the frontend and backend low and provide a much lower barrier for plugins to make use of third party services. This is in comparison to, for example, a session-based system where access tokens are stored server-side. Such a solution would require a much deeper coupling between the auth backend plugin, its session storage, and other backend plugins or separate services. A goal of Backstage is to make it as easy as possible to create new plugins, and an auth solution based on user-to-server OAuth helps in that regard.
-The method with which frontend plugins request access to third-party services is
-through [Utility APIs](../api/utility-apis.md) for each service provider. These
-are all suffixed with `*AuthApiRef`, for example `githubAuthApiRef`. For a
-full list of providers, see the
-[@backstage/core-plugin-api](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.index.html#alertapiref) reference.
+The method with which frontend plugins request access to third-party services is through [Utility APIs](../api/utility-apis.md) for each service provider. These are all suffixed with `*AuthApiRef`, for example `githubAuthApiRef`. For a full list of providers, see the [@backstage/core-plugin-api](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.index.html#alertapiref) reference.
## Custom Authentication Provider
-There are generic authentication providers for OAuth2 and SAML. These can reduce
-the amount of code needed to implement a custom authentication provider that
-adheres to these standards.
+There are generic authentication providers for OAuth2 and SAML. These can reduce the amount of code needed to implement a custom authentication provider that adheres to these standards.
-Backstage uses [Passport](http://www.passportjs.org/) under the hood, which has
-a wide library of authentication strategies for different providers. See
-[Add authentication provider](add-auth-provider.md) for details on adding a new
-Passport-supported authentication method.
+Backstage uses [Passport](http://www.passportjs.org/) under the hood, which has a wide library of authentication strategies for different providers. See [Add authentication provider](add-auth-provider.md) for details on adding a new Passport-supported authentication method.
## Custom ScmAuthApi Implementation
-The default `ScmAuthApi` provides integrations for `github`, `gitlab`, `azure` and `bitbucket` and is created by the following code in `packages/app/src/apis.ts`:
-
-```ts
-ScmAuth.createDefaultApiFactory();
-```
+The default `ScmAuthApi` provides integrations for `github`, `gitlab`, `azure` (Azure DevOps), `bitbucketServer` and `bitbucketCloud` and is created and registered automatically for you by the New Frontend System.
If you require only a subset of these integrations, then you will need a custom implementation of the [`ScmAuthApi`](https://backstage.io/api/stable/interfaces/_backstage_integration-react.ScmAuthApi.html). It is an API used to authenticate different SCM systems generically, based on what resource is being accessed, and is used for example, by the Scaffolder (Software Templates) and Catalog Import plugins.
@@ -499,13 +500,9 @@ backend.add(gheAuth);
## Configuring token issuers
-By default, the Backstage authentication backend generates and manages its own signing keys automatically for any issued
-Backstage tokens. However, these keys have a short lifetime and do not persist after instance restarts.
+By default, the Backstage authentication backend generates and manages its own signing keys automatically for any issued Backstage tokens. However, these keys have a short lifetime and do not persist after instance restarts.
-Alternatively, users can provide their own public and private key files to sign issued tokens. This is beneficial in
-scenarios where the token verification implementation aggressively caches the list of keys, and doesn't attempt to fetch
-new ones even if they encounter an unknown key id. To enable this feature add the following configuration to your config
-file:
+Alternatively, users can provide their own public and private key files to sign issued tokens. This is beneficial in scenarios where the token verification implementation aggressively caches the list of keys, and doesn't attempt to fetch new ones even if they encounter an unknown key id. To enable this feature add the following configuration to your config file:
```yaml
auth:
@@ -522,9 +519,7 @@ auth:
- keyId: ...
```
-The private key should be stored in the PKCS#8 format. The public key should be stored in the SPKI format.
-You can generate the public/private key pair, using openssl and the ES256 algorithm by performing the following
-steps:
+The private key should be stored in the PKCS#8 format. The public key should be stored in the SPKI format. You can generate the public/private key pair, using openssl and the ES256 algorithm by performing the following steps:
Generate a private key using the ES256 algorithm
diff --git a/docs/auth/oidc--old.md b/docs/auth/oidc--old.md
new file mode 100644
index 0000000000..a3df5cef3d
--- /dev/null
+++ b/docs/auth/oidc--old.md
@@ -0,0 +1,293 @@
+---
+id: oidc--old
+title: OIDC provider from scratch (Old Frontend System)
+description: This section shows how to enable and use the Backstage OIDC provider.
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./oidc.md) instead.
+::::
+
+This section shows how to enable and use the Backstage OIDC provider.
+
+## Summary
+
+OIDC is a protocol which has numerous implementations. It's likely that many of your users won't know what the OIDC **protocol** is, but they will recognise your OIDC **implementation**. Backstage supplies a generic `oidc` authorization strategy. You should re-badge this with the name and branding of your OIDC implementation, so that your users will recognise it on the Backstage sign-in page.
+
+For example, if your organization uses [Keycloak](https://www.keycloak.org), you would re-badge the OIDC provider as `Keycloak` and tell users to `Sign In using Keycloak`.
+
+## Steps
+
+The Backstage OIDC provider is not enabled by default. You need to manually enable the provider, and tell it which OIDC server you want to use.
+
+To enable the Backstage OIDC provider:
+
+- Create an API reference to identify the provider.
+- Create the API factory that will handle the authentication.
+- Add or reuse an auth provider so you can authenticate.
+- Add or reuse a resolver to handle the result from the authentication.
+- Configure the provider to access your 3rd party auth solution.
+- Add the provider to the Backstage sign-in page.
+
+For simplicity, we assume that you only have a single OIDC provider in your Backstage installation. (If you need to have multiple OIDC providers in Backstage, the steps will be different.)
+
+We'll explain each step more in detail next.
+
+### The API Reference
+
+An API reference exists to enable **Dependency Injection**. (See [Utility APIs][4] for an extended explanation.)
+
+In this example, we'll create the API ref directly in the `packages/app/src/apis.ts` file. It is not a requirement to put the ref in this file. Any location will do as long as it's available to be imported to where the API factory is, as well as easily accessible to the rest of the application so any package and plugin can inject the API instance when necessary.
+
+```ts
+export const keycloakAuthApiRef: ApiRef<
+ OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
+> = createApiRef({
+ id: 'auth.keycloak',
+});
+```
+
+The `id` of the API ref can be anything you want, as long as it doesn't conflict with other refs. Backstage recommends to use a custom name that references your custom provider.
+
+:::note TypeScript Note
+As we're exporting this API reference, as well as the TypeScript types, we need to
+be able to import this reference anywhere in the app. The types will tell TypeScript
+what instance we're getting from DI when injecting the API. In this case we are defining
+an API for authentication, so we tell TS that this instance complies with 4 API
+interfaces:
+
+- The OIDC API that will handle authentication.
+- Profile API for requesting user profile info from the auth provider in question.
+- Backstage identity API to handle and associate the user profile with backstage identity.
+- Session API, to handle the session the user will have while signed in.
+ :::
+
+### The API Factory (and auth provider)
+
+The Backstage API factories are part of the Backstage Dependency Injection system. The factory function runs once, when something in your Backstage app first attempts to use an instance of the API it provides. The instance is then cached by the DI system for subsequent lookups.
+
+Let's add a new API factory to the `apis` array in the `packages/app/src/apis.ts` file. We will tell it to use the OIDC auth provider internally.
+
+```ts title="packages/app/src/apis.ts"
+/* highlight-add-next-line */
+import { OAuth2 } from '@backstage/core-app-api';
+
+export const apis: AnyApiFactory[] = [
+ /* highlight-add-start */
+ createApiFactory({
+ api: keycloakAuthApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ configApi: configApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
+ // delegate auth to the OAuth2 strategy
+ OAuth2.create({
+ configApi,
+ discoveryApi,
+ oauthRequestApi,
+ provider: {
+ // this value MUST be 'oidc'
+ // it maps our Keycloak-branded sign-in provider onto Backstage's generic OIDC auth strategy
+ id: 'oidc',
+ title: 'Keycloak',
+ icon: () => null,
+ },
+ environment: configApi.getOptionalString('auth.environment'),
+ defaultScopes: ['openid', 'profile', 'email'],
+ popupOptions: {
+ // optional, used to customize sign-in window size
+ size: {
+ fullscreen: true,
+ },
+ /**
+ * or specify popup width and height
+ * size: {
+ width: 1000,
+ height: 1000,
+ }
+ */
+ },
+ }),
+ }),
+ /* highlight-add-end */
+];
+```
+
+### The Resolver
+
+Resolvers exist to map the user identity from the 3rd party (in this case Keycloak) to the Backstage user identity.
+
+The default OIDC provider has a choice of built-in resolvers, here is how you configure them:
+
+```yaml title="app-config.yaml"
+auth:
+ environment: development
+ providers:
+ oidc:
+ development:
+ # ...
+ signIn:
+ resolvers:
+ - resolver: emailMatchingUserEntityProfileEmail
+```
+
+If none of the built-in resolvers are suitable, you can alternatively write a custom resolver.
+
+First, install the OIDC provider module:
+
+```bash
+yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-oidc-provider
+```
+
+Then create a custom resolver as shown below:
+
+```ts title="in packages/backend/src/index.ts"
+/* highlight-add-start */
+import { createBackendModule } from '@backstage/backend-plugin-api';
+import {
+ authProvidersExtensionPoint,
+ createOAuthProviderFactory,
+} from '@backstage/plugin-auth-node';
+import { oidcAuthenticator } from '@backstage/plugin-auth-backend-module-oidc-provider';
+import {
+ stringifyEntityRef,
+ DEFAULT_NAMESPACE,
+} from '@backstage/catalog-model';
+
+const myAuthProviderModule = createBackendModule({
+ // This ID must be exactly "auth" because that's the plugin it targets
+ pluginId: 'auth',
+ // This ID must be unique, but can be anything
+ moduleId: 'keycloak-auth-provider',
+ register(reg) {
+ reg.registerInit({
+ deps: { providers: authProvidersExtensionPoint },
+ async init({ providers }) {
+ providers.registerProvider({
+ // This ID must match the actual provider config, e.g. addressing
+ // auth.providers.keycloak means that this must be "keycloak".
+ providerId: 'keycloak',
+ // Use createProxyAuthProviderFactory instead if it's one of the proxy
+ // based providers rather than an OAuth based one
+ factory: createOAuthProviderFactory({
+ // For more info about authenticators please see https://backstage.io/docs/auth/add-auth-provider/#adding-an-oauth-based-provider
+ authenticator: oidcAuthenticator,
+ async signInResolver(info, ctx) {
+ const userRef = stringifyEntityRef({
+ kind: 'User',
+ name: info.result.fullProfile.userinfo.sub,
+ namespace: DEFAULT_NAMESPACE,
+ });
+ return ctx.issueToken({
+ claims: {
+ sub: userRef, // The user's own identity
+ ent: [userRef], // A list of identities that the user claims ownership through
+ },
+ });
+ },
+ }),
+ });
+ },
+ });
+ },
+});
+/* highlight-add-end */
+//...
+backend.add(import('@backstage/plugin-auth-backend'));
+/* highlight-add-next-line */
+backend.add(myAuthProviderModule);
+//...
+```
+
+For a more detailed explanation about resolvers check the [Identity Resolver][1] page.
+
+### The Configuration
+
+We will now configure our Keycloak-branded OIDC Auth Provider in Backstage, so that it can talk to our Keycloak server.
+
+The first step is to register an OIDC client app for Backstage in your Keycloak server.
+
+Then we need to configure the provider. Based on the provider's code in `plugins/auth-backend/src/providers/oidc/provider.ts` we need the following parameters in the `app-config.yaml`:
+
+```yaml title="app-config.yaml"
+auth:
+ environment: development
+ session:
+ secret: ${AUTH_SESSION_SECRET}
+ providers:
+ oidc:
+ development:
+ metadataUrl: https://example.com/.well-known/openid-configuration
+ clientId: ${AUTH_OIDC_CLIENT_ID}
+ clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
+```
+
+Anything enclosed in `${}` can be replaced directly in the YAML, or provided as environment variables.
+
+#### Required Parameters
+
+These parameters must always be set.
+
+- `clientId`: Grab from the Overview page.
+- `clientSecret`: Can only be seen when creating the secret, if you lose it you'll need a
+ new secret.
+- `metadataUrl`: In Overview > Endpoints tab, grab OpenID Connect metadata document URL.
+
+The OIDC provider **also** requires the `auth.session.secret` to be set.
+
+#### Optional Parameters
+
+These parameters have implicit default values. Don't override them unless you know what you're doing.
+
+- `authorizationUrl` and `tokenUrl`: Open the `metadataUrl` in a browser, that json will
+ hold these 2 urls somewhere in there.
+- `tokenEndpointAuthMethod`
+- `tokenSignedResponseAlg`
+- `scope`: Only used if we didn't specify `defaultScopes` in the provider's factory,
+ basically the same thing.
+- `prompt`: Recommended to use `auto` so the browser will request sign-in to the IDP if the
+ user has no active session.
+- `sessionDuration`: Lifespan of the user session.
+- `startUrlSearchParams`: This is a dictionary of search (query) parameters for the OIDC
+ authorization start URL. Don't define it unless you want to change the identity
+ provider's behavior. (For example, you could set the `organization` parameter to guide
+ users towards a particular sign-in option that your organization prefers.) **Note:** the
+ start URL is controlled by the browser, so this feature is only for improving the
+ Backstage user experience.
+
+:::note Config Reloading
+Backstage does not yet support hot reloading of auth provider configuration. Any changes to this YAML file require a restart of Backstage.
+:::
+
+### The Sign-In Page
+
+The last step is to add the provider to the sign-in page, so users can sign in with your new provider.
+
+If you are using the standard Backstage [`SignInPage`][3] component, you can add it to the `providers` array like this:
+
+```ts title="in packages/app/src/identityProviders.ts"
+export const providers = [
+ // other providers...
+ {
+ id: 'keycloak-auth-provider',
+ title: 'Keycloak',
+ message: 'Sign In using Keycloak',
+ apiRef: keycloakAuthApiRef,
+ },
+];
+```
+
+:::note Note
+These steps apply to most auth providers. The main
+difference between providers will be the contents of the API factory, the code
+in the Auth Provider Factory, the resolver, and the different variables each provider
+needs in the YAML config or env variables.
+:::
+
+[1]: https://backstage.io/docs/auth/identity-resolver
+[3]: https://backstage.io/docs/auth/#sign-in-configuration
+[4]: https://backstage.io/docs/api/utility-apis
diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md
index a55719e564..77af72d0fa 100644
--- a/docs/auth/oidc.md
+++ b/docs/auth/oidc.md
@@ -4,12 +4,12 @@ title: OIDC provider from scratch
description: This section shows how to enable and use the Backstage OIDC provider.
---
-:::info
-This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage
-[version 1.24](../releases/v1.24.0.md). If you are still on the old backend
-system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/oidc--old.md)
-instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
-:::
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./oidc--old.md)
+instead.
+::::
This section shows how to enable and use the Backstage OIDC provider.
@@ -21,112 +21,98 @@ For example, if your organization uses [Keycloak](https://www.keycloak.org), you
## Steps
-The Backstage OIDC provider is not enabled by default. You need to manually enable the provider, and tell it which OIDC server you want to use.
+The Backstage OIDC provider is not enabled by default. You need to:
-To enable the Backstage OIDC provider:
+1. Install and configure the OIDC backend module.
+1. Configure the provider in `app-config.yaml`.
+1. Configure a sign-in resolver.
+1. Add the provider to the Backstage sign-in page.
-- Create an API reference to identify the provider.
-- Create the API factory that will handle the authentication.
-- Add or reuse an auth provider so you can authenticate.
-- Add or reuse a resolver to handle the result from the authentication.
-- Configure the provider to access your 3rd party auth solution.
-- Add the provider to the Backstage sign-in page.
+For simplicity, we assume that you only have a single OIDC provider in your Backstage installation.
-For simplicity, we assume that you only have a single OIDC provider in your Backstage installation. (If you need to have multiple OIDC providers in Backstage, the steps will be different.)
+We'll explain each step in detail next.
-We'll explain each step more in detail next.
+### Backend Installation
-### The API Reference
+To add the OIDC provider to the backend, install the provider module:
-An API reference exists to enable **Dependency Injection**. (See [Utility APIs][4] for an extended explanation.)
-
-In this example, we'll create the API ref directly in the `packages/app/src/apis.ts` file. It is not a requirement to put the ref in this file. Any location will do as long as it's available to be imported to where the API factory is, as well as easily accessible to the rest of the application so any package and plugin can inject the API instance when necessary.
-
-```ts
-export const keycloakAuthApiRef: ApiRef<
- OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
-> = createApiRef({
- id: 'auth.keycloak',
-});
+```bash title="from your Backstage root directory"
+yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-oidc-provider
```
-The `id` of the API ref can be anything you want, as long as it doesn't conflict with other refs. Backstage recommends to use a custom name that references your custom provider.
+Then add it to your backend in `packages/backend/src/index.ts`:
-:::note TypeScript Note
-As we're exporting this API reference, as well as the TypeScript types, we need to
-be able to import this reference anywhere in the app. The types will tell TypeScript
-what instance we're getting from DI when injecting the API. In this case we are defining
-an API for authentication, so we tell TS that this instance complies with 4 API
-interfaces:
-
-- The OIDC API that will handle authentication.
-- Profile API for requesting user profile info from the auth provider in question.
-- Backstage identity API to handle and associate the user profile with backstage identity.
-- Session API, to handle the session the user will have while signed in.
- :::
-
-### The API Factory (and auth provider)
-
-The Backstage API factories are part of the Backstage Dependency Injection system. The factory function runs once, when something in your Backstage app first attempts to use an instance of the API it provides. The instance is then cached by the DI system for subsequent lookups.
-
-Let's add a new API factory to the `apis` array in the `packages/app/src/apis.ts` file. We will tell it to use the OIDC auth provider internally.
-
-```ts title="packages/app/src/apis.ts"
+```ts title="packages/backend/src/index.ts"
+backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-next-line */
-import { OAuth2 } from '@backstage/core-app-api';
-
-export const apis: AnyApiFactory[] = [
- /* highlight-add-start */
- createApiFactory({
- api: keycloakAuthApiRef,
- deps: {
- discoveryApi: discoveryApiRef,
- oauthRequestApi: oauthRequestApiRef,
- configApi: configApiRef,
- },
- factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
- // delegate auth to the OAuth2 strategy
- OAuth2.create({
- configApi,
- discoveryApi,
- oauthRequestApi,
- provider: {
- // this value MUST be 'oidc'
- // it maps our Keycloak-branded sign-in provider onto Backstage's generic OIDC auth strategy
- id: 'oidc',
- title: 'Keycloak',
- icon: () => null,
- },
- environment: configApi.getOptionalString('auth.environment'),
- defaultScopes: ['openid', 'profile', 'email'],
- popupOptions: {
- // optional, used to customize sign-in window size
- size: {
- fullscreen: true,
- },
- /**
- * or specify popup width and height
- * size: {
- width: 1000,
- height: 1000,
- }
- */
- },
- }),
- }),
- /* highlight-add-end */
-];
+backend.add(import('@backstage/plugin-auth-backend-module-oidc-provider'));
```
-### The Resolver
+### Configuration
-Resolvers exist to map the user identity from the 3rd party (in this case Keycloak) to the Backstage user identity.
-
-The default OIDC provider has a choice of built-in resolvers, here is how you configure them:
+Register an OIDC client application for Backstage with your OIDC provider (for example, Keycloak). Then add the provider configuration to your `app-config.yaml`:
```yaml title="app-config.yaml"
auth:
environment: development
+ session:
+ secret: ${AUTH_SESSION_SECRET}
+ providers:
+ oidc:
+ development:
+ metadataUrl: https://example.com/.well-known/openid-configuration
+ clientId: ${AUTH_OIDC_CLIENT_ID}
+ clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
+ signIn:
+ resolvers:
+ - resolver: emailMatchingUserEntityProfileEmail
+```
+
+Anything enclosed in `${}` can be replaced directly in the YAML, or provided as environment variables.
+
+#### Required parameters
+
+These parameters must always be set.
+
+- `clientId`: The client ID from your OIDC provider.
+- `clientSecret`: The client secret tied to the client ID.
+- `metadataUrl`: The OpenID Connect metadata document URL, for example `https://example.com/.well-known/openid-configuration`.
+
+The OIDC provider **also** requires `auth.session.secret` to be set.
+
+#### Optional parameters
+
+These parameters have implicit default values. Don't override them unless you know what you're doing.
+
+- `callbackUrl`: Override the default callback URL used by the OIDC provider.
+- `timeout`: Override the default timeout for calls to the OIDC provider.
+- `tokenEndpointAuthMethod`
+- `tokenSignedResponseAlg`
+- `additionalScopes`: Requests additional scopes on top of the default `openid profile
+email` scopes. Do not configure `scope` directly, as the OIDC provider will reject
+ configurations that include it.
+- `prompt`: Recommended to use `auto` so the browser will request sign-in to the identity
+ provider if the user has no active session.
+- `sessionDuration`: Lifespan of the user session.
+- `startUrlSearchParams`: A dictionary of search (query) parameters for the OIDC
+ authorization start URL. Don't define it unless you want to change the identity
+ provider's behavior. (For example, you could set the `organization` parameter to guide
+ users towards a particular sign-in option that your organization prefers.) **Note:** the
+ start URL is controlled by the browser, so this feature is only for improving the
+ Backstage user experience.
+
+:::note Config Reloading
+Backstage does not yet support hot reloading of auth provider configuration. Any changes to this YAML file require a restart of Backstage.
+:::
+
+### Resolvers
+
+Resolvers map the user identity from the OIDC provider to the Backstage user identity.
+
+The default OIDC provider has a choice of built-in resolvers that you configure in `app-config.yaml` under `auth.providers.oidc..signIn.resolvers`:
+
+```yaml title="app-config.yaml"
+auth:
providers:
oidc:
development:
@@ -136,17 +122,11 @@ auth:
- resolver: emailMatchingUserEntityProfileEmail
```
-If none of the built-in resolvers are suitable, you can alternatively write a custom resolver.
+If none of the built-in resolvers are suitable, you can write a custom resolver. See [Building Custom Resolvers](./identity-resolver.md#building-custom-resolvers) for details.
-First, install the OIDC provider module:
+The following example shows a custom resolver that maps users by their OIDC `sub` claim:
-```bash
-yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-oidc-provider
-```
-
-Then create a custom resolver as shown below:
-
-```ts title="in packages/backend/src/index.ts"
+```ts title="packages/backend/src/index.ts"
/* highlight-add-start */
import { createBackendModule } from '@backstage/backend-plugin-api';
import {
@@ -160,22 +140,15 @@ import {
} from '@backstage/catalog-model';
const myAuthProviderModule = createBackendModule({
- // This ID must be exactly "auth" because that's the plugin it targets
pluginId: 'auth',
- // This ID must be unique, but can be anything
- moduleId: 'keycloak-auth-provider',
+ moduleId: 'custom-oidc-provider',
register(reg) {
reg.registerInit({
deps: { providers: authProvidersExtensionPoint },
async init({ providers }) {
providers.registerProvider({
- // This ID must match the actual provider config, e.g. addressing
- // auth.providers.keycloak means that this must be "keycloak".
- providerId: 'keycloak',
- // Use createProxyAuthProviderFactory instead if it's one of the proxy
- // based providers rather than an OAuth based one
+ providerId: 'oidc',
factory: createOAuthProviderFactory({
- // For more info about authenticators please see https://backstage.io/docs/auth/add-auth-provider/#adding-an-oauth-based-provider
authenticator: oidcAuthenticator,
async signInResolver(info, ctx) {
const userRef = stringifyEntityRef({
@@ -185,8 +158,8 @@ const myAuthProviderModule = createBackendModule({
});
return ctx.issueToken({
claims: {
- sub: userRef, // The user's own identity
- ent: [userRef], // A list of identities that the user claims ownership through
+ sub: userRef,
+ ent: [userRef],
},
});
},
@@ -197,98 +170,111 @@ const myAuthProviderModule = createBackendModule({
},
});
/* highlight-add-end */
-//...
+
+// ...
backend.add(import('@backstage/plugin-auth-backend'));
-/* highlight-add-next-line */
+/* highlight-add-start */
+// Use the custom module instead of the default OIDC module
backend.add(myAuthProviderModule);
-//...
+/* highlight-add-end */
```
-For a more detailed explanation about resolvers check the [Identity Resolver][1] page.
+For a more detailed explanation about resolvers, see the [Identity Resolver](./identity-resolver.md) page.
-### The Configuration
+### Adding the provider to the sign-in page
-We will now configure our Keycloak-branded OIDC Auth Provider in Backstage, so that it can talk to our Keycloak server.
+The last step is to configure the sign-in page in the frontend to use your OIDC provider.
-The first step is to register an OIDC client app for Backstage in your Keycloak server.
+Since OIDC does not have a built-in auth API ref in `@backstage/core-plugin-api`, you need to create a custom one and wire it up using a `SignInPageBlueprint`. The following example shows how to set this up for a Keycloak-branded OIDC provider in `packages/app/src/App.tsx`:
-Then we need to configure the provider. Based on the provider's code in `plugins/auth-backend/src/providers/oidc/provider.ts` we need the following parameters in the `app-config.yaml`:
+```tsx title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/frontend-defaults';
+/* highlight-add-start */
+import {
+ OpenIdConnectApi,
+ ProfileInfoApi,
+ BackstageIdentityApi,
+ SessionApi,
+} from '@backstage/core-plugin-api';
+import { OAuth2 } from '@backstage/core-app-api';
+import { SignInPageBlueprint } from '@backstage/plugin-app-react';
+import { SignInPage } from '@backstage/core-components';
+import {
+ createApiRef,
+ createFrontendModule,
+ configApiRef,
+ discoveryApiRef,
+ oauthRequestApiRef,
+ ApiBlueprint,
+} from '@backstage/frontend-plugin-api';
-```yaml title="app-config.yaml"
-auth:
- environment: development
- session:
- secret: ${AUTH_SESSION_SECRET}
- providers:
- oidc:
- development:
- metadataUrl: https://example.com/.well-known/openid-configuration
- clientId: ${AUTH_OIDC_CLIENT_ID}
- clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
-```
+const keycloakAuthApiRef = createApiRef<
+ OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
+>().with({
+ id: 'auth.keycloak',
+});
-Anything enclosed in `${}` can be replaced directly in the YAML, or provided as environment variables.
+const keycloakAuthApi = ApiBlueprint.make({
+ name: 'keycloak',
+ params: defineParams =>
+ defineParams({
+ api: keycloakAuthApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ configApi: configApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
+ OAuth2.create({
+ configApi,
+ discoveryApi,
+ oauthRequestApi,
+ environment: configApi.getOptionalString('auth.environment'),
+ provider: {
+ id: 'oidc',
+ title: 'Keycloak',
+ icon: () => null,
+ },
+ defaultScopes: ['openid', 'profile', 'email'],
+ }),
+ }),
+});
-#### Required Parameters
-
-These parameters must always be set.
-
-- `clientId`: Grab from the Overview page.
-- `clientSecret`: Can only be seen when creating the secret, if you lose it you'll need a
- new secret.
-- `metadataUrl`: In Overview > Endpoints tab, grab OpenID Connect metadata document URL.
-
-The OIDC provider **also** requires the `auth.session.secret` to be set.
-
-#### Optional Parameters
-
-These parameters have implicit default values. Don't override them unless you know what you're doing.
-
-- `authorizationUrl` and `tokenUrl`: Open the `metadataUrl` in a browser, that json will
- hold these 2 urls somewhere in there.
-- `tokenEndpointAuthMethod`
-- `tokenSignedResponseAlg`
-- `scope`: Only used if we didn't specify `defaultScopes` in the provider's factory,
- basically the same thing.
-- `prompt`: Recommended to use `auto` so the browser will request sign-in to the IDP if the
- user has no active session.
-- `sessionDuration`: Lifespan of the user session.
-- `startUrlSearchParams`: This is a dictionary of search (query) parameters for the OIDC
- authorization start URL. Don't define it unless you want to change the identity
- provider's behavior. (For example, you could set the `organization` parameter to guide
- users towards a particular sign-in option that your organization prefers.) **Note:** the
- start URL is controlled by the browser, so this feature is only for improving the
- Backstage user experience.
-
-:::note Config Reloading
-Backstage does not yet support hot reloading of auth provider configuration. Any changes to this YAML file require a restart of Backstage.
-:::
-
-### The Sign-In Page
-
-The last step is to add the provider to the sign-in page, so users can sign in with your new provider.
-
-If you are using the standard Backstage [`SignInPage`][3] component, you can just add it to the `providers` array like this:
-
-```ts title="in packages/app/src/identityProviders.ts"
-export const providers = [
- // other providers...
- {
- id: 'keycloak-auth-provider',
- title: 'Keycloak',
- message: 'Sign In using Keycloak',
- apiRef: keycloakAuthApiRef,
+const signInPage = SignInPageBlueprint.make({
+ params: {
+ loader: async () => props =>
+ (
+
+ ),
},
-];
+});
+/* highlight-add-end */
+
+export default createApp({
+ features: [
+ // ...
+ /* highlight-add-start */
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [keycloakAuthApi, signInPage],
+ }),
+ /* highlight-add-end */
+ ],
+});
```
+The `id` of the API ref (e.g. `'auth.keycloak'`) and the `id` used in the `SignInPage` provider configuration (e.g. `'keycloak-auth-provider'`) can be customized. However, the `provider.id` passed to `OAuth2.create` must remain `'oidc'` to match Backstage's generic OIDC auth strategy on the backend.
+
:::note Note
-These steps apply to most auth providers. The main
-difference between providers will be the contents of the API factory, the code
-in the Auth Provider Factory, the resolver, and the different variables each provider
-needs in the YAML config or env variables.
+You can configure sign-in to use a redirect flow with no pop-up by adding `enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml`.
:::
-[1]: https://backstage.io/docs/auth/identity-resolver
-[3]: https://backstage.io/docs/auth/#sign-in-configuration
-[4]: https://backstage.io/docs/api/utility-apis
+For more information about sign-in configuration, see [Sign-in Configuration](./index.md#sign-in-configuration).
diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md
index 1423fa92f7..6655c76b29 100644
--- a/docs/auth/service-to-service-auth.md
+++ b/docs/auth/service-to-service-auth.md
@@ -4,13 +4,6 @@ title: Service to Service Auth
description: This section describes service to service authentication works, both internally within Backstage plugins and when external callers want to make requests.
---
-:::info
-This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage
-[version 1.24](../releases/v1.24.0.md). If you are still on the old backend
-system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/service-to-service-auth--old.md)
-instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
-:::
-
This article describes how _service-to-service auth_ works in Backstage, both
between Backstage backend plugins and for external callers who want to make
requests to them. This is in contrast to _user and user-to-service auth_ which
@@ -20,7 +13,7 @@ Each section describes one distinct type of auth flow.
## Standard Plugin-to-Plugin Auth
-Backstage plugins that use the new backend system and handle credentials using
+Backstage plugins that use the backend system and handle credentials using
the `auth` and `httpAuth` service APIs are secure by default, without requiring
any configuration. They generate self-signed tokens automatically for making
requests to other Backstage backend plugins, and the receivers use the caller's
@@ -228,96 +221,6 @@ calling Backstage plugins:
Authorization: Bearer eyJhbG...
```
-## Legacy Tokens
-
-Plugins and backends that are _not_ on the new backend system use a legacy token
-flow, where shared static secrets in your app-config are used for signing and
-verification. If you are on the new backend system and are not using legacy
-plugins using the compatibility wrapper, you can skip this section.
-
-### Configuration (legacy)
-
-In local development, there is no need to configure anything for this auth
-method. But in production, you must configure at least one legacy type external
-access method:
-
-```yaml title="in e.g. app-config.production.yaml"
-backend:
- auth:
- externalAccess:
- - type: legacy
- options:
- secret: my-secret-key-catalog
- subject: legacy-catalog
- - type: legacy
- options:
- secret: my-secret-key-scaffolder
- subject: legacy-scaffolder
-```
-
-The old style keys config is also supported as an alternative, but please
-consider using the new style above instead:
-
-```yaml title="in e.g. app-config.production.yaml"
-backend:
- auth:
- keys:
- - secret: my-secret-key-catalog
- - secret: my-secret-key-scaffolder
-```
-
-The secrets must be any base64-encoded random data, but for security reasons
-should be sufficiently long so as not to be easy to guess by brute force. You
-can for example generate them on the command line:
-
-```shell
-node -p 'require("crypto").randomBytes(24).toString("base64")'
-```
-
-The subjects must be strings without whitespace. They are used for identifying
-each caller, and become part of the credentials object that request recipient
-plugins get.
-
-In both of the examples we showed two secrets being specified, but the minimum
-is one. The order is significant: the first one is always used for signing of
-outgoing requests to other backend plugins, while all of the keys are used for
-verification. This is useful if you want to be able to have unique keys per
-deployment if you are using split deployments of Backstage. Then each deployment
-lists its own signing secret at the top, and only adds the secrets for those
-other deployments that it wants to permit to call it.
-
-For most organizations, we recommend leaving it at just one key and
-[migrating](../backend-system/building-backends/08-migrating.md) to the new
-backend system as soon as possible instead of experimenting with multiple legacy
-secrets.
-
-### External Callers (legacy)
-
-For legacy Backstage backend plugins, the above configuration is enough. But
-external callers who wish to make requests using this flow must generate tokens
-according to the following rules.
-
-The token must be a JWT with a `HS256` signature, using the raw base64 decoded
-value of the configured key as the secret. It must also have the following
-payload:
-
-- `sub`: the exact string "backstage-server"
-- `exp`: one hour from the time it was generated, in epoch seconds
-
-:::note Note
-
-The JWT must encode the `alg` header as a protected header, such as with
-[setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader).
-
-:::
-
-The caller then passes along the JWT token with requests in the `Authorization`
-header:
-
-```yaml
-Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW
-```
-
## Access Restrictions
Each `externalAccess` entry may optionally have an `accessRestrictions` key,
diff --git a/docs/backend-system/architecture/01-index.md b/docs/backend-system/architecture/01-index.md
index 293a46b7ca..6e1012dce0 100644
--- a/docs/backend-system/architecture/01-index.md
+++ b/docs/backend-system/architecture/01-index.md
@@ -2,12 +2,12 @@
id: index
title: Backend System Architecture
sidebar_label: Overview
-description: The structure and architecture of the new Backend System and its component parts
+description: The structure and architecture of the Backend System and its component parts
---
## Building Blocks
-This section introduces the high-level building blocks upon which this new
+This section introduces the high-level building blocks upon which this
system is built. These are all concepts that exist in our current system in one
way or another, but they have all been lifted up to be first class concerns in
the new system. Regardless of whether you are setting up your own backstage
diff --git a/docs/backend-system/building-backends/01-index.md b/docs/backend-system/building-backends/01-index.md
index a38c1d44e5..3fb9ea60a9 100644
--- a/docs/backend-system/building-backends/01-index.md
+++ b/docs/backend-system/building-backends/01-index.md
@@ -2,7 +2,7 @@
id: index
title: Building Backends
sidebar_label: Overview
-description: Building backends using the new backend system
+description: Building backends using the backend system
---
:::note Note
diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md
index 3aa06216b4..fdbac8e65f 100644
--- a/docs/backend-system/building-backends/08-migrating.md
+++ b/docs/backend-system/building-backends/08-migrating.md
@@ -1340,105 +1340,3 @@ const backend = createBackend();
backend.add(import('@backstage/plugin-kubernetes-backend'));
/* highlight-add-end */
```
-
-### The Plugins in Backstage Repo
-
-The vast majority of the backend plugins that currently live in the Backstage Repo have been migrated and their respective `README`s have details on how they should be installed using the New Backend System.
-
-| Package | Role | Migrated | Uses Alpha Export | Link to `README` |
-| ------------------------------------------------------------------ | --------------------- | -------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
-| @backstage-community/plugin-adr-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/adr/plugins/adr-backend/README.md) |
-| @backstage-community/plugin-airbrake-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/airbrake/plugins/airbrake-backend/README.md) |
-| @backstage/plugin-app-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/app-backend/README.md) |
-| @backstage/plugin-auth-backend | backend-plugin | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/README.md) |
-| @backstage/plugin-auth-backend-module-atlassian-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-atlassian-provider/README.md) |
-| @backstage/plugin-auth-backend-module-aws-alb-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-aws-alb-provider/README.md) |
-| @backstage/plugin-auth-backend-module-gcp-iap-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-gcp-iap-provider/README.md) |
-| @backstage/plugin-auth-backend-module-github-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-github-provider/README.md) |
-| @backstage/plugin-auth-backend-module-gitlab-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-gitlab-provider/README.md) |
-| @backstage/plugin-auth-backend-module-google-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-google-provider/README.md) |
-| @backstage/plugin-auth-backend-module-guest-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-guest-provider/README.md) |
-| @backstage/plugin-auth-backend-module-microsoft-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-microsoft-provider/README.md) |
-| @backstage/plugin-auth-backend-module-oauth2-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-oauth2-provider/README.md) |
-| @backstage/plugin-auth-backend-module-oauth2-proxy-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-oauth2-proxy-provider/README.md) |
-| @backstage/plugin-auth-backend-module-oidc-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-oidc-provider/README.md) |
-| @backstage/plugin-auth-backend-module-okta-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-okta-provider/README.md) |
-| @backstage/plugin-auth-backend-module-pinniped-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-pinniped-provider/README.md) |
-| @backstage/plugin-auth-backend-module-vmware-cloud-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-vmware-cloud-provider/README.md) |
-| @backstage-community/plugin-azure-devops-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/azure-devops/plugins/azure-devops-backend/README.md) |
-| @backstage-community/plugin-azure-sites-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/azure-sites/plugins/azure-sites-backend/README.md) |
-| @backstage-community/plugin-badges-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/badges/plugins/badges-backend/README.md) |
-| @backstage-community/plugin-bazaar-backend | backend-plugin | true | true | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/bazaar/plugins/bazaar-backend/README.md) |
-| @backstage/plugin-catalog-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/README.md) |
-| @backstage/plugin-catalog-backend-module-aws | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-aws/README.md) |
-| @backstage/plugin-catalog-backend-module-azure | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-azure/README.md) |
-| @backstage/plugin-catalog-backend-module-backstage-openapi | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-backstage-openapi/README.md) |
-| @backstage/plugin-catalog-backend-module-bitbucket-cloud | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-bitbucket-cloud/README.md) |
-| @backstage/plugin-catalog-backend-module-bitbucket-server | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-bitbucket-server/README.md) |
-| @backstage/plugin-catalog-backend-module-gcp | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-gcp/README.md) |
-| @backstage/plugin-catalog-backend-module-gerrit | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-gerrit/README.md) |
-| @backstage/plugin-catalog-backend-module-github | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-github/README.md) |
-| @backstage/plugin-catalog-backend-module-github-org | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-github-org/README.md) |
-| @backstage/plugin-catalog-backend-module-gitlab | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-gitlab/README.md) |
-| @backstage/plugin-catalog-backend-module-incremental-ingestion | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-incremental-ingestion/README.md) |
-| @backstage/plugin-catalog-backend-module-ldap | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/README.md) |
-| @backstage/plugin-catalog-backend-module-msgraph | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-msgraph/README.md) |
-| @backstage/plugin-catalog-backend-module-openapi | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-openapi/README.md) |
-| @backstage/plugin-catalog-backend-module-puppetdb | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-puppetdb/README.md) |
-| @backstage/plugin-catalog-backend-module-scaffolder-entity-model | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-scaffolder-entity-model/README.md) |
-| @backstage/plugin-catalog-backend-module-unprocessed | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-unprocessed/README.md) |
-| @backstage-community/plugin-code-coverage-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/code-coverage/plugins/code-coverage-backend/README.md) |
-| @backstage/plugin-devtools-backend | backend-plugin | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/devtools-backend/README.md) |
-| @backstage-community/plugin-entity-feedback-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/entity-feedback/plugins/entity-feedback-backend/README.md) |
-| @backstage/plugin-events-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend/README.md) |
-| @backstage/plugin-events-backend-module-aws-sqs | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-aws-sqs/README.md) |
-| @backstage/plugin-events-backend-module-azure | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-azure/README.md) |
-| @backstage/plugin-events-backend-module-bitbucket-cloud | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-bitbucket-cloud/README.md) |
-| @backstage/plugin-events-backend-module-gerrit | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gerrit/README.md) |
-| @backstage/plugin-events-backend-module-github | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-github/README.md) |
-| @backstage/plugin-events-backend-module-gitlab | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md) |
-| @internal/plugin-todo-list-backend | backend-plugin | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-backend/README.md) |
-| @backstage-community/plugin-explore-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/explore/plugins/explore-backend/README.md) |
-| @backstage-community/plugin-jenkins-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/jenkins/plugins/jenkins-backend/README.md) |
-| @backstage-community/plugin-kafka-backend | backend-plugin | true | true | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/kafka/plugins/kafka-backend/README.md) |
-| @backstage/plugin-kubernetes-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/kubernetes-backend/README.md) |
-| @backstage-community/plugin-lighthouse-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/lighthouse/plugins/lighthouse-backend/README.md) |
-| @backstage-community/plugin-linguist-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/linguist/plugins/linguist-backend/README.md) |
-| @backstage-community/plugin-nomad-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/nomad/plugins/nomad-backend/README.md) |
-| @backstage/plugin-notifications-backend | backend-plugin | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/notifications-backend/README.md) |
-| @backstage-community/plugin-periskop-backend | backend-plugin | true | true | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/periskop/plugins/periskop-backend/README.md) |
-| @backstage/plugin-permission-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/permission-backend/README.md) |
-| @backstage/plugin-permission-backend-module-allow-all-policy | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/permission-backend-module-policy-allow-all/README.md) |
-| @backstage-community/plugin-playlist-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/playlist/plugins/playlist-backend/README.md) |
-| @backstage/plugin-proxy-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/proxy-backend/README.md) |
-| @backstage-community/plugin-rollbar-backend | backend-plugin | | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/rollbar/plugins/rollbar-backend/README.md) |
-| @backstage/plugin-scaffolder-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/README.md) |
-| @backstage/plugin-scaffolder-backend-module-azure | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-azure/README.md) |
-| @backstage/plugin-scaffolder-backend-module-bitbucket | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-bitbucket/README.md) |
-| @backstage/plugin-scaffolder-backend-module-bitbucket-cloud | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-bitbucket-cloud/README.md) |
-| @backstage/plugin-scaffolder-backend-module-bitbucket-server | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-bitbucket-server/README.md) |
-| @backstage/plugin-scaffolder-backend-module-confluence-to-markdown | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-confluence-to-markdown/README.md) |
-| @backstage/plugin-scaffolder-backend-module-cookiecutter | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-cookiecutter/README.md) |
-| @backstage/plugin-scaffolder-backend-module-gerrit | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-gerrit/README.md) |
-| @backstage/plugin-scaffolder-backend-module-gitea | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-gitea/README.md) |
-| @backstage/plugin-scaffolder-backend-module-github | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/README.md) |
-| @backstage/plugin-scaffolder-backend-module-gitlab | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-gitlab/README.md) |
-| @backstage/plugin-scaffolder-backend-module-rails | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-rails/README.md) |
-| @backstage/plugin-scaffolder-backend-module-sentry | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-sentry/README.md) |
-| @backstage/plugin-scaffolder-backend-module-yeoman | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-yeoman/README.md) |
-| @backstage/plugin-search-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend/README.md) |
-| @backstage/plugin-search-backend-module-catalog | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-catalog/README.md) |
-| @backstage/plugin-search-backend-module-elasticsearch | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-elasticsearch/README.md) |
-| @backstage/plugin-search-backend-module-explore | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-explore/README.md) |
-| @backstage/plugin-search-backend-module-pg | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-pg/README.md) |
-| @backstage/plugin-search-backend-module-stack-overflow-collator | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-stack-overflow-collator/README.md) |
-| @backstage/plugin-search-backend-module-techdocs | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-techdocs/README.md) |
-| @backstage/plugin-signals-backend | backend-plugin | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/signals-backend/README.md) |
-| @backstage-community/plugin-sonarqube-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/sonarqube/plugins/sonarqube-backend/README.md) |
-| @backstage-community/plugin-stack-overflow-backend | backend-plugin | | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/stack-overflow/plugins/stack-overflow-backend/README.md) |
-| @backstage-community/plugin-tech-insights-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/tech-insights/plugins/tech-insights-backend/README.md) |
-| @backstage-community/plugin-tech-insights-backend-module-jsonfc | backend-plugin-module | true | | [README](https://github.com/backstage/community-plugins/blob/main/workspaces/tech-insights/plugins/tech-insights-backend-module-jsonfc/README.md) |
-| @backstage/plugin-techdocs-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/README.md) |
-| @backstage-community/plugin-todo-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/todo/plugins/todo-backend/README.md) |
-| @backstage/plugin-user-settings-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/user-settings-backend/README.md) |
-| @backstage-community/plugin-vault-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/vault/plugins/vault-backend/README.md) |
diff --git a/docs/backend-system/building-plugins-and-modules/01-index.md b/docs/backend-system/building-plugins-and-modules/01-index.md
index 53bf4af8ef..3b0696aa8e 100644
--- a/docs/backend-system/building-plugins-and-modules/01-index.md
+++ b/docs/backend-system/building-plugins-and-modules/01-index.md
@@ -2,16 +2,9 @@
id: index
title: Building Backend Plugins and Modules
sidebar_label: Overview
-description: Building backend plugins and modules using the new backend system
+description: Building backend plugins and modules using the backend system
---
-:::note Note
-
-If you have an existing backend and/or backend plugins that are not yet
-using the new backend system, see [migrating](./08-migrating.md).
-
-:::
-
This section covers how to build your own backend [plugins](../architecture/04-plugins.md) and
[modules](../architecture/06-modules.md). They are sometimes collectively referred to as
backend _features_, and are the building blocks that adopters add to their
diff --git a/docs/backend-system/core-services/01-index.md b/docs/backend-system/core-services/01-index.md
index 50ebea0e4f..7da7fc1efa 100644
--- a/docs/backend-system/core-services/01-index.md
+++ b/docs/backend-system/core-services/01-index.md
@@ -24,6 +24,7 @@ import { coreServices } from '@backstage/backend-plugin-api';
- [Identity Service](./identity.md) - Deprecated user authentication service, use the [Auth Service](./auth.md) instead.
- [Lifecycle Service](./lifecycle.md) - Registration of plugin startup and shutdown lifecycle hooks.
- [Logger Service](./logger.md) - Plugin-level logging.
+- [Metrics Service](./metrics.md) - Plugin-scoped metrics instrumentation (alpha).
- [Permissions Service](./permissions.md) - Permission system integration for authorization of user actions.
- [Plugin Metadata Service](./plugin-metadata.md) - Built-in service for accessing metadata about the current plugin.
- [Root Config Service](./root-config.md) - Access to static configuration.
diff --git a/docs/backend-system/core-services/actions-registry.md b/docs/backend-system/core-services/actions-registry.md
index 9e493e6bb2..e8e2dc7bb2 100644
--- a/docs/backend-system/core-services/actions-registry.md
+++ b/docs/backend-system/core-services/actions-registry.md
@@ -25,6 +25,7 @@ Each action registered with the service must conform to the `ActionsRegistryActi
### Optional Properties
+- **`visibilityPermission`:** A `BasicPermission` that controls visibility and access to the action through the permissions framework. See [Permissions](#permissions) below.
- **`attributes`:** Object containing behavioral flags:
- **`destructive`:** Boolean indicating if the action modifies or deletes data
- **`idempotent`:** Boolean indicating if running the action multiple times produces the same result
@@ -157,6 +158,43 @@ export const myPlugin = createBackendPlugin({
});
```
+## Permissions
+
+Actions can optionally declare a `visibilityPermission` to control visibility and access through the Backstage permissions framework. The `visibilityPermission` must be a `BasicPermission` (not a resource permission). When set, the action is only visible in listings and accessible by callers who are authorized.
+
+When accessed via the Actions Service or the `/.backstage/actions/v1/...` HTTP endpoints, actions that are denied by the permission policy are filtered from list results and return a `404 Not Found` on invocation, as if they don't exist.
+
+Permissions declared on actions are automatically registered with the `PermissionsRegistryService` so they appear in the permission policy system.
+
+### Adding a Permission to an Action
+
+```typescript
+import { createPermission } from '@backstage/plugin-permission-common';
+
+// Define a permission for your action
+const myDeletePermission = createPermission({
+ name: 'my-plugin.actions.deleteEntity',
+ attributes: { action: 'delete' },
+});
+
+actionsRegistry.register({
+ name: 'delete-entity',
+ title: 'Delete Entity',
+ description: 'Removes an entity from the catalog',
+ visibilityPermission: myDeletePermission,
+ schema: {
+ input: z => z.object({ entityRef: z.string() }),
+ output: z => z.object({ deleted: z.boolean() }),
+ },
+ action: async ({ input }) => {
+ // action logic
+ return { output: { deleted: true } };
+ },
+});
+```
+
+Actions without a `visibilityPermission` field remain visible and accessible by all callers, preserving backwards compatibility.
+
## Best Practices
### Naming Conventions
diff --git a/docs/backend-system/core-services/actions.md b/docs/backend-system/core-services/actions.md
index 02f8b65591..fc2eabc0f3 100644
--- a/docs/backend-system/core-services/actions.md
+++ b/docs/backend-system/core-services/actions.md
@@ -65,6 +65,10 @@ backend:
- 'scaffolder.internal.*'
```
+### Permissions
+
+Actions registered with a `visibilityPermission` field are automatically checked against the permissions framework. When listing actions, any actions denied by the active permission policy are filtered out of the results. When invoking a denied action, a `404 Not Found` error is returned. See the [Actions Registry Permissions](./actions-registry.md#permissions) documentation for how to configure permissions on actions.
+
## Using the Service
### Listing Available Actions
diff --git a/docs/backend-system/core-services/auditor.md b/docs/backend-system/core-services/auditor.md
index fe4ecd196c..2d0864eeca 100644
--- a/docs/backend-system/core-services/auditor.md
+++ b/docs/backend-system/core-services/auditor.md
@@ -146,8 +146,8 @@ To clarify how to utilize the Auditor feature effectively, we recommend explorin
- **Code Implementation Example (createRouter.ts):**
- The [`createRouter.ts`](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/src/service/createRouter.ts) file within the Catalog Backend showcases a practical integration of the `AuditorService` within a Backstage backend plugin.
- Specifically, the lines that demonstrate the creation of an audit event. This includes setting critical parameters such as `eventId` and `severityLevel`, as well as incorporating relevant metadata like `queryType` and `entityRef`.
-- **Documentation Example (README.md):**
- - The "Audit Events" section of the Catalog Backend's [`README.md`](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/README.md#audit-events) provides a well-structured example of documenting emitted audit events.
+- **Documentation Example:**
+ - The "Audit Events" section of the Software Catalog [Audit Documentation](../../features/software-catalog/audit-events.md) provides a well-structured example of documenting emitted audit events.
- It illustrates how to detail various `eventId` values and their corresponding `meta` fields (e.g., `queryType`, `actionType`) for different plugin operations.
These examples provide both a code-level demonstration and a documentation guideline for effectively utilizing the `AuditorService` to manage audit events within your Backstage plugins.
diff --git a/docs/backend-system/core-services/metrics.md b/docs/backend-system/core-services/metrics.md
new file mode 100644
index 0000000000..6396143fad
--- /dev/null
+++ b/docs/backend-system/core-services/metrics.md
@@ -0,0 +1,175 @@
+---
+id: metrics
+title: Metrics Service (alpha)
+sidebar_label: Metrics Service (alpha)
+description: Documentation for the Metrics service
+---
+
+The Metrics Service provides a unified interface for emitting application-level metrics from Backstage backend plugins. It wraps the [OpenTelemetry](https://opentelemetry.io/) Meter API and automatically scopes each plugin's metrics using the OpenTelemetry [Instrumentation Scope](https://opentelemetry.io/docs/concepts/instrumentation-scope/), so telemetry backends can identify which plugin produced each metric.
+
+:::note
+This service is currently in **alpha** and is imported from `@backstage/backend-plugin-api/alpha`. The API may change in future releases.
+:::
+
+## Setting up OpenTelemetry
+
+The Metrics Service does **not** configure the OpenTelemetry SDK itself. You are responsible for initializing the OpenTelemetry Node SDK — including exporters, resource attributes, and views — before starting the Backstage backend. Follow the [tutorial](../../tutorials/setup-opentelemetry.md) for more information.
+
+## How it Relates to OpenTelemetry Auto-Instrumentation
+
+The Metrics Service **complements** auto-instrumentation rather than replacing it. Auto-instrumentation captures infrastructure-level signals like HTTP request counts and durations automatically. The Metrics Service is for **application-level metrics** that only your plugin can provide — things like entities processed, tasks completed, or active sessions.
+
+```ts
+// Auto-instrumentation provides automatically:
+// http.server.request.duration{method="GET", route="/catalog/entities"}
+
+// MetricsService provides manually:
+const processed = metrics.createCounter('entities.processed.total', {
+ description: 'Total entities processed during refresh',
+});
+processed.add(entities.length, { operation: 'refresh' });
+```
+
+## Using the Service
+
+Since the Metrics Service is an alpha API, the service reference is imported from `@backstage/backend-plugin-api/alpha` instead of `coreServices`.
+
+```ts
+import { createBackendPlugin } from '@backstage/backend-plugin-api';
+import { metricsServiceRef } from '@backstage/backend-plugin-api/alpha';
+
+createBackendPlugin({
+ pluginId: 'todos',
+ register(env) {
+ env.registerInit({
+ deps: {
+ metrics: metricsServiceRef,
+ },
+ async init({ metrics }) {
+ const todoCount = metrics.createCounter('todos.total', {
+ description: 'Total number of todos',
+ });
+
+ // Later, when adding a todo:
+ todoCount.add(1, { 'todo.category': 'personal' });
+ },
+ });
+ },
+});
+```
+
+## Instrument Types
+
+The service provides both synchronous and observable (asynchronous) instrument types, matching the OpenTelemetry specification.
+
+### Synchronous Instruments
+
+Synchronous instruments are used inline where the measurement occurs.
+
+| Method | Description | Example Use Case |
+| --------------------- | ------------------------------------------------------ | ---------------------------------- |
+| `createCounter` | Monotonically increasing sum (non-negative increments) | Total requests, entities processed |
+| `createUpDownCounter` | Sum that can increase or decrease | Active connections, queue depth |
+| `createHistogram` | Distribution of values (e.g. durations, sizes) | Request latency, payload sizes |
+| `createGauge` | Point-in-time value | CPU usage, memory utilization |
+
+```ts
+const counter = metrics.createCounter('todos.completed.total', {
+ description: 'Total todos completed',
+});
+counter.add(1, { 'todo.status': 'completed' });
+
+const histogram = metrics.createHistogram('todo.duration', {
+ description: 'Time spent processing a todo',
+ unit: 'seconds',
+ advice: { explicitBucketBoundaries: [0.01, 0.05, 0.1, 0.5, 1, 5] },
+});
+histogram.record(durationInSeconds, {
+ 'todo.category': 'personal',
+ 'todo.status': 'completed',
+});
+
+const upDown = metrics.createUpDownCounter('todos.in_flight', {
+ description: 'Number of todos currently in flight',
+});
+upDown.add(1);
+// ... later
+upDown.add(-1);
+```
+
+### Observable Instruments
+
+Observable instruments use callbacks that are invoked when a metric collection occurs. This is useful for metrics that are expensive to compute or that come from external sources like databases.
+
+| Method | Description | Example Use Case |
+| ------------------------------- | --------------------------------------------------- | -------------------------------------- |
+| `createObservableCounter` | Monotonically increasing sum, reported via callback | Total items ingested from external API |
+| `createObservableUpDownCounter` | Sum that can go up or down, reported via callback | Connection pool size |
+| `createObservableGauge` | Point-in-time value, reported via callback | Row counts, cache hit ratios |
+
+```ts
+const entityCount = metrics.createObservableGauge('catalog.entities.count', {
+ description: 'Total amount of entities in the catalog',
+});
+
+entityCount.addCallback(async gauge => {
+ const results = await getEntityCountsByKind();
+ for (const { kind, count } of results) {
+ gauge.observe(count, { kind });
+ }
+});
+```
+
+## Metric Options
+
+All `create*` methods accept an optional `MetricOptions` object:
+
+| Property | Type | Description |
+| ------------- | -------- | -------------------------------------------------------------------- |
+| `description` | `string` | Human-readable description of the metric |
+| `unit` | `string` | Unit of measurement (e.g. `'seconds'`, `'{entity}'`, `'bytes'`) |
+| `advice` | `object` | Aggregation hints, such as `explicitBucketBoundaries` for histograms |
+
+## Type-Safe Attributes
+
+Metric instruments accept a generic type parameter that constrains the attributes passed to `add`, `record`, or `observe`:
+
+```ts
+interface TodoAttributes {
+ 'todo.category': string;
+ 'todo.status': 'completed' | 'in_progress' | 'blocked';
+}
+
+const completed = metrics.createCounter(
+ 'todos.completed.total',
+ { description: 'Total todos completed' },
+);
+
+// Type-safe attributes are enforced
+completed.add(1, { 'todo.category': 'personal', 'todo.status': 'completed' });
+```
+
+## Advanced Configuration
+
+The service reads optional configuration from `app-config.yaml` under `backend.metrics.plugin..meter`. This lets operators override the OpenTelemetry Instrumentation Scope for a specific plugin without code changes.
+
+:::tip
+Each plugin automatically receives a meter named `backstage-plugin-`. You typically won't need to configure it.
+:::
+
+```yaml
+backend:
+ metrics:
+ plugin:
+ catalog:
+ meter:
+ name: 'custom-catalog-meter'
+ version: '2.0.0'
+ schemaUrl: 'https://example.com/schema'
+```
+
+| Property | Type | Default | Description |
+| ----------- | -------- | ----------------------------- | ------------------------------- |
+| `name` | `string` | `backstage-plugin-` | Name of the OpenTelemetry meter |
+| `version` | `string` | — | Version string for the meter |
+| `schemaUrl` | `string` | — | Schema URL for the meter |
diff --git a/docs/backend-system/core-services/scheduler.md b/docs/backend-system/core-services/scheduler.md
index f61f77cc7e..97a807982e 100644
--- a/docs/backend-system/core-services/scheduler.md
+++ b/docs/backend-system/core-services/scheduler.md
@@ -45,7 +45,21 @@ The scheduler exposes a REST API on top of each plugin's base URL, that lets you
### `GET /.backstage/scheduler/v1/tasks`
-Lists all tasks that the given plugin registered at startup, and their current states. The response shape is as follows:
+Lists all tasks that the given plugin registered at startup, and their current states.
+
+For example, to list all scheduled tasks for the Catalog plugin:
+
+```sh
+curl 'https:///api/catalog/.backstage/scheduler/v1/tasks'
+```
+
+You can try this out on the Backstage demo instance:
+
+```sh
+curl 'https://demo.backstage.io/api/catalog/.backstage/scheduler/v1/tasks'
+```
+
+The response shape is as follows:
```json
{
@@ -118,6 +132,18 @@ The `workerState` shape is as follows:
Schedules the given task ID for immediate execution, instead of waiting for its
next scheduled time slot to arrive.
+For example, to trigger a specific Catalog task:
+
+```bash
+curl -X POST "https:///api/catalog/.backstage/scheduler/v1/tasks/InternalOpenApiDocumentationProvider:refresh/trigger"
+```
+
+A working example would be:
+
+```bash
+curl -X POST "https://demo.backstage.io/api/catalog/.backstage/scheduler/v1/tasks/InternalOpenApiDocumentationProvider:refresh/trigger"
+```
+
Note that there can still be an additional small delay before a worker discovers
that the task is due and actually picks it up. This typically takes less than a
second, but it can vary.
@@ -130,6 +156,39 @@ Responds with
- `404 Not Found` if there was no such registered task for this plugin
- `409 Conflict` if the task was already in a running state
+### `POST /.backstage/scheduler/v1/tasks//cancel`
+
+Cancels the running task with the given task ID.
+
+Note that `` must be URL-encoded so that it remains a single path segment
+in the URL (for example by using `encodeURIComponent` in JavaScript or standard
+percent-encoding).
+
+For example, to cancel a specific Catalog task:
+
+```bash
+curl -X POST "https:///api/catalog/.backstage/scheduler/v1/tasks/InternalOpenApiDocumentationProvider%3Arefresh/cancel"
+```
+
+A working example would be:
+
+```bash
+curl -X POST "https://demo.backstage.io/api/catalog/.backstage/scheduler/v1/tasks/InternalOpenApiDocumentationProvider%3Arefresh/cancel"
+```
+
+Note that there can still be an additional small delay before a worker discovers
+that the task is cancelled. This can take up to a few seconds. Note also that it
+is up to the task implementation to properly react to the abort signal that was
+passed into it.
+
+The request has no body.
+
+Responds with
+
+- `200 OK` if successful
+- `404 Not Found` if there was no such registered task for this plugin
+- `409 Conflict` if the task was not in a running state
+
## Testing
The `@backstage/backend-test-utils` package provides `mockServices.scheduler`, which provides a mocked implementation of the scheduler service that can be used in tests. This mocked implementation is used by default in `startTestBackend`, and it will immediately run any registered tasks on startup as long as they're not configured to run manually or with an initial delay.
diff --git a/docs/conf/reading.md b/docs/conf/reading.md
index 905f081f1d..2fa74fa1d2 100644
--- a/docs/conf/reading.md
+++ b/docs/conf/reading.md
@@ -115,7 +115,7 @@ example `getString`. These will throw an error if there is no value available.
## Accessing ConfigApi in Frontend Plugins
-The [ConfigApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ConfigApi.html) in the frontend is a
+The [ConfigApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ConfigApi.html) in the frontend is a
[UtilityApi](../api/utility-apis.md). It's accessible as usual via the
`configApiRef` exported from `@backstage/core-plugin-api`:
diff --git a/docs/conf/user-interface/index.md b/docs/conf/user-interface/index.md
index cb53ebe81c..107fde3d57 100644
--- a/docs/conf/user-interface/index.md
+++ b/docs/conf/user-interface/index.md
@@ -156,7 +156,6 @@ These colors form a layered neutral scale for your application backgrounds. `--b
| Token Name | Description |
| ----------------------------- | ------------------------------------------------------------ |
| `--bui-bg-app` | The base background color of your Backstage instance. |
-| `--bui-bg-popover` | The background color used for popovers, tooltips, and menus. |
| `--bui-bg-neutral-1` | First elevated layer. Use for cards, dialogs, and panels. |
| `--bui-bg-neutral-1-hover` | Hover state for elements on neutral-1. |
| `--bui-bg-neutral-1-pressed` | Pressed state for elements on neutral-1. |
diff --git a/docs/contribute/doc-style-guide.md b/docs/contribute/doc-style-guide.md
new file mode 100644
index 0000000000..901df0c55c
--- /dev/null
+++ b/docs/contribute/doc-style-guide.md
@@ -0,0 +1,334 @@
+---
+id: doc-style-guide
+title: Documentation Style Guide
+description: Writing style guidelines for Backstage documentation
+---
+
+This page gives writing style guidelines for the Backstage documentation.
+These are guidelines, not rules. Use your best judgment, and feel free to
+propose changes to this document in a pull request.
+
+For additional information on contributing to the docs, see the
+[Contributors Guide](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#documentation-guidelines).
+
+## Language
+
+Backstage documentation uses U.S. English spelling and grammar.
+
+The documentation site is built with [Docusaurus](https://docusaurus.io/) and
+uses standard Markdown with some Docusaurus-specific features like
+[admonitions](https://docusaurus.io/docs/markdown-features/admonitions).
+
+## Tone
+
+Backstage documentation should feel approachable, professional, and helpful.
+Write as if you are a knowledgeable colleague explaining something to a peer
+who is new to the topic but not new to software development.
+
+- **Be friendly but not casual.** Avoid slang, humor that might not translate,
+ or overly enthusiastic language. A warm, straightforward tone works best.
+- **Be respectful of the reader's time.** Get to the point. If a concept needs
+ a longer explanation, provide it, but don't pad content with filler.
+- **Be encouraging without being patronizing.** Assume the reader is competent.
+ Avoid phrases like "as everyone knows" or "obviously."
+- **Be inclusive.** Use gender-neutral language. Avoid cultural references that
+ might not be understood globally. Write for an international audience.
+- **Be precise.** Use the correct technical terminology for Backstage concepts.
+ When introducing a new term, define it on first use.
+
+## Documentation formatting standards
+
+### Use bold for user interface elements
+
+| Do | Don't |
+| :---------------- | :-------------- |
+| Click **Fork**. | Click "Fork". |
+| Select **Other**. | Select "Other". |
+
+### Use italics to define or introduce new terms
+
+| Do | Don't |
+| :------------------------------------------ | :-------------------------------------------- |
+| A _plugin_ is a modular extension ... | A "plugin" is a modular extension ... |
+| These components form the _backend system_. | These components form the **backend system**. |
+
+### Use code style for filenames, directories, and paths
+
+| Do | Don't |
+| :--------------------------------------------- | :------------------------------------------- |
+| Open the `app-config.yaml` file. | Open the app-config.yaml file. |
+| Go to the `/plugins` directory. | Go to the /plugins directory. |
+| Open the `packages/backend/src/index.ts` file. | Open the packages/backend/src/index.ts file. |
+
+### Use code style for inline code and commands
+
+| Do | Don't |
+| :------------------------------------------------------------------------- | :------------------------------------------------------------------- |
+| The `yarn start` command starts the app. | The "yarn start" command starts the app. |
+| Run `yarn install` from the project root. | Run "yarn install" from the project root. |
+| Use single backticks to enclose inline code, for example `const x = true`. | Use bold or italics for inline code, for example **const x = true**. |
+| Enclose code samples with triple backticks. | Enclose code samples with any other syntax. |
+| Use meaningful variable names that have context. | Use variable names such as `foo`, `bar`, and `baz`. |
+
+### Use code style for package names and API references
+
+
+
+| Do | Don't |
+| :--------------------------------------------------------------- | :--------------------------------------------------------------- |
+| Install the `@backstage/core-plugin-api` package. | Install the @backstage/core-plugin-api package. |
+| The `createRouter` function creates a new router. | The createRouter function creates a new router. |
+| Set the value of the `backend.baseUrl` field in the config file. | Set the value of the "backend.baseUrl" field in the config file. |
+
+
+
+### Use angle brackets for placeholders
+
+Use angle brackets for placeholders. Tell the reader what a placeholder
+represents. For example:
+
+```shell
+yarn workspace @backstage/plugin- start
+```
+
+### Use the international standard for punctuation inside quotes
+
+| Do | Don't |
+| :---------------------------------------------- | :---------------------------------------------- |
+| Events are recorded with an associated "stage". | Events are recorded with an associated "stage." |
+| The copy is called a "fork". | The copy is called a "fork." |
+
+## Code snippet formatting
+
+### Don't include the command prompt
+
+| Do | Don't |
+| :------------- | :--------------- |
+| `yarn install` | `$ yarn install` |
+
+### Separate commands from output
+
+Verify that the app is running:
+
+```shell
+yarn start
+```
+
+The output is similar to this:
+
+```console
+[0] webpack output is served from /
+[1] Loaded config from app-config.yaml
+```
+
+### Use appropriate language tags for code blocks
+
+Use the correct language identifier for fenced code blocks: `ts` or `typescript`
+for TypeScript, `yaml` for YAML configuration, `shell` for shell commands,
+`console` for command output, and `diff` for changesets.
+
+## Admonitions
+
+Backstage documentation uses
+[Docusaurus admonitions](https://docusaurus.io/docs/markdown-features/admonitions)
+for callouts. Use `:::note`, `:::tip`, `:::caution`, and `:::danger` as
+appropriate.
+
+```markdown
+:::note
+You can use _Markdown_ inside admonitions.
+:::
+```
+
+:::note
+You can use _Markdown_ inside admonitions.
+:::
+
+Use `:::note` for supplementary information, `:::tip` for helpful suggestions,
+`:::caution` for potential pitfalls, and `:::danger` for actions that could
+cause data loss or security issues.
+
+Keep admonitions short and focused. Each admonition should contain a single,
+clear point. If you find yourself writing multiple paragraphs inside an
+admonition, consider whether the content belongs in the main text instead.
+
+Avoid stacking multiple admonitions in a row. Too many callouts on a page
+dilute their impact and make the content harder to read. If a section has
+more than two admonitions, restructure the content so that most of the
+information is in regular paragraphs.
+
+## Markdown elements
+
+### Line breaks
+
+Use a single newline to separate block-level content like headings, lists,
+images, code blocks, and others. Manually wrap paragraphs in the Markdown
+source at a reasonable line length. This makes diffs easier to review and
+helps downstream localization.
+
+### Headings and titles
+
+| Do | Don't |
+| :------------------------------------------------------------------------ | :--------------------------------------------------------------------- |
+| Use ordered headings to provide a meaningful outline of your content. | Use headings level 4 through 6 unless absolutely necessary. |
+| Use sentence case for headings. For example, **Extend the catalog model** | Use title case for headings. For example, **Extend The Catalog Model** |
+| Use pound signs (`#`) for headings. | Use underlines (`---` or `===`) for headings. |
+
+### Paragraphs
+
+| Do | Don't |
+| :---------------------------------------------------------- | :----------------------------------- |
+| Try to keep paragraphs under 6 sentences. | Write long, unbroken walls of text. |
+| Use three hyphens (`---`) for horizontal rules when needed. | Use horizontal rules for decoration. |
+
+### Links
+
+| Do | Don't |
+| :------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------- |
+| Write hyperlinks with descriptive text. For example: See [Getting Started](../getting-started/index.md) for details. | Use ambiguous link text. For example: See [here](../getting-started/index.md) for details. |
+| Write Markdown-style links: `[link text](./index.md)`. | Write HTML-style links or create links that open in new tabs. |
+
+### Lists
+
+- End each item in a list with a period if one or more items in the list are
+ complete sentences. For consistency, either all items or none should be
+ complete sentences.
+- Use the number one (`1.`) for ordered lists.
+- Use (`-`) for unordered lists.
+- Leave a blank line after each list.
+- Indent nested lists with two spaces.
+
+### Tables
+
+Use Markdown tables with clear column headers. Keep table content concise.
+For large amounts of structured data, consider using a list or separate
+subsections instead.
+
+## Content best practices
+
+### Spell out acronyms on first use
+
+When using an acronym, spell it out in full on first use followed by the
+acronym in parentheses. After that, you can use the acronym alone.
+
+| Do | Don't |
+| :------------------------------------------------------ | :------------------------------------ |
+| Software Development Kit (SDK) | SDK (without ever defining it) |
+| Role-Based Access Control (RBAC) ... configure RBAC ... | RBAC ... configure RBAC ... |
+| Hyper Text Markup Language (HTML) | HTML (on first use without expansion) |
+
+Exception: Universally understood acronyms like URL, API, or HTML do not
+need to be spelled out if they are common knowledge for the target audience
+of software developers.
+
+### Use present tense
+
+| Do | Don't |
+| :---------------------------------- | :-------------------------------------- |
+| This command starts a proxy. | This command will start a proxy. |
+| The plugin provides a catalog page. | The plugin will provide a catalog page. |
+
+Exception: Use future or past tense if it is required to convey the correct
+meaning.
+
+### Use active voice
+
+| Do | Don't |
+| :--------------------------------------- | :------------------------------------------ |
+| You can explore the API using a browser. | The API can be explored using a browser. |
+| The YAML file specifies the base URL. | The base URL is specified in the YAML file. |
+
+Exception: Use passive voice if active voice leads to an awkward construction.
+
+### Use simple and direct language
+
+| Do | Don't |
+| :-------------------------- | :------------------------------------------------------- |
+| To create a plugin, ... | In order to create a plugin, ... |
+| See the configuration file. | Please see the configuration file. |
+| View the catalog entities. | With this next command, we'll view the catalog entities. |
+
+### Address the reader as "you"
+
+| Do | Don't |
+| :--------------------------------------- | :-------------------------------------- |
+| You can create a plugin by ... | We'll create a plugin by ... |
+| In the preceding output, you can see ... | In the preceding output, we can see ... |
+
+### Avoid Latin phrases
+
+Prefer English terms over Latin abbreviations.
+
+| Do | Don't |
+| :--------------- | :-------- |
+| For example, ... | e.g., ... |
+| That is, ... | i.e., ... |
+
+Exception: Use "etc." for and so on.
+
+## Patterns to avoid
+
+### Be intentional with "we"
+
+"We" is fine in tutorials and walkthroughs where it means "you and I, working
+through this together." Avoid "we" when it's unclear whether it refers to the
+Backstage project, the maintainers, or the reader's team.
+
+| Ok | Avoid |
+| :------------------------------------------ | :--------------------------------- |
+| Next, we need to add the backend package. | We provide a new feature ... |
+| We can verify this by running `yarn start`. | In version 1.25, we have added ... |
+
+### Avoid jargon and idioms
+
+Some readers speak English as a second language. Avoid jargon and idioms to
+help them understand better.
+
+| Do | Don't |
+| :------------------- | :-------------------- |
+| Internally, ... | Under the hood, ... |
+| Create a new plugin. | Spin up a new plugin. |
+
+### Avoid statements about the future
+
+Avoid making promises or giving hints about the future. If you need to talk
+about an experimental feature, clearly label it as such.
+
+### Avoid statements that will soon be out of date
+
+Avoid words like "currently" and "new." A feature that is new today might not
+be considered new in a few months.
+
+| Do | Don't |
+| :------------------------------ | :---------------------------------- |
+| In version 1.25, ... | In the current version, ... |
+| The search feature provides ... | The new search feature provides ... |
+
+### Avoid words that assume a specific level of understanding
+
+Avoid words such as "just", "simply", "easy", "easily", or "simple". These
+words do not add value.
+
+| Do | Don't |
+| :------------------------- | :------------------------------ |
+| Include one command in ... | Include just one command in ... |
+| Run the container ... | Simply run the container ... |
+| You can remove ... | You can easily remove ... |
+| These steps ... | These simple steps ... |
+
+## Backstage word list
+
+A list of Backstage-specific terms and words to be used consistently across
+the site.
+
+| Term | Usage |
+| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------- | --- |
+| Backstage | Always capitalized. |
+| plugin | Lowercase when referring to the concept. Use code style when referring to a specific package, for example `@backstage/plugin-catalog`. |
+| Software Catalog | Capitalized as a product name. Use "catalog" (lowercase) when referring to the concept generically. |
+| Software Templates | Capitalized as a product name. |
+| TechDocs | One word, camel case. |
+| Scaffolder | Capitalized as a product name. |
+| app-config | Use code style: `app-config.yaml`. |
+| open source | Two words, lowercase (unless starting a sentence). |
+| backend system | Lowercase when referring to the Backstage backend framework. | |
diff --git a/docs/contribute/project-structure.md b/docs/contribute/project-structure.md
index f58afedee8..f1be29c312 100644
--- a/docs/contribute/project-structure.md
+++ b/docs/contribute/project-structure.md
@@ -55,12 +55,10 @@ defined in
[`package.json`](https://github.com/backstage/backstage/blob/master/package.json):
```json
- "workspaces": {
- "packages": [
- "packages/*",
- "plugins/*"
- ]
- },
+ "workspaces": [
+ "packages/*",
+ "plugins/*"
+ ],
```
Let's look at them individually.
@@ -146,7 +144,10 @@ are separated out into their own folder, see further down.
- [`dev-utils/`](https://github.com/backstage/backstage/tree/master/packages/dev-utils) -
Helps you setup a plugin for isolated development so that it can be served
- separately.
+ separately. This is for plugins using the legacy frontend system.
+
+- [`frontend-dev-utils/`](https://github.com/backstage/backstage/tree/master/packages/frontend-dev-utils) -
+ Utilities for developing frontend plugins using the new frontend system. Provides the `createDevApp` helper for setting up a minimal development app in a plugin's `dev/` entry point.
- [`e2e-test/`](https://github.com/backstage/backstage/tree/master/packages/e2e-test) -
Another CLI that can be run to try out what would happen if you build all the
diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md
index 852248d543..05e5d63076 100644
--- a/docs/deployment/docker.md
+++ b/docs/deployment/docker.md
@@ -113,7 +113,7 @@ CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app
For more details on how the `backend:bundle` command and the `skeleton.tar.gz`
file works, see the
-[`backend:bundle` command docs](../tooling/cli/03-commands.md#backendbundle).
+[`backend:bundle` command docs](../tooling/cli/03-commands.md#package-bundle).
The `Dockerfile` is located at `packages/backend/Dockerfile`, but needs to be
executed with the root of the repo as the build context, in order to get access
diff --git a/docs/deployment/index.md b/docs/deployment/index.md
index 76c8c12932..a5f3c9ac7a 100644
--- a/docs/deployment/index.md
+++ b/docs/deployment/index.md
@@ -32,6 +32,4 @@ This method is covered in [Building a Docker image](docker.md) and
There are many ways to deploy Backstage! You can find more examples in the community contributed guides found [here](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/).
-If you need to run Backstage behind a corporate proxy, this
-[contributed guide](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md)
-may help.
+If you need to run Backstage behind a corporate proxy, see the [corporate proxy guide](../tutorials/corporate-proxy.md).
diff --git a/docs/dls/component-design-guidelines.md b/docs/dls/component-design-guidelines.md
index fddffd9fd9..f3e5fe2b49 100644
--- a/docs/dls/component-design-guidelines.md
+++ b/docs/dls/component-design-guidelines.md
@@ -104,3 +104,5 @@ accessibility.
[8]: https://v4.mui.com/customization/palette/#default-values
[9]: https://v4.mui.com/customization/typography/
[10]: https://backstage.io/docs/conf/user-interface
+[11]: https://v4.mui.com/customization/components/
+[12]: https://v4.mui.com/customization/default-theme/
diff --git a/docs/faq/product.md b/docs/faq/product.md
index f09b5696b8..fed4c2eb65 100644
--- a/docs/faq/product.md
+++ b/docs/faq/product.md
@@ -16,7 +16,7 @@ brand.
No, but it can be! Backstage is designed to be a developer portal for all your
infrastructure tooling, services, and documentation. So, it's not a monitoring
platform — but that doesn't mean you can't integrate a monitoring tool into
-Backstage by writing [a plugin](#what-is-a-plugin-in-backstage).
+Backstage by writing [a plugin](technical.md#what-is-a-plugin-in-backstage).
### How is Backstage licensed?
diff --git a/docs/features/kubernetes/authenticationstrategy.md b/docs/features/kubernetes/authenticationstrategy.md
index e39201aaeb..5c20965bde 100644
--- a/docs/features/kubernetes/authenticationstrategy.md
+++ b/docs/features/kubernetes/authenticationstrategy.md
@@ -157,7 +157,7 @@ export class AwsIamStrategy implements AuthenticationStrategy {
Sometimes you need to add a new way to authenticate against a kubernetes cluster not support by default by Backstage. This is how integrators can bring their own kubernetes auth strategies through the use of the [`addAuthStrategy`](https://github.com/backstage/backstage/blob/57397e7d6d2d725712c439f4ab93f2ac6aa27bf8/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts#L211) method on `KubernetesBuilder` or through the [AuthStrategyExtensionPoint](https://github.com/backstage/backstage/blob/57397e7d6d2d725712c439f4ab93f2ac6aa27bf8/plugins/kubernetes-backend/src/plugin.ts#L112). So, on the following sections, we are going to introduce a new AuthStrategy for [Pinniped][1], an authentication service for Kubernetes clusters.
-### Custom Pinniped auth strategy in the new backend system
+### Custom Pinniped auth strategy in the backend system
To add a new AuthStrategy, we need to create a new Pinniped [backend module](../../backend-system/building-plugins-and-modules/01-index.md#modules) to extend the Kubernetes-Backend plugin. The Pinniped module will interact with the Kubernetes-Backend plugin through the [extension points](../../backend-system/architecture/05-extension-points.md) registered by the plugin. The Kubernetes-Backend plugin [registers](https://github.com/backstage/backstage/blob/ebe7afad9d19f279469168ca0d4feceb92c1ad36/plugins/kubernetes-backend/src/plugin.ts#L155) multiple extension points like `kubernetesObjectsProvider`, `kubernetesClusterSupplier`, `kubernetesFetcher`, `kubernetesServiceLocator` and the `kubernetesAuthStrategy`.
diff --git a/docs/features/kubernetes/proxy.md b/docs/features/kubernetes/proxy.md
index 85ad86f83b..35dcd1f8c3 100644
--- a/docs/features/kubernetes/proxy.md
+++ b/docs/features/kubernetes/proxy.md
@@ -99,7 +99,7 @@ even if a valid ID token was attached that a cluster would authorize.
## Other known limitations
-The proxy as it was released in [Backstage 1.9](../../releases/v1.9.0-changelog.md#patch-changes-15)
+The proxy as it was released in [Backstage 1.9](../../releases/v1.9.0-changelog.md)
has a known bug:
- [#15901](https://github.com/backstage/backstage/issues/15901) - it cannot
diff --git a/docs/features/search/README.md b/docs/features/search/README.md
index eb5d12080c..742df07e25 100644
--- a/docs/features/search/README.md
+++ b/docs/features/search/README.md
@@ -34,7 +34,7 @@ The following sections show the plugins and search engines currently supported b
| Plugin | Support Status |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| Software Catalog | ✅ |
-| [TechDocs](./how-to-guides.md#how-to-index-techdocs-documents) | ✅ |
+| [TechDocs](./how-to-guides.md#how-to-customize-fields-in-the-software-catalog-or-techdocs-index) | ✅ |
| [Stack Overflow](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-stack-overflow-collator/README.md#index-stack-overflow-questions-to-search) | ✅ |
### Search engines
diff --git a/docs/features/search/getting-started.md b/docs/features/search/getting-started.md
index e5029ed35f..155aaa2e58 100644
--- a/docs/features/search/getting-started.md
+++ b/docs/features/search/getting-started.md
@@ -342,4 +342,4 @@ indexBuilder.addCollator({
});
```
-> For more advanced customization of the Search backend, also see how to guides such as [How to index TechDocs documents](./how-to-guides.md#how-to-index-techdocs-documents) and [How to limit what can be searched in the Software Catalog](./how-to-guides.md#how-to-limit-what-can-be-searched-in-the-software-catalog)
+> For more advanced customization of the Search backend, also see how to guides such as [How to customize fields in the Software Catalog or TechDocs index](./how-to-guides.md#how-to-customize-fields-in-the-software-catalog-or-techdocs-index)
diff --git a/docs/features/software-catalog/api.md b/docs/features/software-catalog/api.md
index ca8b8f638d..2a0e20cbdc 100644
--- a/docs/features/software-catalog/api.md
+++ b/docs/features/software-catalog/api.md
@@ -150,7 +150,54 @@ Some more real world usable examples:
#### Full text filtering
-TODO
+You can perform a text search across entity fields using the `fullTextFilterTerm`
+query parameter. This performs a case-insensitive substring match against the
+values in the entity YAML fields.
+
+By default, when no `fullTextFilterFields` parameter is specified, the search
+runs against the current sort field (from `orderField`), or `metadata.uid` if
+no sort field is set. This means that without specifying fields explicitly, the
+search may not match against the fields you expect.
+
+To control which fields are searched, pass the `fullTextFilterFields` query
+parameter as a comma-separated list of entity field paths.
+
+Query parameters:
+
+- `fullTextFilterTerm` - The text to search for (case insensitive, substring match)
+- `fullTextFilterFields` - A comma-separated list of entity field paths to
+ search against (e.g. `metadata.name,metadata.title`)
+
+Example:
+
+```text
+/entities/by-query?fullTextFilterTerm=my-service&fullTextFilterFields=metadata.name,metadata.title
+
+ Return entities whose metadata.name OR metadata.title contains "my-service"
+```
+
+Some more real world usable examples:
+
+- Search for components by name:
+
+ `/entities/by-query?filter=kind=component&fullTextFilterTerm=payment&fullTextFilterFields=metadata.name`
+
+- Search across both name and title:
+
+ `/entities/by-query?filter=kind=system&fullTextFilterTerm=platform&fullTextFilterFields=metadata.name,metadata.title`
+
+- Combine with other filters (e.g. owned by a specific group):
+
+ `/entities/by-query?filter=kind=component,relations.ownedBy=group:default/my-team&fullTextFilterTerm=api&fullTextFilterFields=metadata.name`
+
+:::note Note
+
+Full text filtering is mutually exclusive with cursor-based pagination. When a
+`cursor` is provided, `fullTextFilterTerm` and `fullTextFilterFields` are
+ignored — the cursor already encodes the original filter parameters from the
+initial request.
+
+:::
#### Field selection
@@ -210,6 +257,191 @@ if `prevCursor` exists, it can be used to retrieve the previous batch of entitie
it isn't possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters,
as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration.
+### `POST /entities/by-query`
+
+This supports the same features as the `GET` variant, but in a `POST` body to
+not have to abide by URL length limits. Additionally, it supports advanced, more
+expressive querying format - see below. The response format is identical.
+
+#### Querying by filter predicate
+
+You can pass in a filter predicate to select a subset of entities in the
+catalog. They are comprised of an optional logical expression tree (using
+`$all`, `$any`, `$not`), ending in filter sets that can have custom matchers
+(e.g. `$exists`, `$in`, `$hasPrefix`, `$contains`).
+
+This is an example of what such a filter predicate expression might look like:
+
+```js
+{
+ "query": {
+ "$all": [
+ {
+ "kind": "Component",
+ "spec.type": { "$in": ["service", "website"] }
+ },
+ {
+ "$not": {
+ "metadata.annotations.backstage.io/orphan": "true"
+ }
+ }
+ ]
+ }
+}
+```
+
+A filter set is an object whose keys are dot separated paths into an object, and
+the values are either primitives (string, number, or boolean) or custom matchers
+as per below. An example of a simple such filter set is:
+
+```js
+// All of the following must be true for a given entity (there's an
+// implicit AND between them)
+{
+ // The kind field is matched against a literal, case insensitively
+ "kind": "Component",
+ // The type field inside the spec is matched using a custom matcher, see below
+ "spec.type": { "$in": ["service", "website"] }
+}
+```
+
+The root of the query is always an object, whether there is a logic expression
+tree or not. Nodes with a single key that starts with a `$` sign have special
+meaning.
+
+- `$not`: Logical negation.
+
+ Its value must be a single expression. Example:
+
+ ```js
+ // Matches entities that do NOT have kind Component
+ {
+ "$not": {
+ "kind": "Component",
+ }
+ }
+ ```
+
+ Note that `$not` cannot be used in a right hand side value matcher.
+
+ ```js
+ // ❌ WRONG
+ { "kind": { "$not": "Component" } }
+ // ✅ CORRECT
+ { "$not": { "kind": "Component" } }
+ ```
+
+- `$all`: Require that all given expressions match each entity.
+
+ Its value must be an array of expressions. Example:
+
+ ```js
+ // Matches entities that BOTH have kind Component and type website
+ {
+ "$all": [
+ { "kind": "Component" },
+ { "spec.type": "website" }
+ ]
+ }
+ ```
+
+ An empty array always matches every entity.
+
+- `$any`: Require that at least one of a set of expressions match a given entity.
+
+ Its value must be an array of expressions. Example:
+
+ ```js
+ // Matches entities that EITHER have kind Component or type website
+ {
+ "$any": [
+ { "kind": "Component" },
+ { "spec.type": "website" }
+ ]
+ }
+ ```
+
+ An empty array never matches anything.
+
+- `$exists`: Assert on the existence of fields.
+
+ Its value is either `true`, meaning that the field must exist on the entity
+ (no matter what its value), or `false`, meaning that it must not exist.
+ Example:
+
+ ```js
+ // Matches entities that DO NOT have that annotation, ignoring what the
+ // value might be
+ {
+ "metadata.annotations.backstage.io/orphan": {
+ "$exists": false
+ },
+ }
+ ```
+
+- `$in`: Assert that a field has any of a set of primitive values.
+
+ Its value must be an array of string, number, and/or boolean values. Example:
+
+ ```js
+ // Matches entities whose type is EITHER service or website
+ {
+ "spec.type": {
+ "$in": ["service", "website"]
+ }
+ }
+ ```
+
+ The matching is case insensitive. An empty array never matches anything.
+
+- `$hasPrefix`: Assert that a field is a string that starts with a certain prefix text.
+
+ Its value is a string. Example:
+
+ ```js
+ // Matches entities whose project slug annotation starts with "backstage/"
+ {
+ "metadata.annotations.github.com/project-slug": {
+ "$hasPrefix": "backstage/"
+ }
+ }
+ ```
+
+ The matching is case insensitive, and captures both exact matches and strings
+ that start with the given prefix.
+
+- `$contains`: Assert that an array contains an element that matches the given expression.
+
+ There is only limited support for this matcher. One use case is for relations:
+
+ ```js
+ {
+ // Specifically type and (optionally) targetRef supported, and only
+ // with equality or "$in" for the targetRef
+ "relations": {
+ "$contains": {
+ "type": "ownedBy",
+ "targetRef": {
+ "$in": ["user:default/foo", "group:default/bar"]
+ }
+ }
+ }
+ }
+ ```
+
+ The other use case is for arrays where you match with a primitive value, such
+ as tags. Example:
+
+ ```js
+ {
+ // Works for array fields whose items are primitive values
+ // (typically strings, but numbers and booleans are also supported)
+ "metadata.tags": {
+ "$contains": "java"
+ }
+ }
+ ```
+
### `GET /entities`
Lists entities.
diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md
index d62bb34505..2e984d2942 100644
--- a/docs/features/software-catalog/catalog-customization.md
+++ b/docs/features/software-catalog/catalog-customization.md
@@ -728,6 +728,49 @@ Notes:
- Icons for groups and tabs are resolved via the app's IconsApi. When using a string icon id (for example `"dashboard"`), ensure that the corresponding icon bundles are enabled/installed in your app (see the [IconBundleBlueprint documentation](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.IconBundleBlueprint.html)).
- Group icons are only rendered if `showNavItemIcons` is set to `true`.
+### Content ordering within groups
+
+By default, content items within each group are sorted alphabetically by title. You can change this with the `defaultContentOrder` option, which supports two modes:
+
+- **`title`** (default) — sort alphabetically by the content extension's title (case-insensitive).
+- **`natural`** — preserve the natural extension discovery/registration order.
+
+A page-level `defaultContentOrder` sets the default for all groups, and individual groups can override it with a per-group `contentOrder`:
+
+```yaml
+app:
+ extensions:
+ - page:catalog/entity:
+ config:
+ # Default content order for all groups
+ defaultContentOrder: title
+
+ groups:
+ - documentation:
+ title: Docs
+ # Override: keep natural order for this group
+ contentOrder: natural
+```
+
+Note that content ordering only applies to content items within groups. Ungrouped tabs (those not matching any group definition) always retain their natural order.
+
+### Group aliases
+
+Groups can declare `aliases` — a list of other group IDs that should be treated as equivalent. Any entity content extension targeting an aliased group ID will be included in the aliasing group. This is useful when renaming or merging groups without having to reconfigure individual extensions:
+
+```yaml
+app:
+ extensions:
+ - page:catalog/entity:
+ config:
+ groups:
+ - develop:
+ title: Develop
+ # Content targeting 'development' will appear in this group
+ aliases:
+ - development
+```
+
### Overriding or disabling a tab's group (per extension)
Each entity content extension (tabs on the entity page) can declare a default `group` in code. You can override or disable this per installation in `app-config.yaml` using the extension's config:
diff --git a/docs/features/software-catalog/configuration.md b/docs/features/software-catalog/configuration.md
index 3ed027a0a4..6ceb00b3b5 100644
--- a/docs/features/software-catalog/configuration.md
+++ b/docs/features/software-catalog/configuration.md
@@ -366,3 +366,82 @@ Now install your module.
```ts title="packages/backend/src/index.ts"
backend.add(eventsModuleCatalogErrors);
```
+
+## OpenAPI and AsyncAPI Placeholder Support
+
+The **OpenAPI Catalog Backend Module** registers a JSON Schema placeholder resolver for the `openapi` (and `asyncapi`) placeholder keys. This enables you to use `$openapi` and `$asyncapi` references in your catalog entities, while having all underlying `$ref` pointers resolved and bundled as part of the schema processing.
+
+### Installation
+
+1. Add the package to your backend:
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-openapi
+```
+
+2. Register the module in your backend:
+
+```ts title="packages/backend/src/index.ts"
+backend.add(import('@backstage/plugin-catalog-backend-module-openapi'));
+```
+
+### Usage
+
+To trigger the `$ref` resolution, use the `$openapi` (or `$asyncapi`) placeholder in your catalog entity definition:
+
+```yaml
+apiVersion: backstage.io/v1alpha1
+kind: API
+metadata:
+ name: example
+ description: Example API
+spec:
+ type: openapi
+ lifecycle: production
+ owner: team
+ definition:
+ $openapi: ./spec/openapi.yaml # by using $openapi Backstage will now resolve all $ref instances
+```
+
+## Backstage OpenAPI Module
+
+As Backstage increasingly uses OpenAPI to define its core APIs (such as the Catalog and Scaffolder), discovering and interacting with these APIs is essential for integrating external tools.
+
+You can install the **Backstage OpenAPI Module** to easily expose the OpenAPI specifications for your Backstage instance plugins directly into the catalog.
+
+### Installation
+
+1. Install the module in your backend:
+
+```bash
+yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-backstage-openapi
+```
+
+2. Register the module in your backend:
+
+```ts title="packages/backend/src/index.ts"
+backend.add(
+ import('@backstage/plugin-catalog-backend-module-backstage-openapi'),
+);
+```
+
+3. Add the configuration to your `app-config.yaml`:
+
+```yaml title="app-config.yaml"
+catalog:
+ providers:
+ backstageOpenapi:
+ plugins:
+ - catalog
+ - scaffolder
+ # Optional configuration:
+ # definitionFormat controls how generated definitions are serialized.
+ # Supported values: 'json' (default) or 'yaml'.
+ # definitionFormat: json
+ # entityOverrides can be used to override parts of the produced entities.
+ # For example, to add a tag to all generated APIs:
+ # entityOverrides:
+ # metadata:
+ # tags:
+ # - from-openapi
+```
diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md
index ac8baadad5..f9ab41e37a 100644
--- a/docs/features/software-catalog/descriptor-format.md
+++ b/docs/features/software-catalog/descriptor-format.md
@@ -607,7 +607,7 @@ component, but there will always be one ultimate owner.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
-| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
+| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
### `spec.system` [optional]
@@ -811,7 +811,7 @@ Template, but there will always be one ultimate owner.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
-| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
+| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
## Kind: API
@@ -921,7 +921,7 @@ one ultimate owner.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
-| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
+| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
### `spec.system` [optional]
@@ -1121,7 +1121,7 @@ Describes the following entity kind:
| `apiVersion` | `backstage.io/v1alpha1` |
| `kind` | `Resource` |
-A resource describes the infrastructure a system needs to operate, like BigTable
+A Resource describes the infrastructure a system needs to operate, like BigTable
databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together with
components and systems allows to visualize resource footprint, and create
tooling around them.
@@ -1164,7 +1164,7 @@ resource, but there will always be one ultimate owner.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
-| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
+| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
### `spec.type` [required]
@@ -1262,7 +1262,7 @@ but there will always be one ultimate owner.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
-| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
+| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
### `spec.domain` [optional]
@@ -1334,7 +1334,7 @@ but there will always be one ultimate owner.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
-| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
+| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
### `spec.subdomainOf` [optional]
diff --git a/docs/features/software-catalog/faq.md b/docs/features/software-catalog/faq.md
index 24b490ee13..6a8f9b99d4 100644
--- a/docs/features/software-catalog/faq.md
+++ b/docs/features/software-catalog/faq.md
@@ -25,7 +25,7 @@ On the technical side, this is unwanted complexity. You need to implement and ma
On the user experience side, a Backstage experience without complete organizational data is a serious hindrance to getting the full power out of the tool. Your users won't be able to click on owners and seeing who they are and what teams they belong to. They won't be able to find out what the communications paths are when they need to reach you or your managers when something goes wrong or they have a feature request. They can't get an overview of what teams own and how they relate to each other. It will be a much more barren experience. Organizational data is highly valuable to have centrally available, complete and correct.
-## Can I call the catalog itself from inside a processor / provider?
+## Can I call the catalog itself from inside a processor / provider? {#can-i-call-the-catalog-itself-from-inside-a-processor--provider}
While it's possible to get hold of a catalog client via the `catalogServiceRef` from `@backstage/plugin-catalog-node`, it's almost never the right thing to do, and we strongly discourage from doing so.
diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md
index a221ed9157..0c812bd8dd 100644
--- a/docs/features/software-catalog/index.md
+++ b/docs/features/software-catalog/index.md
@@ -32,7 +32,7 @@ More specifically, the Software Catalog enables two main use-cases:
## Getting Started
The Software Catalog is available to browse at `/catalog`. If you've followed
-[Getting Started with Backstage](../../getting-started), you should be able to
+[Getting Started with Backstage](../../getting-started/index.md), you should be able to
browse the catalog at `http://localhost:3000`.

diff --git a/docs/features/software-templates/authorizing-scaffolder-template-details.md b/docs/features/software-templates/authorizing-scaffolder-template-details.md
index 7ff2cd9377..e35d81d260 100644
--- a/docs/features/software-templates/authorizing-scaffolder-template-details.md
+++ b/docs/features/software-templates/authorizing-scaffolder-template-details.md
@@ -6,7 +6,13 @@ description: How to authorize parts of a template and authorize scaffolder task
The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template. It also allows you to control access to scaffolder tasks.
-### Authorizing parameters and steps
+:::tip
+
+To better understand the following examples make sure to review the [Writing a permission policy](../../permissions/writing-a-policy.md)) documentation before moving forward.
+
+:::
+
+## Authorizing parameters and steps
To mark specific parameters or steps as requiring permission, add the `backstage:permissions` property to the parameter or step with one or more tags. For example:
@@ -288,62 +294,3 @@ All other users are granted permissions to perform/access the following actions/
- Read scaffolder tasks and their associated events/logs for tasks created by the user.
Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex use cases.
-
-### Authorizing in the New Backend System
-
-Instead of the changes in `permission.ts` noted in the above example you will make them in your `index.ts`. You will need to create a module where your permission policy will get added. Here is a very simplified example of how to do that:
-
-```ts title="packages/backend/src/index.ts"
-import { createBackendModule } from '@backstage/backend-plugin-api';
-import {
- PolicyDecision,
- AuthorizeResult,
-} from '@backstage/plugin-permission-common';
-import {
- PermissionPolicy,
- PolicyQuery,
- PolicyQueryUser,
-} from '@backstage/plugin-permission-node';
-import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha';
-
-class ExamplePermissionPolicy implements PermissionPolicy {
- async handle(
- request: PolicyQuery,
- user?: PolicyQueryUser,
- ): Promise {
- // Various scaffolder permission checks ...
-
- return {
- result: AuthorizeResult.ALLOW,
- };
- }
-}
-
-const customPermissionBackendModule = createBackendModule({
- pluginId: 'permission',
- moduleId: 'allow-all-policy',
- register(reg) {
- reg.registerInit({
- deps: { policy: policyExtensionPoint },
- async init({ policy }) {
- policy.setPolicy(new ExamplePermissionPolicy());
- },
- });
- },
-});
-
-const backend = createBackend();
-
-// Other plugins...
-
-/* highlight-add-start */
-backend.add(import('@backstage/plugin-permission-backend'));
-backend.add(customPermissionBackendModule);
-/* highlight-add-end */
-```
-
-:::note Note
-
-The `ExamplePermissionPolicy` here could be the one from the [Authorizing parameters and steps](#authorizing-parameters-and-steps) example or from the [Authorizing actions](#authorizing-actions) example. It would work the same way for both of them.
-
-:::
diff --git a/docs/features/software-templates/builtin-actions.md b/docs/features/software-templates/builtin-actions.md
index 22d8ef6454..d74123704e 100644
--- a/docs/features/software-templates/builtin-actions.md
+++ b/docs/features/software-templates/builtin-actions.md
@@ -32,7 +32,7 @@ Here's how to add an action module, first you need to run this command:
yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-github
```
-Then you need to add it to your backend, this is a simplified new backend system for example purposes:
+Then you need to add it to your backend, this is a simplified backend system for example purposes:
```ts title="/packages/backend/src/index.ts"
import { createBackend } from '@backstage/backend-defaults';
@@ -59,57 +59,3 @@ backend.start();
A list of all registered actions can be found under `/create/actions`. For local
development you should be able to reach them at
`http://localhost:3000/create/actions`.
-
-## Migrating from `fetch:cookiecutter` to `fetch:template`
-
-The `fetch:template` action is a new action with a similar API to
-`fetch:cookiecutter` but no dependency on `cookiecutter`. There are two options
-for migrating templates that use `fetch:cookiecutter` to use `fetch:template`:
-
-### Using `cookiecutterCompat` mode
-
-The new `fetch:template` action has a `cookiecutterCompat` flag which should
-allow most templates built for `fetch:cookiecutter` to work without any changes.
-
-1. Update action name in `template.yaml`. The name should be changed from
- `fetch:cookiecutter` to `fetch:template`.
-2. Set `cookiecutterCompat` to `true` in the `fetch:template` step input in
- `template.yaml`.
-
-```yaml title="template.yaml"
-steps:
- - id: fetchBase
- name: Fetch Base
- # highlight-remove-next-line
- action: fetch:cookiecutter
- # highlight-add-next-line
- action: fetch:template
- input:
- url: ./skeleton
- # highlight-add-next-line
- cookiecutterCompat: true
- values:
-```
-
-### Manual migration
-
-If you prefer, you can manually migrate your templates to avoid the need for
-enabling cookiecutter compatibility mode, which will result in slightly less
-verbose template variables expressions.
-
-1. Update action name in `template.yaml`. The name should be changed from
- `fetch:cookiecutter` to `fetch:template`.
-2. Update variable syntax in file names and content. `fetch:cookiecutter`
- expects variables to be enclosed in `{{` `}}` and prefixed with
- `cookiecutter.`, while `fetch:template` expects variables to be enclosed in
- `${{` `}}` and prefixed with `values.`. For example, a reference to variable
- `myInputVariable` would need to be migrated from
- `{{ cookiecutter.myInputVariable }}` to `${{ values.myInputVariable }}`.
-3. Replace uses of `jsonify` with `dump`. The
- [`jsonify` filter](https://cookiecutter.readthedocs.io/en/latest/advanced/template_extensions.html#jsonify-extension)
- is built in to `cookiecutter`, and is not available by default when using
- `fetch:template`. The
- [`dump` filter](https://mozilla.github.io/nunjucks/templating.html#dump) is
- the equivalent filter in nunjucks, so an expression like
- `{{ cookiecutter.myAwesomeList | jsonify }}` should be migrated to
- `${{ values.myAwesomeList | dump }}`.
diff --git a/docs/features/software-templates/configuration.md b/docs/features/software-templates/configuration.md
index bbf35557ce..c266fa1217 100644
--- a/docs/features/software-templates/configuration.md
+++ b/docs/features/software-templates/configuration.md
@@ -107,29 +107,6 @@ Default secrets are resolved from environment variables and accessible via `${{
**Security Note:** Secrets are automatically masked in logs and are only available to backend actions, never exposed to the frontend.
-## Disabling Docker in Docker situation (Optional)
-
-Software templates use the `fetch:template` action by default, which requires no
-external dependencies and offers a
-[Cookiecutter-compatible mode](https://backstage.io/docs/features/software-templates/builtin-actions#using-cookiecuttercompat-mode).
-There is also a `fetch:cookiecutter` action, which uses
-[Cookiecutter](https://github.com/cookiecutter/cookiecutter) directly for
-templating. By default, the `fetch:cookiecutter` action will use the
-[scaffolder-backend/Cookiecutter](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile)
-docker image.
-
-If you are running Backstage from a Docker container and you want to avoid
-calling a container inside a container, you can set up Cookiecutter in your own
-image, this will use the local installation instead.
-
-You can do so by including the following lines in the last step of your
-`Dockerfile`:
-
-```Dockerfile
-RUN apt-get update && apt-get install -y python3 python3-pip
-RUN pip3 install cookiecutter
-```
-
## Customizing the ScaffolderPage with Grouping and Filtering
Once you have more than a few software templates you may want to customize your
diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md
index 58da85c6eb..37419b8bcf 100644
--- a/docs/features/software-templates/index.md
+++ b/docs/features/software-templates/index.md
@@ -14,26 +14,13 @@ locations like GitHub or GitLab.
When creating custom scaffolder actions, **use camelCase for action IDs** instead of kebab-case. Action IDs with dashes (like `fetch-component-id`) will cause template expressions like `${{ steps.fetch-component-id.output.componentId }}` to return `NaN` because the dashes are evaluated as subtraction operators in JavaScript expressions.
-:::note
-
-See the [Writing Custom Actions guide](./writing-custom-actions.md#naming-conventions) and [Template Migration guide](./migrating-from-v1beta2-to-v1beta3.md#watch-out-for-dash-case) for more details.
+See the [Writing Custom Actions guide](./writing-custom-actions.md#naming-conventions) for more details.
:::
## Prerequisites
-:::note Note
-
-If you're running Backstage with Node 20 or later, you'll need to pass the flag `--no-node-snapshot` to Node in order to
-use the templates feature.
-One way to do this is to specify the `NODE_OPTIONS` environment variable before starting Backstage:
-`export NODE_OPTIONS="${NODE_OPTIONS:-} --no-node-snapshot"`
-
-It's important to append to the existing `NODE_OPTIONS` value, if it's already set, rather than overwriting it, since some NodeJS Debugging tools may rely on this environment variable to work properly.
-
-:::
-
-These docs assume you have already gone over the [Backstage Getting Started](../../getting-started) section and you are able to run Backstage locally or it has been deployed somewhere.
+These docs assume you have already gone over the [Backstage Getting Started](../../getting-started/index.md) section and you are able to run Backstage locally or it has been deployed somewhere.
## Getting Started
diff --git a/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md b/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md
deleted file mode 100644
index 91fc34f978..0000000000
--- a/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md
+++ /dev/null
@@ -1,241 +0,0 @@
----
-id: migrating-from-v1beta2-to-v1beta3
-title: Migrating to v1beta3 templates
-description: How to migrate your existing templates to beta3 syntax
----
-
-# 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`
-
-```ts title="packages/backend/src/plugins/catalog.ts"
-/* highlight-add-next-line */
-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
-
-export default async function createPlugin(
- env: PluginEnvironment,
-): Promise {
- const builder = await CatalogBuilder.create(env);
- /* highlight-add-next-line */
- 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.
-
-```yaml
- kind: Template
- # highlight-remove-next-line
- apiVersion: backstage.io/v1beta2
- # highlight-add-next-line
- 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.
-
-```yaml
-spec:
- steps:
- input:
- # highlight-remove-next-line
- description: 'This is {{ parameters.name }}'
- # highlight-add-next-line
- description: This is ${{ parameters.name }}
- # highlight-remove-next-line
- repoUrl: '{{ parameters.repoUrl }}'
- # highlight-add-next-line
- 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.
-
-```yaml
-spec:
- steps:
- input:
- # highlight-remove-next-line
- if: '{{ eq parameters.value "backstage" }}'
- # highlight-add-next-line
- if: ${{ parameters.value === "backstage" }}
-```
-
-And then for the `not`
-
-```yaml
-spec:
- steps:
- input:
- # highlight-remove-next-line
- if: '{{ not parameters.value "backstage" }}'
- # highlight-add-next-line
- 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.
-
-```yaml
-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:
- # highlight-remove-next-line
- address: '{{ json parameters.address }}'
- # highlight-add-next-line
- address: ${{ parameters.address }}
- # highlight-remove-next-line
- test: '{{ parameters.test }}'
- # highlight-add-next-line
- 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.
-
-```yaml
-spec:
- steps:
- input:
- # highlight-remove-next-line
- repoUrl: '{{ parseRepoUrl parameters.repoUrl }}'
- # highlight-add-next-line
- 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 }` 🚀
-
-## Links should be used instead of named outputs
-
-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.
-
-```yaml
-output:
- # highlight-remove-start
- remoteUrl: {{ steps['publish'].output.remoteUrl }}
- entityRef: {{ steps['register'].output.entityRef }}
- # highlight-remove-end
- # highlight-add-start
- links:
- - title: Repository
- url: ${{ steps['publish'].output.remoteUrl }}
- - title: Open in catalog
- icon: catalog
- entityRef: ${{ steps['register'].output.entityRef }}
- # highlight-add-end
-```
-
-## 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.
-
-```yaml
- steps:
- # highlight-remove-start
- 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!
- # highlight-remove-end
-
- steps:
- # highlight-add-start
- id: myCustomAction
- ...
-
- id: publishPullRequest
- input:
- repoUrl: ${{ steps.myCustomAction.output.repoUrl }}
- # highlight-add-end
-```
-
-Alternatively, it's possible to keep the `dash-case` syntax and use brackets for property access as you would in JavaScript:
-
-```yaml
-input:
- repoUrl: ${{ steps['my-custom-action'].output.repoUrl }}
-```
-
-### Summary
-
-Of course, we're always available on [discord](https://discord.gg/backstage-687207715902193673) if
-you're stuck or something's not working as expected. You can also
-[raise an issue](https://github.com/backstage/backstage/issues/new/choose) with
-feedback or bugs!
diff --git a/docs/features/software-templates/migrating-to-rjsf-v5.md b/docs/features/software-templates/migrating-to-rjsf-v5.md
deleted file mode 100644
index d35eb166f0..0000000000
--- a/docs/features/software-templates/migrating-to-rjsf-v5.md
+++ /dev/null
@@ -1,138 +0,0 @@
----
-id: migrating-to-rjsf-v5
-title: 'Migrating to react-jsonschema-form@v5'
-description: Docs on migrating to `react-jsonschema-form`@v5 and the new designs
----
-
-:::note Note
-
-If you were previously using the `/alpha` imports to test out the `scaffolder/next` work, those imports have been promoted to the default exports from the respective packages. You should just have to remove the `/alpha` from the import path, and remove the `Next` from the import name. `NextScaffolderPage` -> `ScaffolderPage`, `createNextScaffolderFieldExtension` -> `createScaffolderFieldExtension` etc.
-
-:::
-
-## What's `react-jsonschema-form`?
-
-This library is core to the frontend part of the scaffolder plugin, and is responsible for rendering the form in which developers and end users fill out to meet the `jsonschema` requirement for the parameters section.
-
-Since the initial release of the `scaffolder` plugin, we we're on a pretty old version of `react-jsonschema-form` (v3), which has been pretty outdated as of late. The problem with us just bumping this library was that there are several breaking changes with the new v5 version, which we've tried pretty aggressively not to pass on to our end users for their templates and [Custom Field Extensions](https://backstage.io/docs/features/software-templates/writing-custom-field-extensions/).
-
-We're hoping that by duplicating the types from version 3 of `react-jsonschema-form` and making these the types that we will support even though the underlying library is v5, it should get us through all of the breaking changes without passing that down.
-
-## What's new?
-
-With that in mind, this release has `v5` of `react-jsonschema-form`, and with that comes all the new features and bugfixes in `v4` that we were waiting for - one of the main ones being the ability to use `if / then / else` syntax in the `template.yaml` definitions! 🎉
-
-We've also rebuilt how validation works in the `scaffolder` components, which now means that we've opened the ability to have `async` validation functions in your `Field Extensions`.
-
-Some of the pages have gotten a little bit of an overhaul in terms of UI based on some research and feedback from the community and internally.
-
-- The `TemplateList` page has gotten some new `Card` components which show a little more information than the previous version with a little `material-ui` standards.
-- The `WizardPage` has received some new updates with the stepper now running horizontally, and the `Review` step being a dedicated step in the stepper.
-- The `OngoingTask` page now does not show the logs by default, and instead has a much cleaner interface for tracking the ongoing steps and the pipeline of actions that are currently showing.
- - You can also now provide your own `OutputsComponent` which can be used to render the outputs from an ongoing / completed task in a way that suits your templates the best. For instance, if your template produces `Pull Requests`, it could be useful to render these in an interactive way where you can see the statuses of each of these `Pull Requests` in the `Ongoing Task` page.
-
-There's also a lot of bug fixes, and other things, but these are the main ones that we wanted to highlight.
-
-## How do I upgrade
-
-With the release of [`v1.20.0`](https://github.com/backstage/backstage/releases/tag/v1.20.0) these changes should have been made for you. We're hoping that it should be pretty transparent, and things just work as expected. Please reach out to us on [discord](https://discord.com/invite/MUpMjP2) or in a [issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=bug&projects=&template=bug.yaml&title=%F0%9F%90%9B+Bug+Report%3A+%3Ctitle%3E) if you're having issues.
-
-It's possible that if you have a hard dependency on any of the `@rjsf/*` libraries in your app, you'll need to bump these manually to the version that we currently support: `5.13.6` at the time of writing. There could be breaking changes that you will have to fix here however, which we think should be pretty simple, but they're things like changing imports from `@rjsf/core` to `@rjsf/utils`.
-
-```ts
-/* highlight-remove-next-line */
-import { FieldValidation } from '@rjsf/core';
-/* highlight-add-next-line */
-import { FieldValidation } from '@rjsf/utils';
-```
-
-## Escape hatch
-
-If for some reason the upgrade to [`v1.20.0`](https://github.com/backstage/backstage/releases/tag/v1.20.0) didn't go as planned, there's an escape hatch for use until the next mainline release in which we will try to get any issues fixed before removing the legacy code.
-
-We've moved some of the older exports to an `/alpha` export so you should be able switch to using the old library just in case.
-
-```tsx
-/* highlight-remove-next-line */
-import { ScaffolderPage } from '@backstage/plugin-scaffolder';
-/* highlight-add-next-line */
-import { LegacyScaffolderPage } from '@backstage/plugin-scaffolder/alpha';
-```
-
-And this API should be the exact same as the previous Router, so you should be able to make a change like the following further down in this file:
-
-```tsx
-
- entity?.metadata?.tags?.includes('recommended') ?? false,
- },
- ]}
- />
- }
->
-
-
- {/* ... other extensions */}
-
-
-
- {/* ... other layouts */}
-
-
-```
-
-And you can also update any of your `CustomFieldExtensions` to use the old helper like so:
-
-```ts
-/* highlight-remove-next-line */
-import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder';
-/* highlight-add-next-line */
-import { createLegacyScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react/alpha';
-
-export const EntityNamePickerFieldExtension = scaffolderPlugin.provide(
- /* highlight-remove-next-line */
- createScaffolderFieldExtension({
- /* highlight-add-next-line */
- createLegacyScaffolderFieldExtension({
- component: EntityNamePicker,
- name: 'EntityNamePicker',
- validation: entityNamePickerValidation,
- }),
-);
-```
-
-And in the component themselves, you might have to do the following:
-
-```tsx
-/* highlight-remove-next-line */
-import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
-/* highlight-add-next-line */
-import { LegacyFieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react/alpha';
-
-export const EntityNamePicker = (
- /* highlight-remove-next-line */
- props: FieldExtensionComponentProps,
- /* highlight-add-next-line */
- props: LegacyFieldExtensionComponentProps,
-) => {
- const {
- onChange,
- required,
- schema: { title = 'Name', description = 'Unique name of the component' },
- rawErrors,
- formData,
- idSchema,
- placeholder,
- } = props;
- // ..
-};
-```
diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md
index f5ed9e4854..94bf339595 100644
--- a/docs/features/software-templates/writing-custom-actions.md
+++ b/docs/features/software-templates/writing-custom-actions.md
@@ -8,15 +8,6 @@ If you want to extend the functionality of the Scaffolder, you can do so
by writing custom actions which can be used alongside our
[built-in actions](./builtin-actions.md).
-:::note Note
-
-When adding custom actions, the actions array will **replace the
-built-in actions too**. Meaning, you will no longer be able to use them.
-If you want to continue using the builtin actions, include them in the `actions`
-array when registering your custom actions, as seen below.
-
-:::
-
## Streamlining Custom Action Creation with Backstage CLI
The creation of custom actions in Backstage has never been easier thanks to the Backstage CLI. This tool streamlines the
@@ -56,7 +47,7 @@ its generated unit test. We will replace the existing placeholder code with our
import { resolveSafeChildPath } from '@backstage/backend-plugin-api';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import fs from 'fs-extra';
-import { type z } from 'zod';
+import { type z } from 'zod/v3';
export const createNewFileAction = () => {
return createTemplateAction({
diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md
index 3fe4d82a1f..f2e8486a07 100644
--- a/docs/features/software-templates/writing-custom-field-extensions.md
+++ b/docs/features/software-templates/writing-custom-field-extensions.md
@@ -290,7 +290,7 @@ and type for your field props to preventing having to duplicate the definitions:
```tsx
//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx
...
-import { z } from 'zod';
+import { z } from 'zod/v3';
import { makeFieldSchemaFromZod } from '@backstage/plugin-scaffolder';
const MyCustomExtensionWithOptionsFieldSchema = makeFieldSchemaFromZod(
diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md
index 5d9fcba603..23b376caf5 100644
--- a/docs/features/software-templates/writing-templates.md
+++ b/docs/features/software-templates/writing-templates.md
@@ -325,6 +325,57 @@ spec:
token: ${{ each.value.token }}
```
+### Defining a Secrets Schema
+
+You can define a JSON Schema for secrets that will be validated when a task is created. This is useful when secrets are passed programmatically (e.g., via CI/CD pipelines or API calls) rather than through the UI form. The schema ensures that required secrets are provided before task execution begins.
+
+```yaml
+apiVersion: scaffolder.backstage.io/v1beta3
+kind: Template
+metadata:
+ name: publish-to-npm
+ title: Publish to NPM
+spec:
+ owner: backstage/techdocs-core
+ type: service
+
+ # Define required secrets with a JSON Schema
+ secrets:
+ schema:
+ required:
+ - NPM_TOKEN
+ properties:
+ NPM_TOKEN:
+ type: string
+ description: NPM authentication token for publishing
+
+ parameters:
+ - title: Package Details
+ properties:
+ packageName:
+ type: string
+ title: Package Name
+
+ steps:
+ - id: publish
+ action: npm:publish
+ input:
+ packageName: ${{ parameters.packageName }}
+ token: ${{ secrets.NPM_TOKEN }}
+```
+
+When a task is created without the required secrets, the API returns a `400` error with a descriptive message:
+
+```json
+{
+ "errors": [
+ {
+ "message": "secrets.NPM_TOKEN is required"
+ }
+ ]
+}
+```
+
### Custom step layouts
If you find that the default layout of the form used in a particular step does not meet your needs then you can supply your own [custom step layout](./writing-custom-step-layouts.md).
@@ -525,7 +576,7 @@ token from the user, which you can do on a per-provider basis, in case your
template can be published to multiple providers.
Note, that you will need to configure an [authentication provider](../../auth/index.md#configuring-authentication-providers), alongside the
-[`ScmAuthApi`](../../auth/index.md#scaffolder-configuration-software-templates) for your source code management (SCM) service to make this feature work.
+[`ScmAuthApi`](../../auth/index.md#custom-scmauthapi-implementation) for your source code management (SCM) service to make this feature work.
### The Repository Branch Picker
diff --git a/docs/features/techdocs/FAQ.md b/docs/features/techdocs/FAQ.md
index 9c78e3e123..6def714b1b 100644
--- a/docs/features/techdocs/FAQ.md
+++ b/docs/features/techdocs/FAQ.md
@@ -11,7 +11,7 @@ This page answers frequently asked questions about [TechDocs](README.md).
- [What static site generator is TechDocs using?](#what-static-site-generator-is-techdocs-using)
- [What is the mkdocs-techdocs-core plugin?](#what-is-the-mkdocs-techdocs-core-plugin)
-- [Does TechDocs support file formats other than Markdown (e.g. RST, AsciiDoc)?](#does-techdocs-support-file-formats-other-than-markdown-eg-rst-asciidoc-)
+- [Does TechDocs support file formats other than Markdown (e.g. RST, AsciiDoc)?](#does-techdocs-support-file-formats-other-than-markdown-eg-rst-asciidoc)
- [What should be the value of `backstage.io/techdocs-ref` when using external build and storage?](#what-should-be-the-value-of-backstageiotechdocs-ref-when-using-external-build-and-storage)
- [Is it possible for users to suggest changes or provide feedback on a TechDocs page?](#is-it-possible-for-users-to-suggest-changes-or-provide-feedback-on-a-techdocs-page)
@@ -30,7 +30,7 @@ plugins (e.g.
[MkDocs Monorepo Plugin](https://github.com/spotify/mkdocs-monorepo-plugin)) as
well as a selection of Python Markdown extensions that TechDocs supports.
-#### Does TechDocs support file formats other than Markdown (e.g. RST, AsciiDoc) ?
+#### Does TechDocs support file formats other than Markdown (e.g. RST, AsciiDoc)?
Not right now. We are currently using MkDocs to generate the documentation from
source, so the files have to be in Markdown format. However, in the future we
diff --git a/docs/features/techdocs/addons--new.md b/docs/features/techdocs/addons--old.md
similarity index 56%
rename from docs/features/techdocs/addons--new.md
rename to docs/features/techdocs/addons--old.md
index cbafcbe127..1a22befc2e 100644
--- a/docs/features/techdocs/addons--new.md
+++ b/docs/features/techdocs/addons--old.md
@@ -1,11 +1,11 @@
---
-id: addons--new
+id: addons--old
title: TechDocs Addons
description: How to find, use, or create TechDocs Addons.
---
:::info
-This documentation is written for [the new frontend system](../../frontend-system/index.md) which is still in alpha and is only supported by a small number of plugins. If you are on the [old frontend system](./getting-started.md#adding-techdocs-frontend-plugin) you may want to read [its own article](./addons.md) instead.
+This documentation is written for [the old frontend system](./getting-started.md#adding-techdocs-frontend-plugin). If you are on the [new frontend system](../../frontend-system/index.md) you may want to read [its own article](./addons.md) instead.
:::
## Concepts
@@ -50,8 +50,9 @@ representative of physical spaces in the TechDocs UI:
### Addon Registry
The installation and configuration of Addons happens within a Backstage app's
-frontend. Addons are imported from plugins and registered as a plugin extension which
-are configured for both the TechDocs Reader page as well as the Entity docs page.
+frontend. Addons are imported from plugins and added underneath a registry
+component called ``. This registry can be configured for both
+the TechDocs Reader page as well as the Entity docs page.
Addons are rendered in the order in which they are registered.
@@ -59,26 +60,62 @@ Addons are rendered in the order in which they are registered.
To start using Addons you need to add the `@backstage/plugin-techdocs-module-addons-contrib` package to your app. You can do that by running this command from the root of your project: `yarn --cwd packages/app add @backstage/plugin-techdocs-module-addons-contrib`
-Addons can then be installed as a module in your `App.tsx`:
+Addons can be installed and configured in much the same way as extensions for
+other Backstage plugins: by adding them underneath an extension registry
+component (``) under the route representing the TechDocs Reader
+page in your `App.tsx`:
```tsx
// packages/app/src/App.tsx
-import { createApp } from '@backstage/frontend-defaults';
-import { createFrontendModule } from '@backstage/frontend-plugin-api';
-import { techDocsReportIssueAddonModule } from '@backstage/plugin-techdocs-module-addons-contrib/alpha';
+import { TechDocsReaderPage } from '@backstage/plugin-techdocs';
+import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
+import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
// ...
-const app = createApp({
- features: [
- // ...
- techDocsReportIssueAddonModule,
- // ...other techdocs addon modules
- ],
-});
+ }>
+
+
+ {/* Other addons can be added here. */}
+
+ ;
+```
-export default app.createRoot();
+If you are using a custom [TechDocs reader page](./how-to-guides.md#how-to-customize-the-techdocs-reader-page) your setup will be very similar, here's an example:
+
+```ts
+ }>
+
+
+ {/* Other addons can be added here. */}
+
+ {techDocsPage} // This is your custom TechDocs reader page
+
+```
+
+The process for configuring Addons on the documentation tab on the entity page
+is very similar; instead of adding the `` registry under a
+``, you'd add it as a child of ` `:
+
+```tsx
+// packages/app/src/components/catalog/EntityPage.tsx
+
+import { EntityLayout } from '@backstage/plugin-catalog';
+import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
+import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
+import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
+
+// ...
+
+
+
+
+
+ {/* Other addons can be added here. */}
+
+
+ ;
```
Note that on the entity page, because the Catalog plugin is responsible for the
@@ -89,12 +126,12 @@ page header, TechDocs Addons whose location is `Header` will not be rendered.
Addons can, in principle, be provided by any plugin! To make it easier to
discover available Addons, we've compiled a list of them here:
-| Addon | Package/Plugin | Description |
-| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| [`techDocsExpandableNavigationAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsExpandableNavigationAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. |
-| [`techDocsReportIssueAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsReportIssueAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
-| [`techDocsTextSizeAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsReportIssueAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
-| [`techDocsLightBoxAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsLightBoxAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. When clicking on the zoom icon it zooms the image to fit in the screen (similar to `background-size: contain`). |
+| Addon | Package/Plugin | Description |
+| -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [` `](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.ExpandableNavigation.html) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. |
+| [` `](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.ReportIssue.html) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
+| [` `](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.TextSize.html) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
+| [` `](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.LightBox.html) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. When clicking on the zoom icon it zooms the image to fit in the screen (similar to `background-size: contain`). Images inside links are ignored to avoid blocking navigation. |
Got an Addon to contribute? Feel free to add a row above!
@@ -105,32 +142,29 @@ specific locations within a TechDocs site. To package such a react component as
an Addon, follow these steps:
1. Write the component in your plugin like any other component
-2. Create the addon extension using the `TechDocsAddonBlueprint`
-3. Create and export the addon module from your plugin
+2. Create, provide, and export the component from your plugin
```ts
// plugins/your-plugin/src/plugin.ts
-import { TechDocsAddonLocations } from '@backstage/plugin-techdocs-react';
-import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha';
-import { CatGifComponent } from './addons';
-import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import {
+ createTechDocsAddonExtension,
+ TechDocsAddonLocations,
+} from '@backstage/plugin-techdocs-react';
+import { CatGifComponent, CatGifComponentProps } from './addons';
// ...
-const techDocsCatGifAddon = AddonBlueprint.make({
- name: 'cat-gif',
- params: {
+// You must "provide" your Addon, just like any extension, via your plugin.
+export const CatGif = yourPlugin.provide(
+ // This function "creates" the Addon given a component and location. If your
+ // component can be configured via props, pass the prop type here too.
+ createTechDocsAddonExtension({
name: 'CatGif',
location: TechDocsAddonLocations.Header,
component: CatGifComponent,
- },
-});
-
-export const techDocsCatGifAddonModule = createFrontendModule({
- pluginId: 'techdocs',
- extensions: [techDocsCatGifAddon],
-});
+ }),
+);
```
### Addons in the Content location
@@ -147,8 +181,7 @@ provided by the Addon framework.
```tsx
// plugins/your-plugin/src/addons/MakeAllImagesCatGifs.tsx
-
-import React, { useEffect } from 'react';
+import { useEffect } from 'react';
import { useShadowRootElements } from '@backstage/plugin-techdocs-react';
// This is a normal react component; in order to make it an Addon, you would
diff --git a/docs/features/techdocs/addons.md b/docs/features/techdocs/addons.md
index 78b472edf2..9a72f508c3 100644
--- a/docs/features/techdocs/addons.md
+++ b/docs/features/techdocs/addons.md
@@ -5,7 +5,7 @@ description: How to find, use, or create TechDocs Addons.
---
:::info
-This documentation is written for [the old frontend system](./getting-started.md#adding-techdocs-frontend-plugin). If you are on the [new frontend system](../../frontend-system/index.md) you may want to read [its own article](./addons--new.md) instead.
+This documentation is written for [the new frontend system](../../frontend-system/index.md). If you are on the [old frontend system](./getting-started.md#adding-techdocs-frontend-plugin) you may want to read [its own article](./addons--old.md) instead.
:::
## Concepts
@@ -50,9 +50,8 @@ representative of physical spaces in the TechDocs UI:
### Addon Registry
The installation and configuration of Addons happens within a Backstage app's
-frontend. Addons are imported from plugins and added underneath a registry
-component called ``. This registry can be configured for both
-the TechDocs Reader page as well as the Entity docs page.
+frontend. Addons are imported from plugins and registered as a plugin extension which
+are configured for both the TechDocs Reader page as well as the Entity docs page.
Addons are rendered in the order in which they are registered.
@@ -60,62 +59,26 @@ Addons are rendered in the order in which they are registered.
To start using Addons you need to add the `@backstage/plugin-techdocs-module-addons-contrib` package to your app. You can do that by running this command from the root of your project: `yarn --cwd packages/app add @backstage/plugin-techdocs-module-addons-contrib`
-Addons can be installed and configured in much the same way as extensions for
-other Backstage plugins: by adding them underneath an extension registry
-component (``) under the route representing the TechDocs Reader
-page in your `App.tsx`:
+Addons can then be installed as a module in your `App.tsx`:
```tsx
// packages/app/src/App.tsx
-import { TechDocsReaderPage } from '@backstage/plugin-techdocs';
-import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
-import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
+import { createApp } from '@backstage/frontend-defaults';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import { techDocsReportIssueAddonModule } from '@backstage/plugin-techdocs-module-addons-contrib/alpha';
// ...
- }>
-
-
- {/* Other addons can be added here. */}
-
- ;
-```
+const app = createApp({
+ features: [
+ // ...
+ techDocsReportIssueAddonModule,
+ // ...other techdocs addon modules
+ ],
+});
-If you are using a custom [TechDocs reader page](./how-to-guides.md#how-to-customize-the-techdocs-reader-page) your setup will be very similar, here's an example:
-
-```ts
- }>
-
-
- {/* Other addons can be added here. */}
-
- {techDocsPage} // This is your custom TechDocs reader page
-
-```
-
-The process for configuring Addons on the documentation tab on the entity page
-is very similar; instead of adding the `` registry under a
-``, you'd add it as a child of ` `:
-
-```tsx
-// packages/app/src/components/catalog/EntityPage.tsx
-
-import { EntityLayout } from '@backstage/plugin-catalog';
-import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
-import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
-import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
-
-// ...
-
-
-
-
-
- {/* Other addons can be added here. */}
-
-
- ;
+export default app.createRoot();
```
Note that on the entity page, because the Catalog plugin is responsible for the
@@ -126,12 +89,12 @@ page header, TechDocs Addons whose location is `Header` will not be rendered.
Addons can, in principle, be provided by any plugin! To make it easier to
discover available Addons, we've compiled a list of them here:
-| Addon | Package/Plugin | Description |
-| -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| [` `](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.ExpandableNavigation.html) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. |
-| [` `](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.ReportIssue.html) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
-| [` `](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.TextSize.html) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
-| [` `](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.LightBox.html) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. When clicking on the zoom icon it zooms the image to fit in the screen (similar to `background-size: contain`). |
+| Addon | Package/Plugin | Description |
+| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [`techDocsExpandableNavigationAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsExpandableNavigationAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. |
+| [`techDocsReportIssueAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsReportIssueAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
+| [`techDocsTextSizeAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsTextSizeAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
+| [`techDocsLightBoxAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsLightBoxAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. When clicking on the zoom icon it zooms the image to fit in the screen (similar to `background-size: contain`). Images inside links are ignored to avoid blocking navigation. |
Got an Addon to contribute? Feel free to add a row above!
@@ -142,29 +105,32 @@ specific locations within a TechDocs site. To package such a react component as
an Addon, follow these steps:
1. Write the component in your plugin like any other component
-2. Create, provide, and export the component from your plugin
+2. Create the addon extension using the `TechDocsAddonBlueprint`
+3. Create and export the addon module from your plugin
```ts
// plugins/your-plugin/src/plugin.ts
-import {
- createTechDocsAddonExtension,
- TechDocsAddonLocations,
-} from '@backstage/plugin-techdocs-react';
-import { CatGifComponent, CatGifComponentProps } from './addons';
+import { TechDocsAddonLocations } from '@backstage/plugin-techdocs-react';
+import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha';
+import { CatGifComponent } from './addons';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
// ...
-// You must "provide" your Addon, just like any extension, via your plugin.
-export const CatGif = yourPlugin.provide(
- // This function "creates" the Addon given a component and location. If your
- // component can be configured via props, pass the prop type here too.
- createTechDocsAddonExtension({
+const techDocsCatGifAddon = AddonBlueprint.make({
+ name: 'cat-gif',
+ params: {
name: 'CatGif',
location: TechDocsAddonLocations.Header,
component: CatGifComponent,
- }),
-);
+ },
+});
+
+export const techDocsCatGifAddonModule = createFrontendModule({
+ pluginId: 'techdocs',
+ extensions: [techDocsCatGifAddon],
+});
```
### Addons in the Content location
@@ -181,7 +147,8 @@ provided by the Addon framework.
```tsx
// plugins/your-plugin/src/addons/MakeAllImagesCatGifs.tsx
-import { useEffect } from 'react';
+
+import React, { useEffect } from 'react';
import { useShadowRootElements } from '@backstage/plugin-techdocs-react';
// This is a normal react component; in order to make it an Addon, you would
diff --git a/docs/features/techdocs/architecture.md b/docs/features/techdocs/architecture.md
index 8c9c4401cc..ff1c9d30eb 100644
--- a/docs/features/techdocs/architecture.md
+++ b/docs/features/techdocs/architecture.md
@@ -17,8 +17,7 @@ out-of-the box experience.
See below for our recommended deployment architecture which takes care
of stability, scalability and speed. Also look at the
-[HOW TO migrate guide](how-to-guides
-md#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach).
+[HOW TO migrate guide](how-to-guides.md#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach).
:::
diff --git a/docs/features/techdocs/cli.md b/docs/features/techdocs/cli.md
index f6be53cb55..fa3d9148e2 100644
--- a/docs/features/techdocs/cli.md
+++ b/docs/features/techdocs/cli.md
@@ -208,10 +208,13 @@ Options:
#### Publishing from behind a proxy
-For users attempting to publish TechDocs content behind a proxy, the TechDocs CLI leverages `global-agent` to navigate the proxy to successfully connect to that location. To enable `global-agent`, the following variables need to be set prior to running the techdocs-cli command:
+On Node.js 22.21.0+, set `NODE_USE_ENV_PROXY=1` along with `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` to route TechDocs publishing through a proxy. See the [corporate proxy guide](../../tutorials/corporate-proxy.md) for details.
+
+On older Node.js versions, the TechDocs CLI leverages `global-agent` to navigate the proxy. To enable `global-agent`, the following variables need to be set prior to running the techdocs-cli command:
```bash
-export GLOBAL_AGENT_HTTPS_PROXY=${HTTP_PROXY}
+export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY}
+export GLOBAL_AGENT_HTTPS_PROXY=${HTTPS_PROXY}
export GLOBAL_AGENT_NO_PROXY=${NO_PROXY}
```
diff --git a/docs/features/techdocs/configuring-ci-cd.md b/docs/features/techdocs/configuring-ci-cd.md
index d818c129a4..a06eb5e475 100644
--- a/docs/features/techdocs/configuring-ci-cd.md
+++ b/docs/features/techdocs/configuring-ci-cd.md
@@ -104,7 +104,7 @@ the TechDocs plugin in your Backstage app.
Here is an example workflow using GitHub Actions CI and AWS S3 storage. You can
use any CI and any other
-[TechDocs supported cloud storage providers](README.md#platforms-supported).
+[TechDocs supported cloud storage providers](README.md#supported).
Add a `.github/workflows/techdocs.yml` file in your
[Software Template(s)](../software-templates/index.md) like this -
diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md
index 1246600683..76c231ff0e 100644
--- a/docs/features/techdocs/getting-started.md
+++ b/docs/features/techdocs/getting-started.md
@@ -95,9 +95,6 @@ That's it! Now, we need the TechDocs Backend plugin for the frontend to work.
## Adding TechDocs Backend plugin
-:::note
-These instructions are for the new backend system. For setting this up in the old backend system, see [here](https://github.com/backstage/backstage/blob/v1.36.1/docs/features/techdocs/getting-started.md)
-:::
First we need to install the `@backstage/plugin-techdocs-backend` package.
```bash title="From your Backstage root directory"
diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md
index 755d88b163..33faf44841 100644
--- a/docs/features/techdocs/how-to-guides.md
+++ b/docs/features/techdocs/how-to-guides.md
@@ -852,9 +852,9 @@ and publish the documentation for them. If the value of the `company.com/techdoc
annotation is anything other than `'local'`, the user is responsible for publishing
documentation to the appropriate location in the TechDocs external storage.
-### Hybrid build strategy using the New Backend System
+### Hybrid build strategy using the Backend System
-To setup a hybrid build strategy using the New Backend System you'll follow the same steps as above but for Step 4 you will need to do the following:
+To setup a hybrid build strategy using the Backend System you'll follow the same steps as above but for Step 4 you will need to do the following:
```ts title="packages/backend/src/index.ts"
const backend = createBackend();
diff --git a/docs/frontend-system/architecture/10-app.md b/docs/frontend-system/architecture/10-app.md
index 0303dd5e70..d2af7b1c72 100644
--- a/docs/frontend-system/architecture/10-app.md
+++ b/docs/frontend-system/architecture/10-app.md
@@ -46,35 +46,57 @@ App feature discovery lets you automatically discover and install features provi
Because feature discovery needs to interact with the compilation process, it is only available when using the `@backstage/cli` to build your app. It is hooked into the WebPack compilation process by scanning your app package for compatible dependencies, which are then made part of the app compilation bundle.
-To enable frontend feature discovery, add the following configuration to your `app-config.yaml`:
+For information on how to configure feature discovery and other installation options, see [Installing Plugins](../building-apps/05-installing-plugins.md).
-```yaml
-app:
- packages: all
+## Preparing an App in Phases
+
+Most apps should use `createApp` from `@backstage/frontend-defaults`, which takes care of all app preparation internally. For more advanced use cases there is also a lower-level `prepareSpecializedApp` API in `@backstage/frontend-app-api`.
+
+This API is useful when you need to render a bootstrap tree before the full app can be finalized, for example while waiting for sign-in or other session-dependent state. It gives you access to a bootstrap app tree immediately, lets you either subscribe to finalization with `onFinalized()` or finalize synchronously with `finalize()`, and lets you reuse a prepared session in a later app instance.
+
+```tsx
+import {
+ FinalizedSpecializedApp,
+ prepareSpecializedApp,
+} from '@backstage/frontend-app-api';
+
+const preparedApp = prepareSpecializedApp({
+ config,
+ features: [appPlugin, ...features],
+});
+
+const bootstrapApp = preparedApp.getBootstrapApp();
+
+const unsubscribe = preparedApp.onFinalized(
+ (finalizedApp: FinalizedSpecializedApp) => {
+ console.log(finalizedApp.sessionState);
+ },
+);
```
-This will cause all dependencies in your app package to be installed automatically. If this is not desired, you can use include or exclude filters to narrow down the set of packages:
+The `getBootstrapApp()` method exposes the partial app tree that is available during bootstrap. If you call `onFinalized()`, you are subscribing to the bootstrap-owned finalization flow. In the sign-in case, the sign-in page receives an `onSignInSuccess` callback, and once it provides an identity through that callback the full app is finalized and `onFinalized()` subscribers are notified.
-```yaml
-app:
- packages:
- # Only the following packages will be included
- include:
- - '@backstage/plugin-catalog'
- - '@backstage/plugin-scaffolder'
----
-app:
- packages:
- # All but the following package will be included
- exclude:
- - '@backstage/plugin-catalog'
+If you instead call `finalize()`, you are taking ownership of finalization yourself. This only works when the app can be finalized synchronously, for example when all predicate context is already available or when you passed a reusable session state to `prepareSpecializedApp()` up front:
+
+```tsx
+const preparedApp = prepareSpecializedApp({
+ config,
+ features: [appPlugin, ...features],
+ advanced: {
+ sessionState,
+ },
+});
+
+const app = preparedApp.finalize();
```
-Note that you do not need to manually exclude packages that you also import explicitly in code, since plugin instances are deduplicated by the app. You will never end up with duplicate plugin installations except if they are in fact two different plugin instances with different IDs.
+When using phased app preparation, `app/root.children` acts as the main session boundary. Conditional extensions behind that boundary are evaluated during finalization. Conditional `app/root.elements` and API branches are also deferred until finalization, while other bootstrap-visible predicates are ignored and reported as warnings.
+
+Utility APIs that are first materialized during bootstrap are frozen for the lifetime of that app instance. Finalization may still add new APIs and may override existing API refs that were not materialized during bootstrap, but any deferred override of an already materialized bootstrap API is ignored and reported as an app error.
## Plugin Info Resolution
-When a plugin is installed in an app it may provide sources of information about the plugin that can be useful to end users and admins. This includes things like what version of a plugin is running, what team owns the plugin, and who to contact for support. You can read more about how the plugins provide this information in the [plugins `info` option section](./15-plugins.md#info).
+When a plugin is installed in an app it may provide sources of information about the plugin that can be useful to end users and admins. This includes things like what version of a plugin is running, what team owns the plugin, and who to contact for support. You can read more about how the plugins provide this information in the [plugins `info` option section](./15-plugins.md#info-option).
By default the app will pick a few common fields from `package.json` files, and assume that the opaque manifests are `catalog-info.yaml` files that some information can be gathered from too. This information will then be available via the `info()` method on plugin instances, returning a structure of the `FrontendPluginInfo` type.
diff --git a/docs/frontend-system/architecture/15-plugins.md b/docs/frontend-system/architecture/15-plugins.md
index c2f5f237c3..d124a0e61d 100644
--- a/docs/frontend-system/architecture/15-plugins.md
+++ b/docs/frontend-system/architecture/15-plugins.md
@@ -76,6 +76,20 @@ These are the routes that the plugin exposes to the app. The `routes` option dec
This is a list of feature flag declarations that your plugin provides to the app. This makes sure that the feature flags are correctly registered and can be toggled in the app. To read a feature flag you can use the feature flags [Utility API](../architecture/33-utility-apis.md), accessible via `featureFlagsApiRef`.
+### `if` option
+
+The `if` option lets you apply a shared condition to all extensions that are provided by a plugin instance. This is useful when you want to gate an entire plugin behind a feature flag or permission without repeating the same predicate on every individual extension.
+
+```tsx
+export default createFrontendPlugin({
+ pluginId: 'my-plugin',
+ if: { featureFlags: { $contains: 'my-plugin-enabled' } },
+ extensions: [...],
+});
+```
+
+This predicate is applied to every extension from that plugin instance. If any extension already has its own `if` predicate, the two are combined using logical `AND`.
+
### `info` option
This options is used to provide loaders for different sources of information about the plugin that may be useful to users and admins. The two available loaders are `packageJson` and `manifest`, and a plugin can use either or both as needed. The resulting information is available via the `info()` method on the plugin instance once it is installed in an app, but it is up to each app to decide how to derive the information from the provided sources.
diff --git a/docs/frontend-system/architecture/20-extensions.md b/docs/frontend-system/architecture/20-extensions.md
index 106ab5340a..14c31764ca 100644
--- a/docs/frontend-system/architecture/20-extensions.md
+++ b/docs/frontend-system/architecture/20-extensions.md
@@ -17,7 +17,7 @@ Each extensions has a number of different properties that define how it behaves
The ID of an extension is used to uniquely identity it, and it should ideally be unique across the entire Backstage ecosystem. For each frontend app instance there can only be a single extension for any given ID. Installing multiple extensions with the same ID will either result in an error or one of the extensions will override the others. The ID is also used to reference the extensions from other extensions, in configuration, and in other places such as developer tools and analytics.
-When creating an extension you do not provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the [extension blueprint](./23-extension-blueprints.md), the only exception is if you use [`createExtension`](#creating-an-extensions) directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted.
+When creating an extension you do not provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the [extension blueprint](./23-extension-blueprints.md), the only exception is if you use [`createExtension`](#creating-an-extension) directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted.
The extension ID will be constructed using the pattern `[:][][/][]`, where the separating `/` is only present if both a namespace and name are defined.
@@ -41,6 +41,43 @@ Each extension in the app can be disabled, meaning it will not be instantiated a
The ordering of extensions is sometimes very important, as it may for example affect in which order they show up in the UI. When an extension is toggled from disabled to enabled through configuration it resets the ordering of the extension, pushing it to the end of the list. It is generally recommended to leave extensions as disabled by default if their order is important, allowing for the order in which their are enabled in the configuration to determine their order in the app.
+### Conditions
+
+Extensions can also be conditionally enabled by providing an `if` predicate. This is available both on `createExtension(...)` directly and when creating extensions from blueprints.
+
+The predicate uses the same `FilterPredicate` syntax as elsewhere in Backstage, but in the frontend system it is evaluated against app-level data such as `featureFlags` and `permissions`. For example, the following page is only installed when the `experimental-features` flag is active:
+
+```tsx
+const examplePage = PageBlueprint.make({
+ params: {
+ path: '/example',
+ loader: () => import('./ExamplePage').then(m => ),
+ },
+ if: { featureFlags: { $contains: 'experimental-features' } },
+});
+```
+
+You can also combine conditions using logical operators such as `$all`, `$any`, and `$not`:
+
+```tsx
+const guardedCard = CardBlueprint.make({
+ params: {
+ title: 'Guarded Card',
+ loader: () => import('./GuardedCard').then(m => ),
+ },
+ if: {
+ $all: [
+ { featureFlags: { $contains: 'experimental-features' } },
+ { permissions: { $contains: 'catalog.entity.create' } },
+ ],
+ },
+});
+```
+
+Conditions are evaluated when the app tree is prepared, not continuously while the app is running. If the underlying feature flags or permissions change, the app needs to be prepared again in order for the extension tree to change, which in practice typically means reloading the app.
+
+If a plugin or module also provides an `if` predicate, it is combined with the extension-level predicate using logical `AND`. See the [plugin `if` option](./15-plugins.md#if-option) and [frontend modules](./25-extension-overrides.md#creating-a-frontend-module) sections for more details.
+
### Configuration & configuration schema
Each extension can define a configuration schema that describes the configuration that it accepts. This schema is used to validate the configuration provided by integrators, but also to fill in default configuration values. The configuration itself is provided by integrators in order to customize the extension. It is not possible to provide a default configuration of an extension, this must instead be done through defaults in the configuration schema. This allows for a simpler configuration logic where multiple configurations of the same extension completely replace each other rather than being merged.
diff --git a/docs/frontend-system/architecture/23-extension-blueprints.md b/docs/frontend-system/architecture/23-extension-blueprints.md
index f6efccccbb..97eb01022d 100644
--- a/docs/frontend-system/architecture/23-extension-blueprints.md
+++ b/docs/frontend-system/architecture/23-extension-blueprints.md
@@ -9,7 +9,7 @@ The `createExtension` function and related APIs is considered a low-level buildi
## Creating an extension from a blueprint
-Every extension blueprint provides a `make` method that can be used to create new extensions. It is a simple way to create a new extension where the base blueprint provides all the necessary functionality. All you need to do is to provide the necessary blueprint parameters, but you also have the ability to provide additional options, for example a `name` for the extension.
+Every extension blueprint provides a `make` method that can be used to create new extensions. It is a simple way to create a new extension where the base blueprint provides all the necessary functionality. All you need to do is to provide the necessary blueprint parameters, but you also have the ability to provide additional options, for example a `name`, `attachTo`, `disabled`, or `if` predicate for the extension.
The following is a simple example of how one might use the blueprint `make` method to create a new extension:
diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md
index 5e684476c8..22d553b084 100644
--- a/docs/frontend-system/architecture/25-extension-overrides.md
+++ b/docs/frontend-system/architecture/25-extension-overrides.md
@@ -9,7 +9,9 @@ description: Frontend extension overrides
An important customization point in the frontend system is the ability to override existing extensions. It can be used for anything from slight tweaks to the extension logic, to completely replacing an extension with a custom implementation. While extensions are encouraged to make themselves configurable, there are many situations where you need to override an extension to achieve the desired behavior. The ability to override extensions should be kept in mind when building plugins, and can be a powerful tool to allow for deeper customizations without the need to re-implement large parts of the plugin.
-In general, most features should have a good level of customization built into them, so that users do not have to leverage extension overrides to achieve common goals. A well written feature often has [configuration](../../conf/) settings, or uses extension inputs for extensibility where applicable. An example of this is the search plugin, which allows you to provide result renderers as inputs rather than replacing the result page wholesale just to tweak how results are shown. Adopters should take advantage of those when possible in order to reduce the need and size of extension overrides.
+In general, most features should have a good level of customization built into them, so that users do not have to leverage extension overrides to achieve common goals. A well written feature often has [configuration](../../conf/index.md) settings, or uses extension inputs for extensibility where applicable. An example of this is the search plugin, which allows you to provide result renderers as inputs rather than replacing the result page wholesale just to tweak how results are shown. Adopters should take advantage of those when possible in order to reduce the need and size of extension overrides.
+
+Extension overrides can also replace or remove existing `if` predicates. This applies both to direct extension overrides through `.override(...)` and to plugin-level overrides through `plugin.withOverrides(...)`. Frontend modules can use the same extension override mechanism to adjust or clear the condition for an overridden extension.
## Overriding an extension
@@ -343,3 +345,5 @@ export default app.createRoot();
```
You must define a `pluginId` when creating a frontend module, and the plugin must also be installed for the module to be loaded.
+
+Frontend modules also support an `if` option. Just like for plugins, that predicate is applied to every extension that comes from the module, and is combined with any extension-level `if` predicate using logical `AND`. This is useful when you want to enable or disable an entire override package based on a feature flag or permission.
diff --git a/docs/frontend-system/building-apps/01-index.md b/docs/frontend-system/building-apps/01-index.md
index 8610dfb7b5..d24e1622fd 100644
--- a/docs/frontend-system/building-apps/01-index.md
+++ b/docs/frontend-system/building-apps/01-index.md
@@ -18,12 +18,12 @@ The create-app CLI requires Node.js Active LTS Release, see the [prerequisites d
:::
```sh
-# The command bellow creates a Backstage App inside the current folder.
+# The command below creates a Backstage App inside the current folder.
# The name of the app-folder is the name that was provided when prompted.
-npx @backstage/create-app@latest --next
+npx @backstage/create-app@latest
```
-Using the `--next` flag will result in a Backstage app using the New Frontend System which will be further explained in the sections below.
+This will create a Backstage app using the new frontend system which will be further explained in the sections below.
## The app instance
@@ -57,19 +57,17 @@ Note that `createRoot` returns the root element that is rendered by React. The a
Visit the [built-in extensions](#customize-or-override-built-in-extensions) section to see what is installed by default in a Backstage application.
+For advanced bootstrap flows that need access to the app tree before the full app is finalized, see [preparing an app in phases](../architecture/10-app.md#preparing-an-app-in-phases).
+
## Configure your app
### Bind external routes
Linking routes from different plugins requires this configuration. You can do this either through a configuration file or by coding, visit [this](https://backstage.io/docs/frontend-system/architecture/routes#binding-external-route-references) page for instructions.
-### Enable feature discovery
+### Install plugins
-Use this setting to enable experimental feature discovery when building your app with `@backstage/cli`. With this configuration your application tries to discover and install package extensions automatically, check [here](../architecture/10-app.md#feature-discovery) for more details.
-
-:::warning
-Remember that package extensions that are not auto-discovered must be manually added to the application when creating an app. See [features](#install-features-manually) for more details.
-:::
+Plugins are typically installed by adding them as dependencies of your app package and relying on feature discovery to automatically detect them. For details on how this works, including how to manually install plugins or control which packages are discovered, see [Installing Plugins](./05-installing-plugins.md).
### Configure extensions individually
@@ -83,7 +81,7 @@ Previously you would customize the application routes, components, apis, sidebar
### Install features manually
-A manual installation is required if your packages are not discovered automatically, either because you are not using `@backstage/cli` to build your application or because the features are defined in local modules in the app package. In order to manually install a feature, you must import it and pass it to the `createApp` function:
+Most plugins are installed automatically through [feature discovery](./05-installing-plugins.md#feature-discovery). Manual installation is needed if your packages are not discovered automatically, either because you are not using `@backstage/cli` to build your application or because the features are defined in local modules in the app package. In order to manually install a feature, you must import it and pass it to the `createApp` function:
```tsx title="packages/app/src/App.tsx"
import { createApp } from '@backstage/frontend-defaults';
diff --git a/docs/frontend-system/building-apps/03-built-in-extensions.md b/docs/frontend-system/building-apps/03-built-in-extensions.md
index e1d0b0282d..637aded411 100644
--- a/docs/frontend-system/building-apps/03-built-in-extensions.md
+++ b/docs/frontend-system/building-apps/03-built-in-extensions.md
@@ -99,7 +99,7 @@ This is the extension that creates the app root element, so it renders root leve
| ---------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| router | A React component that should manager the app routes context. | It must be one [router](https://reactrouter.com/en/main/routers/picking-a-router#web-projects) component or a custom component compatible with the 'react-router' library. | true | [BrowserRouter](https://reactrouter.com/en/main/router-components/browser-router) | [createRouterExtension](https://backstage.io/docs/reference/frontend-plugin-api.createrouterextension) |
| signInPage | A React component that should render the app sign-in page. | Should call the `onSignInSuccess` prop when the user has been successfully authorized, otherwise the user will not be correctly redirected to the application home page. | true | The default `AppRoot` extension does not use a default component for this input, it bypasses the user authentication check and always renders all routes when a login page is not installed. | [createSignInPageExtension](https://backstage.io/docs/reference/frontend-plugin-api.createsigninpageextension/) |
-| children | A React component that renders the app sidebar and main content in a particular layout. | - | false | The [`App/Layout`](#app-layout) extension output. | No creator available, configure or override the [`App/Layout`](#app-layout) extension. |
+| children | A React component that renders the app sidebar and main content in a particular layout. | - | true | The [`App/Layout`](#app-layout) extension output. | No creator available, configure or override the [`App/Layout`](#app-layout) extension. |
| elements | React elements to be rendered outside of the app layout, such as shared popups. | - | false | See [default elements](#default-app-root-elements-extensions). | [createAppRootElementExtension](https://backstage.io/docs/reference/frontend-plugin-api.createapprootelementextension/) |
| wrappers | React components that should wrap the root element. | - | true | - | [createAppRootWrapperExtension](https://backstage.io/docs/reference/frontend-plugin-api.createapprootwrapperextension/) |
@@ -183,6 +183,30 @@ Be careful when overriding this extension, as to do so correctly you must consid
- Remember to user the route refs for getting paths dynamically, otherwise if an adopter modifies a path through configuration, the route is not going to point to the configured path;
- Adopters expect to be able to customize the `NotFoundErrorPage` component via Components API, you should render this component for routes not configured.
+#### Configurations
+
+| Key | Type | Default value | Description |
+| ----------- | -------------------------------- | ------------- | -------------------------------------------------------------------- |
+| `redirects` | `{ from: string, to: string }[]` | - | A list of URL redirects. Navigation to `from` will redirect to `to`. |
+
+##### Configuring redirects
+
+You can configure redirects for the `app/routes` extension to automatically redirect users from one path to another. This is useful when restructuring your app's routes, for example after moving or renaming plugin pages.
+
+```yaml title="app-config.yaml"
+app:
+ extensions:
+ - app/routes:
+ config:
+ redirects:
+ - from: /old-path
+ to: /new-path
+ - from: /legacy/page
+ to: /updated/page
+```
+
+Redirects are matched before any regular routes and use the [`replace`](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState) strategy, so the old path will not appear in the browser history.
+
#### Inputs
| Name | Description | Type | Optional | Default | Extension creator |
diff --git a/docs/frontend-system/building-apps/05-installing-plugins.md b/docs/frontend-system/building-apps/05-installing-plugins.md
new file mode 100644
index 0000000000..9007ce0eb1
--- /dev/null
+++ b/docs/frontend-system/building-apps/05-installing-plugins.md
@@ -0,0 +1,71 @@
+---
+id: installing-plugins
+title: Installing Plugins
+sidebar_label: Installing Plugins
+description: How to install frontend plugins in a Backstage app
+---
+
+Frontend plugins are installed in your Backstage app by adding them as dependencies of your app package. Most of the time this is all you need to do, as the app will automatically discover and install the plugin.
+
+## Install a plugin package
+
+To install a plugin, add it as a dependency to your app package. For example, to install the catalog plugin:
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/app add @backstage/plugin-catalog
+```
+
+If your app is set up with [feature discovery](#feature-discovery), the plugin will be automatically detected and installed in the app. No additional code changes are needed.
+
+## Feature discovery
+
+Feature discovery lets the app automatically discover and install plugins from the dependencies of your app package. This is enabled by setting `app.packages` to `all` in your `app-config.yaml`:
+
+```yaml title="app-config.yaml"
+app:
+ packages: all
+```
+
+This is the recommended setup and is the default for all new Backstage apps. With this enabled, any plugin that is added as a dependency of your app package will be automatically discovered and installed. You can use include or exclude filters to control which packages are discovered:
+
+```yaml title="app-config.yaml"
+app:
+ packages:
+ include:
+ - '@backstage/plugin-catalog'
+ - '@backstage/plugin-scaffolder'
+```
+
+```yaml title="app-config.yaml"
+app:
+ packages:
+ exclude:
+ - '@backstage/plugin-catalog'
+```
+
+Feature discovery requires that your app is built using the `@backstage/cli`, which is the default for all Backstage apps. Note that you do not need to exclude packages that you also install manually in code, since plugin instances are deduplicated by the app.
+
+For more details on how feature discovery works under the hood, see the [Feature Discovery](../architecture/10-app.md#feature-discovery) architecture documentation.
+
+## Manual installation
+
+If your app does not have [feature discovery](#feature-discovery) enabled, or if you need more control over the plugin installation, you can install plugins manually. This is done by importing the plugin and passing it to `createApp`:
+
+```tsx title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/frontend-defaults';
+import catalogPlugin from '@backstage/plugin-catalog/alpha';
+
+const app = createApp({
+ features: [catalogPlugin],
+});
+
+export default app.createRoot();
+```
+
+Manual installation may also be necessary if you need to control the ordering of plugins, for example when customizing route priorities. Since manually installed plugins are deduplicated against automatically discovered ones, you can safely install a plugin both manually and through feature discovery without causing conflicts.
+
+If you need to use a 3rd-party plugin that does not yet support the new frontend system, you can use the conversion utilities from `@backstage/core-compat-api` to wrap it. See [Converting 3rd-party Plugins](./06-plugin-conversion.md) for details.
+
+## Configuring installed plugins
+
+Once a plugin is installed, you can configure its extensions through the `app.extensions` section of your `app-config.yaml`. See [Configuring Extensions](./02-configuring-extensions.md) for details.
diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md
index 2849b7d68b..2784b10b7a 100644
--- a/docs/frontend-system/building-apps/08-migrating.md
+++ b/docs/frontend-system/building-apps/08-migrating.md
@@ -800,6 +800,22 @@ If you are using [app feature discovery](../architecture/10-app.md#feature-disco
Continue this process for each of your legacy routes until you have migrated all of them. For any plugin with additional extensions installed as children of the `Route`, refer to the plugin READMEs for more detailed instructions. For the entity pages, refer to the [separate section](#catalog-entity-page).
+##### Migrating `` routes
+
+If your old routes include `` elements to forward users from one path to another, you can replace them with the built-in redirect configuration on the `app/routes` extension:
+
+```yaml title="app-config.yaml"
+app:
+ extensions:
+ - app/routes:
+ config:
+ redirects:
+ - from: /old-path
+ to: /new-path
+```
+
+See the [`app/routes` built-in extension documentation](./03-built-in-extensions.md#configuring-redirects) for more details.
+
### Migrating core, internal and third-party plugins
For certain core plugins — such as the Catalog plugin's entity page — we provide a dedicated step-by-step migration guide, since these plugins often require a more gradual approach due to their complexity.
@@ -899,39 +915,7 @@ It's encouraged that once you switch over to using the new frontend system, that
This practice is also pretty important early on, as it's going to help you get familiar with the practices of the new frontend system.
-When creating a new Backstage app with `create-app` and using the `--next` flag you'll automatically get these choices in the `yarn new` command, but if you want to bring these templates to an older app, you can add the following to your root `package.json`:
-
-```json
-{
- ...
- "scripts": {
- ...
- "new": "backstage-cli new"
- },
- "backstage": {
- "cli": {
- "new": {
- "globals": {
- "license": "UNLICENSED"
- },
- "templates": [
- "@backstage/cli/templates/new-frontend-plugin",
- "@backstage/cli/templates/new-frontend-plugin-module",
- "@backstage/cli/templates/backend-plugin",
- "@backstage/cli/templates/backend-plugin-module",
- "@backstage/cli/templates/plugin-web-library",
- "@backstage/cli/templates/plugin-node-library",
- "@backstage/cli/templates/plugin-common-library",
- "@backstage/cli/templates/web-library",
- "@backstage/cli/templates/node-library",
- "@backstage/cli/templates/catalog-provider-module",
- "@backstage/cli/templates/scaffolder-backend-module"
- ]
- }
- }
- }
-}
-```
+The `yarn new` command now defaults to the new frontend system templates for frontend plugins. If you have an older app that was created before this change, you can simply update the `@backstage/cli-module-new` package to get access to the new templates.
## Troubleshooting
diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md
index d9acf76e15..9d5507c012 100644
--- a/docs/frontend-system/building-plugins/01-index.md
+++ b/docs/frontend-system/building-plugins/01-index.md
@@ -11,7 +11,7 @@ frontend _features_, and what you install to build up a Backstage frontend [app]
## Creating a new plugin
-This guide assumes that you already have a Backstage project set up. Even if you only want to develop a single plugin for publishing, we still recommend that you do so in a standard Backstage monorepo project, as you often end up needing multiple packages. For instructions on how to set up a new project, see our [getting started](../../getting-started/index.md#prerequisites) documentation.
+This guide assumes that you already have a Backstage project set up. Even if you only want to develop a single plugin for publishing, we still recommend that you do so in a standard Backstage monorepo project, as you often end up needing multiple packages. For instructions on how to set up a new project, see our [getting started](../../getting-started/index.md) documentation.
To create a frontend plugin, run `yarn new`, select `plugin`, and fill out the rest of the prompts. This will create a new package at `plugins/`, which will be the main entrypoint for your plugin.
@@ -110,6 +110,21 @@ What we've built here is a very common type of plugin. It's a top-level tool tha
We have also provided external access to our route reference by passing it to the plugin `routes` option. This makes it possible for app integrators to bind an external link from a different plugin to our plugin page. You can read more about how this works in the [External Route References](../architecture/36-routes.md#external-route-references) section.
+## Running a dev server
+
+Each frontend plugin package has a `dev/` folder that is used as the entry point when you run `yarn start`. This is a convenient way to run your plugin in isolation during development. The `@backstage/frontend-dev-utils` package provides a `createDevApp` helper that sets up a minimal app for this purpose:
+
+```tsx title="in dev/index.ts"
+import { createDevApp } from '@backstage/frontend-dev-utils';
+import myPlugin from '../src';
+
+createDevApp({ features: [myPlugin] });
+```
+
+This will create and render a Backstage app with only your plugin installed. If you need to include additional features that your plugin depends on, pass them along in the `features` array. You can also use `bindRoutes` to wire up any external routes that your plugin depends on.
+
+The dev setup is started by running `yarn start` in the plugin directory, which uses the `backstage-cli package start` command. It sets up a local development server with hot reloading, just like a full app.
+
## Utility APIs
Another type of extensions that is commonly used are [Utility APIs](../utility-apis/01-index.md). They can encapsulate shared pieces of functionality of your plugin, for example an API client for a backend service. You can optionally export your Utility API for other plugins to use, or allow integrators to replace the implementation of your Utility API with their own. For details on how to define and provide your own Utility API in your plugin, see the section on [creating Utility APIs](../utility-apis/02-creating.md).
@@ -223,3 +238,11 @@ export const examplePlugin = createFrontendPlugin({
The `ExampleEntityContent` itself is again a regular React component where you can implement any functionality you want. To access the entity that the content is being rendered for, you can use the `useEntity` hook from `@backstage/plugin-catalog-react`. You can see a full list of APIs provided by the catalog React library in [the API reference](https://backstage.io/api/stable/modules/_backstage_plugin-catalog-react.index.html).
For a more complete list of the different kinds of extensions that you can create for your plugin, see the [extension blueprints](./03-common-extension-blueprints.md) section.
+
+## Related topics
+
+The following guides cover cross-cutting concerns for building frontend plugins:
+
+- [Internationalization (i18n)](./07-internationalization.md) — Adding translations to your plugin using `createTranslationRef` and `useTranslationRef`.
+- [Plugin Analytics](./08-analytics.md) — Instrumenting user interactions with the Analytics API using `AnalyticsImplementationBlueprint`.
+- [Feature Flags](./09-feature-flags.md) — Defining and using feature flags via the `featureFlags` option of `createFrontendPlugin`.
diff --git a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md
index 5a3fb530a4..e8c9ea2ffb 100644
--- a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md
+++ b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md
@@ -11,25 +11,25 @@ This section covers many of the [extension blueprints](../architecture/23-extens
These are the [extension blueprints](../architecture/23-extension-blueprints.md) provided by the Backstage frontend framework itself.
-### Api - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.ApiBlueprint.html)
+### Api - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.ApiBlueprint.html)
An API extension is used to add or override [Utility API factories](../utility-apis/01-index.md) in the app. They are commonly used by plugins for both internal and shared APIs. There are also many built-in Api extensions provided by the framework that you are able to override.
-### NavItem (deprecated) - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.NavItemBlueprint.html)
+### NavItem (deprecated) - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.NavItemBlueprint.html)
The `NavItemBlueprint` is deprecated. The app now auto-discovers navigation items from page extensions, so explicit nav item extensions are no longer needed. To migrate, ensure your plugin and/or page extensions have a `title` and `icon` set — these are used to populate the sidebar automatically.
-### Page - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.PageBlueprint.html)
+### Page - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.PageBlueprint.html)
Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes. Pages automatically inherit the plugin's `title` and `icon` as defaults, which can be overridden per-page via `PageBlueprint` params.
To enable sub-pages on a page, you can either omit the `loader` param to use the built-in default implementation that renders sub-pages as tabs, or provide a custom `loader` that explicitly handles the sub-page inputs.
-### SubPage - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.SubPageBlueprint.html)
+### SubPage - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.SubPageBlueprint.html)
Sub-page extensions create tabbed content within a parent page. They are attached to a page extension's `pages` input and rendered as tabs in the page header. Each sub-page has a `path` (relative to the parent page), a `title` for the tab, and an optional `icon`. Content is lazy-loaded via a `loader` function.
-### PluginHeaderAction - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.PluginHeaderActionBlueprint.html)
+### PluginHeaderAction - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.PluginHeaderActionBlueprint.html)
Plugin header action extensions provide plugin-scoped actions that appear in the page header. They are automatically scoped to the plugin that provides them and will appear in the header of all pages belonging to that plugin. Actions are lazy-loaded via a `loader` function that returns a React element.
@@ -41,23 +41,23 @@ Plugin wrappers allow you to install components that will wrap all elements rend
## Extension blueprints in `@backstage/plugin-app-react`
-### SignInPage - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.SignInPageBlueprint.html)
+### SignInPage - [Reference](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.SignInPageBlueprint.html)
Sign-in page extension have a single purpose - to implement a custom sign-in page. They are always attached to the app root extension and are rendered before the rest of the app until the user is signed in.
-### SwappableComponent - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.SwappableComponentBlueprint.html)
+### SwappableComponent - [Reference](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.SwappableComponentBlueprint.html)
Swappable Components are extensions that are used to replace the implementations of components in the app and plugins.
-### Theme - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.ThemeBlueprint.html)
+### Theme - [Reference](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.ThemeBlueprint.html)
Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use.
-### Icons - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.IconBundleBlueprint.html)
+### Icons - [Reference](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.IconBundleBlueprint.html)
Icon bundle extensions provide the ability to replace or provide new icons to the app. You can use the above blueprint to make new extension instances which can be installed into the app.
-### Translation - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.TranslationBlueprint.html)
+### Translation - [Reference](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.TranslationBlueprint.html)
Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages.
@@ -67,7 +67,7 @@ Nav content extensions allow you to replace the entire navbar with your own comp
Your custom component receives a `navItems` prop—a collection with `take(id)` and `rest()` methods for placing specific items in custom positions. Nav items are auto-discovered from page extensions, and metadata (title, icon) comes from page config, nav item extensions, or plugin defaults. Use `navItems.take('page:home')` to take a specific item by extension ID, and `navItems.rest()` to get all remaining items. The deprecated `items` prop (a flat list) remains supported for backward compatibility.
-### Router - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.RouterBlueprint.html)
+### Router - [Reference](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.RouterBlueprint.html)
Router extensions allow you to replace the router component used by the app. They are always attached to the app root extension.
diff --git a/docs/frontend-system/building-plugins/07-internationalization.md b/docs/frontend-system/building-plugins/07-internationalization.md
new file mode 100644
index 0000000000..3e882cb8e2
--- /dev/null
+++ b/docs/frontend-system/building-plugins/07-internationalization.md
@@ -0,0 +1,406 @@
+---
+id: internationalization
+title: Internationalization
+sidebar_label: Internationalization
+description: Adding internationalization to plugins and apps
+---
+
+## Overview
+
+The Backstage core function provides internationalization for plugins and apps. The underlying library is [`i18next`](https://www.i18next.com/) with some additional Backstage typescript magic for type safety with keys.
+
+## For a plugin developer
+
+When you are creating your plugin, you have the possibility to use `createTranslationRef` to define all messages for your plugin. For example:
+
+```ts
+import { createTranslationRef } from '@backstage/frontend-plugin-api';
+
+/** @alpha */
+export const myPluginTranslationRef = createTranslationRef({
+ id: 'plugin.my-plugin',
+ messages: {
+ indexPage: {
+ title: 'All your components',
+ createButtonTitle: 'Create new component',
+ },
+ entityPage: {
+ notFound: 'Entity not found',
+ },
+ },
+});
+```
+
+And then use these messages in your components like:
+
+```tsx
+import { useTranslationRef } from '@backstage/frontend-plugin-api';
+
+const { t } = useTranslationRef(myPluginTranslationRef);
+
+return (
+
+
+ {t('indexPage.createButtonTitle')}
+
+
+);
+```
+
+You will see how the initial dictionary structure and nesting get converted into dot notation, so we encourage `camelCase` in key names and lean on the nesting structure to separate keys.
+
+### Guidelines for `i18n` messages and keys
+
+The API for `i18n` messages and keys can be pretty tricky to get right, as it's a pretty flexible API. We've put together some guidelines to help you get started that encourage good practices when thinking about translating plugins:
+
+#### Key names
+
+When defining messages it is recommended to use a nested structure that represents the semantic hierarchy in your translations. This allows for better organization and understanding of the structure. For example:
+
+```ts
+export const myPluginTranslationRef = createTranslationRef({
+ id: 'plugin.my-plugin',
+ messages: {
+ dashboardPage: {
+ title: 'All your components',
+ subtitle: 'Create new component',
+ widgets: {
+ weather: {
+ title: 'Weather',
+ description: 'Shows the weather',
+ },
+ calendar: {
+ title: 'Calendar',
+ description: 'Shows the calendar',
+ },
+ },
+ },
+ entityPage: {
+ notFound: 'Entity not found',
+ },
+ },
+});
+```
+
+Think about the semantic placement of content rather than the text content itself. Group related translations under a common prefix, and use nesting to represent relationships between different parts of your application. It's good to start grouping under extensions, page sections, or visual scopes and experiences.
+
+Translations should avoid using their own text content as key where possible, as this can lead to confusion if the translation changes. Instead prefer to use keys that describe the location or usage of the text.
+
+#### Common Key names
+
+This list is intended to grow over time, but below are some examples of common key names and patterns that we encourage you to use where possible:
+
+- `${page}.title`
+- `${page}.subtitle`
+- `${page}.description`
+
+- `${page}.header.title`
+
+#### Key reuse
+
+Reusing the same key in multiple places is discouraged. This helps prevent ambiguity, and instead keeps the usage of each key as clear as possible. Consider creating duplicate keys that are grouped under a semantic section instead.
+
+#### Flat keys
+
+Avoid a flat key structure at the root level, as it can lead to naming conflicts and make the translation file harder to manage and change evolve over time. Instead, group translations under a common prefix.
+
+```ts
+export const myPluginTranslationRef = createTranslationRef({
+ id: 'plugin.my-plugin',
+ messages: {
+ // this is BAD
+ title: 'My page',
+ subtitle: 'My subtitle',
+ // this is GOOD
+ dashboardPage: {
+ header: {
+ title: 'All your components',
+ subtitle: 'Create new component',
+ },
+ },
+ },
+});
+```
+
+#### Plurals
+
+The `i18next` library, which is used as the underlying implementation, has built-in support for pluralization. You can use this feature as is described in [the documentation](https://www.i18next.com/translation-function/plurals).
+
+We encourage you to use this feature and avoid creating different key prefixes for pluralized content. For example:
+
+```ts
+export const myPluginTranslationRef = createTranslationRef({
+ id: 'plugin.my-plugin',
+ messages: {
+ dashboardPage: {
+ title: 'All your components',
+ subtitle: 'Create new component',
+ cards: {
+ title_one: 'You have one card',
+ title_two: 'You have two cards',
+ title_other: 'You have many cards ({{count}})',
+ },
+ },
+ entityPage: {
+ notFound: 'Entity not found',
+ },
+ },
+});
+```
+
+#### JSX Elements
+
+The translation API supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string.
+
+For example, you might define the following messages:
+
+```ts title="define the message"
+export const myPluginTranslationRef = createTranslationRef({
+ id: 'plugin.my-plugin',
+ messages: {
+ entityPage: {
+ redirect: {
+ message: 'The entity you are looking for has been moved to {{link}}.',
+ link: 'new location',
+ },
+ },
+ },
+});
+```
+
+Which can be used within a component like this:
+
+```tsx title="use within a component"
+const { t } = useTranslationRef(myPluginTranslationRef);
+
+return (
+
+);
+```
+
+The return type of the outer `t` function will be a `JSX.Element`, with the underlying value being a React fragment of the different parts of the message.
+
+## For an application developer
+
+As an app developer you can both override the default English messages of any plugin, and provide translations for additional languages.
+
+### Overriding messages
+
+To customize specific messages without adding new languages, create a translation extension using `TranslationBlueprint` from `@backstage/plugin-app-react` together with `createTranslationMessages` from `@backstage/frontend-plugin-api`:
+
+```ts
+import { createTranslationMessages } from '@backstage/frontend-plugin-api';
+import { TranslationBlueprint } from '@backstage/plugin-app-react';
+import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';
+
+const catalogTranslations = TranslationBlueprint.make({
+ name: 'catalog-overrides',
+ params: {
+ resource: createTranslationMessages({
+ ref: catalogTranslationRef,
+ messages: {
+ 'indexPage.title': 'Service directory',
+ 'indexPage.createButtonTitle': 'Register new service',
+ },
+ }),
+ },
+});
+```
+
+Then install it as a feature in your app:
+
+```ts
+import { createApp } from '@backstage/frontend-defaults';
+
+const app = createApp({
+ features: [catalogTranslations],
+});
+```
+
+You only need to include the keys you want to override — any missing keys fall back to the plugin's defaults.
+
+### Adding language translations
+
+To add support for additional languages, create a translation resource with lazy-loaded message files for each language, and install it using `TranslationBlueprint`:
+
+```ts
+import { createTranslationResource } from '@backstage/frontend-plugin-api';
+import { TranslationBlueprint } from '@backstage/plugin-app-react';
+import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha';
+
+const userSettingsTranslations = TranslationBlueprint.make({
+ name: 'user-settings-zh',
+ params: {
+ resource: createTranslationResource({
+ ref: userSettingsTranslationRef,
+ translations: {
+ zh: () => import('./userSettings-zh'),
+ },
+ }),
+ },
+});
+```
+
+The translation messages can be defined using `createTranslationMessages` for type safety:
+
+```ts
+// packages/app/src/translations/userSettings-zh.ts
+
+import { createTranslationMessages } from '@backstage/frontend-plugin-api';
+import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha';
+
+const zh = createTranslationMessages({
+ ref: userSettingsTranslationRef,
+ full: false, // False means that this is a partial translation
+ messages: {
+ 'languageToggle.title': '语言',
+ 'languageToggle.select': '选择{{language}}',
+ },
+});
+
+export default zh;
+```
+
+Or as a plain object export:
+
+```ts
+// packages/app/src/translations/userSettings-zh.ts
+export default {
+ 'languageToggle.title': '语言',
+ 'languageToggle.select': '选择{{language}}',
+ 'languageToggle.description': '切换语言',
+ 'themeToggle.title': '主题',
+ 'themeToggle.description': '切换主题',
+ 'themeToggle.select': '选择{{theme}}',
+ 'themeToggle.selectAuto': '选择自动主题',
+ 'themeToggle.names.auto': '自动',
+ 'themeToggle.names.dark': '暗黑',
+ 'themeToggle.names.light': '明亮',
+};
+```
+
+Install the translation extension in your app:
+
+```ts
+import { createApp } from '@backstage/frontend-defaults';
+
+const app = createApp({
+ features: [userSettingsTranslations],
+});
+```
+
+Go to the Settings page — you should see language switching buttons. Switch languages to verify your translations are loaded correctly.
+
+### Using the CLI for full translation workflows
+
+When translating your app to other languages at scale — especially when working with external translation systems — the Backstage CLI provides `translations export` and `translations import` commands that automate the extraction and wiring of translation messages across all your plugin dependencies.
+
+#### Exporting default messages
+
+From your app package directory (e.g. `packages/app`), run:
+
+```bash
+yarn backstage-cli translations export
+```
+
+This scans all frontend plugin dependencies (including transitive ones) for `TranslationRef` definitions and writes their default English messages as JSON files:
+
+```text
+translations/
+ manifest.json
+ messages/
+ catalog.en.json
+ org.en.json
+ scaffolder.en.json
+ ...
+```
+
+Each `.en.json` file contains the flattened message keys and their default values:
+
+```json
+{
+ "indexPage.title": "All your components",
+ "indexPage.createButtonTitle": "Create new component",
+ "entityPage.notFound": "Entity not found"
+}
+```
+
+#### Creating translations
+
+Copy the exported files and translate them for your target languages:
+
+```bash
+cp translations/messages/catalog.en.json translations/messages/catalog.zh.json
+```
+
+Then edit `catalog.zh.json` with the translated strings. You only need to include the keys you want to translate — missing keys fall back to the English defaults at runtime.
+
+#### Generating wiring code
+
+Once you have translated files in place, run:
+
+```bash
+yarn backstage-cli translations import
+```
+
+This generates a TypeScript module at `src/translations/resources.ts` that wires everything together:
+
+```ts
+// This file is auto-generated by backstage-cli translations import
+// Do not edit manually.
+
+import { createTranslationResource } from '@backstage/frontend-plugin-api';
+import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';
+
+export default [
+ createTranslationResource({
+ ref: catalogTranslationRef,
+ translations: {
+ zh: () => import('../../translations/messages/catalog.zh.json'),
+ },
+ }),
+];
+```
+
+Install the generated resources as features in your app:
+
+```ts
+import { createApp } from '@backstage/frontend-defaults';
+import translationResources from './translations/resources';
+
+const app = createApp({
+ features: translationResources,
+});
+```
+
+#### Custom file patterns
+
+By default, message files use the pattern `messages/{id}.{lang}.json` (e.g. `messages/catalog.en.json`). You can change this with the `--pattern` option:
+
+```bash
+yarn backstage-cli translations export --pattern '{lang}/{id}.json'
+```
+
+This produces a directory structure grouped by language instead:
+
+```text
+translations/en/catalog.json
+translations/zh/catalog.json
+```
+
+The pattern is stored in the manifest, so the `import` command automatically uses the same layout.
+
+#### Integration with external translation systems
+
+The exported JSON files are standard key-value pairs compatible with most external translation systems. A typical workflow looks like:
+
+1. Run `translations export` to generate the source English files
+2. Upload the `.en.json` files to your translation system
+3. Download the translated files back into the translations directory
+4. Run `translations import` to regenerate the wiring code
+
+For full command reference, see the [CLI commands documentation](../../tooling/cli/03-commands.md#translations-export).
diff --git a/docs/frontend-system/building-plugins/08-analytics.md b/docs/frontend-system/building-plugins/08-analytics.md
new file mode 100644
index 0000000000..0b51b5ae1a
--- /dev/null
+++ b/docs/frontend-system/building-plugins/08-analytics.md
@@ -0,0 +1,317 @@
+---
+id: analytics
+title: Plugin Analytics
+sidebar_label: Analytics
+description: Measuring usage of your Backstage instance
+---
+
+Setting up, maintaining, and iterating on an instance of Backstage can be a
+large investment. To help measure return on this investment, Backstage comes
+with an event-based Analytics API that grants app integrators the flexibility to
+collect and analyze Backstage usage in the analytics tool of their choice, while
+providing plugin developers a standard interface for instrumenting key user
+interactions.
+
+## Concepts
+
+- **Events** consist of, at a minimum, an `action` (like `click`) and a
+ `subject` (like `thing that was clicked on`).
+- **Attributes** represent additional dimensional data (in the form of key/value
+ pairs) that may be provided on an event-by-event basis. To continue the above
+ example, the URL a user clicked to might look like `{ "to": "/a/page" }`.
+- **Context** represents the broader context in which an event took place. By
+ default, information like `pluginId`, `extension`, and `routeRef` are
+ provided.
+
+This composition of events aims to allow analysis at different levels of detail,
+enabling very granular questions (like "what is the most clicked on thing on a
+particular route") as well as very high-level questions (like "what is the most
+used plugin in my Backstage instance") to be answered.
+
+## Supported Analytics Tools
+
+While all that's needed to consume and forward these events to an analytics tool
+is a concrete implementation of [AnalyticsApi][analytics-api-type], common
+integrations are packaged and provided as plugins. Find your analytics tool of
+choice below.
+
+| Analytics Tool | Support Status |
+| ------------------------------------- | -------------- |
+| [Google Analytics][ga] | Yes ✅ |
+| [Google Analytics 4][ga4] | Yes ✅ |
+| [New Relic Browser][newrelic-browser] | Community ✅ |
+| [Matomo][matomo] | Community ✅ |
+| [Quantum Metric][qm] | Community ✅ |
+| [Generic HTTP][generic-http] | Community ✅ |
+
+To suggest an integration, please [open an issue][add-tool] for the analytics
+tool your organization uses. Or jump to [Writing Integrations][int-howto] to
+learn how to contribute the integration yourself!
+
+[ga]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga/README.md
+[ga4]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga4/README.md
+[newrelic-browser]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-newrelic-browser/README.md
+[qm]: https://github.com/quantummetric/analytics-module-qm/blob/main/README.md
+[matomo]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-matomo/README.md
+[add-tool]: https://github.com/backstage/backstage/issues/new?assignees=&labels=plugin&template=plugin_template.md&title=%5BAnalytics+Module%5D+THE+ANALYTICS+TOOL+TO+INTEGRATE
+[int-howto]: #writing-integrations
+[analytics-api-type]: https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.AnalyticsApi.html
+[generic-http]: https://github.com/pfeifferj/backstage-plugin-analytics-generic/blob/main/README.md
+
+## Key Events
+
+The following table summarizes events that, depending on the plugins you have
+installed, may be captured.
+
+| Action | Subject | Other Notes |
+| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `navigate` | The URL of the page that was navigated to. | Fired immediately when route location changes (unless associated plugin/route data is ambiguous, in which case the event is fired after plugin/route data becomes known, immediately before the next event or document unload). The parameters of the current route will be included as attributes. |
+| `click` | The text of the link that was clicked on. | The `to` attribute represents the URL clicked to. |
+| `create` | The `name` of the software being created; if no `name` property is requested by the given Software Template, then the string `new {templateName}` is used instead. | The context holds an `entityRef`, set to the template's ref (e.g. `template:default/template-name`). The `value` represents the number of minutes saved by running the template (based on the template's `backstage.io/time-saved` annotation, if available). |
+| `search` | The search term entered in any search bar component. | The context holds `searchTypes`, representing `types` constraining the search. The `value` represents the total number of search results for the query. This may not be visible if the permission framework is being used. |
+| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided. |
+| `not-found` | The path of the resource that resulted in a not found page | Fired by at least TechDocs. |
+
+If there is an event you'd like to see captured, please [open an issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md&title=[Analytics%20Event]:%20THE+EVENT+TO+CAPTURE) describing the event you want to see and the questions it
+would help you answer. Or jump to [Capturing Events](#capturing-events) to learn how
+to contribute the instrumentation yourself!
+
+_OSS plugin maintainers: feel free to document your events in the table above._
+
+## Writing Integrations
+
+Analytics event forwarding is implemented as a Backstage [Utility API](../utility-apis/01-index.md). The
+provided API need only provide a single method `captureEvent`, which takes
+an `AnalyticsEvent` object.
+
+A simple implementation using `AnalyticsImplementationBlueprint`:
+
+```ts
+import { AnalyticsImplementationBlueprint } from '@backstage/plugin-app-react';
+
+export const acmeAnalyticsImplementation =
+ AnalyticsImplementationBlueprint.make({
+ name: 'acme',
+ params: define =>
+ define({
+ deps: {},
+ factory() {
+ return {
+ captureEvent: event => {
+ window._AcmeAnalyticsQ.push(event);
+ },
+ };
+ },
+ }),
+ });
+```
+
+In reality, you would likely want to encapsulate instantiation logic and pull
+some details from configuration. A more complete example might look like:
+
+```ts
+import {
+ AnalyticsApi,
+ AnalyticsEvent,
+ configApiRef,
+} from '@backstage/frontend-plugin-api';
+import { AnalyticsImplementationBlueprint } from '@backstage/plugin-app-react';
+import { AcmeAnalytics } from 'acme-analytics';
+
+class AcmeAnalyticsImpl implements AnalyticsApi {
+ private constructor(accountId: number) {
+ AcmeAnalytics.init(accountId);
+ }
+
+ static fromConfig(config) {
+ const accountId = config.getString('app.analytics.acme.id');
+ return new AcmeAnalyticsImpl(accountId);
+ }
+
+ captureEvent(event: AnalyticsEvent) {
+ const { action, ...rest } = event;
+ AcmeAnalytics.send(action, rest);
+ }
+}
+
+export const acmeAnalyticsImplementation =
+ AnalyticsImplementationBlueprint.make({
+ name: 'acme',
+ params: define =>
+ define({
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) => AcmeAnalyticsImpl.fromConfig(configApi),
+ }),
+ });
+```
+
+If you are integrating with an analytics service (as opposed to an internal
+tool), consider contributing your API implementation as a plugin!
+
+By convention, such packages should be named
+`@backstage/analytics-module-[name]`, and any configuration should be keyed
+under `app.analytics.[name]`.
+
+### Handling User Identity
+
+If the analytics platform you are integrating with has a first-class concept of
+user identity, you can (optionally) choose to support this by the following this
+convention:
+
+- Allow your implementation to be instantiated with the `identityApi` as one of
+ its dependencies.
+- Use the `userEntityRef` resolved by `identityApi`'s `getBackstageIdentity()`
+ method as the basis for the user ID you send to your analytics platform.
+
+## Capturing Events
+
+To instrument an event in a component, start by retrieving an analytics tracker
+using the `useAnalytics()` hook provided by `@backstage/frontend-plugin-api`. The
+tracker includes a `captureEvent` method which takes an `action` and a `subject`
+as arguments.
+
+```ts
+import { useAnalytics } from '@backstage/frontend-plugin-api';
+
+const analytics = useAnalytics();
+analytics.captureEvent('deploy', serviceName);
+```
+
+### Providing Extra Attributes
+
+Additional dimensional `attributes` as well as a numeric `value` can be provided
+on a third `options` argument if/when relevant for the event:
+
+```ts
+analytics.captureEvent('merge', pullRequestName, {
+ value: pullRequestAgeInMinutes,
+ attributes: {
+ org,
+ repo,
+ },
+});
+```
+
+In the above example, an event resembling the following object would be
+captured:
+
+```json
+{
+ "action": "merge",
+ "subject": "Name of Pull Request",
+ "value": 60,
+ "attributes": {
+ "org": "some-org",
+ "repo": "some-repo"
+ }
+}
+```
+
+### Providing Context for Events
+
+The `attributes` option is good for capturing details available to you within
+the component that you're instrumenting. For capturing metadata only available
+further up the react tree, or to help app integrators aggregate distinct events
+by some common value, use an ``.
+
+```tsx
+import { AnalyticsContext, useAnalytics } from '@backstage/frontend-plugin-api';
+
+const MyComponent = ({ value }) => {
+ const analytics = useAnalytics();
+ const handleClick = () => analytics.captureEvent('check', value);
+ return ;
+};
+
+const MyWrapper = () => {
+ return (
+
+
+
+ );
+};
+```
+
+In the above example, clicking on ` ` would result in an analytics
+event resembling:
+
+```json
+{
+ "action": "check",
+ "subject": "Some Value",
+ "context": {
+ "segment": "xyz"
+ }
+}
+```
+
+Note that, for brevity in the example above, the context keys provided by
+Backstage core (`pluginId`, `extension`, and `routeRef`) have been omitted. In
+reality, those details would be included alongside any additional context
+provided by you.
+
+Analytics contexts can be nested; their values are merged down the react tree,
+allowing keys to be overwritten.
+
+### Event Naming Considerations
+
+An event is split into its constituent parts to enable analysis at various
+levels of granularity. In order to maintain this flexibility at analysis-time,
+it's important to keep each of these levels of detail disaggregated.
+
+- Avoid providing an overly specific `action`. For example, instead of
+ `filterEntityTable`, consider just using `filter` as the action, and allowing
+ `EntityTable` to be specified as part of the event's `context` (most likely
+ automatically as part of the `extension` in which the `filter` event was
+ captured).
+
+- On the flip side, when adding `attributes` to or `context` around an event,
+ look at existing events and see if the data you are capturing matches the
+ intention, type, or even the content of _their_ `attributes` or `context`.
+ For instance, it's common for events that involve the Catalog to include an
+ `entityRef` contextual key. Using the same keys and values in your event will
+ ensure that events instrumented across plugins can easily be aggregated.
+
+### Unit Testing Event Capture
+
+The `@backstage/frontend-test-utils` package includes a `MockAnalyticsApi` implementation
+that you can use in your unit tests to spy on and make assertions about any
+analytics events captured.
+
+Use it like this:
+
+```tsx
+import { render, fireEvent, waitFor } from '@testing-library/react';
+import { analyticsApiRef } from '@backstage/frontend-plugin-api';
+import {
+ MockAnalyticsApi,
+ TestApiProvider,
+ wrapInTestApp,
+} from '@backstage/frontend-test-utils';
+
+describe('SomeComponent', () => {
+ it('should capture event on click', () => {
+ const apiSpy = new MockAnalyticsApi();
+
+ const { getByText } = render(
+ wrapInTestApp(
+
+
+ ,
+ ),
+ );
+
+ fireEvent.click(getByText('some component text'));
+
+ await waitFor(() => {
+ expect(apiSpy.getEvents()[0]).toMatchObject({
+ action: 'expected action',
+ subject: 'expected subject',
+ attributes: {
+ foo: 'bar',
+ },
+ });
+ });
+ });
+});
+```
diff --git a/docs/frontend-system/building-plugins/09-feature-flags.md b/docs/frontend-system/building-plugins/09-feature-flags.md
new file mode 100644
index 0000000000..06ee56be24
--- /dev/null
+++ b/docs/frontend-system/building-plugins/09-feature-flags.md
@@ -0,0 +1,78 @@
+---
+id: feature-flags
+title: Feature Flags
+sidebar_label: Feature Flags
+description: Defining and using feature flags in plugins and apps
+---
+
+Backstage offers the ability to define feature flags inside a plugin or during application creation. This allows you to restrict parts of your plugin to those individual users who have toggled the feature flag to on.
+
+This page describes the process of defining, setting and reading a feature flag. If you are looking for using feature flags specifically with software templates please see [Writing Templates](https://backstage.io/docs/features/software-templates/writing-templates#remove-sections-or-fields-based-on-feature-flags).
+
+## Defining a Feature Flag
+
+### In a plugin
+
+Feature flags are declared via the `featureFlags` option in `createFrontendPlugin`:
+
+```ts title="src/plugin.ts"
+import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
+
+export const examplePlugin = createFrontendPlugin({
+ pluginId: 'example',
+ featureFlags: [
+ {
+ name: 'show-example-feature',
+ description: 'Enables the new beta dashboard view',
+ },
+ ],
+ extensions: [
+ // ...
+ ],
+});
+```
+
+Note that the `description` property is optional. If not provided, the default "Registered in {pluginId} plugin" message is shown.
+
+### In the application
+
+Defining a feature flag in the application is done by adding feature flags in the `featureFlags` array in the
+`createApp()` function call:
+
+```ts title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/frontend-defaults';
+
+const app = createApp({
+ // ...
+ featureFlags: [
+ {
+ name: 'tech-radar',
+ description: 'Enables the tech radar plugin',
+ },
+ ],
+ // ...
+});
+```
+
+## Enabling Feature Flags
+
+Feature flags are defaulted to off and can be updated by individual users in the backstage interface. These are set by navigating to the page under `Settings` > `Feature Flags`.
+
+The user's selection is saved in the user's browser local storage. Once a feature flag is toggled it may be required for a user to refresh the page to see the change.
+
+## Evaluating Feature Flag State
+
+You can query a feature flag using the [FeatureFlagsApi](https://backstage.io/api/stable/interfaces/_backstage_frontend-plugin-api.index.FeatureFlagsApi.html):
+
+```tsx
+import { useApi, featureFlagsApiRef } from '@backstage/frontend-plugin-api';
+
+function MyComponent() {
+ const featureFlagsApi = useApi(featureFlagsApiRef);
+
+ if (featureFlagsApi.isActive('show-example-feature')) {
+ return ;
+ }
+ return ;
+}
+```
diff --git a/docs/getting-started/config/authentication--old.md b/docs/getting-started/config/authentication--old.md
new file mode 100644
index 0000000000..f719d87181
--- /dev/null
+++ b/docs/getting-started/config/authentication--old.md
@@ -0,0 +1,213 @@
+---
+id: authentication--old
+title: Authentication
+description: How to setup authentication for your Backstage app
+---
+
+:::info
+This documentation is written for the old frontend system. If you are on the [new frontend system](../../frontend-system/index.md) you may want to read [its own article](./authentication.md) instead.
+:::
+
+Audience: Admins or Developers
+
+## Summary
+
+We'll be walking you through how to setup authentication for your Backstage app using GitHub. After finishing this guide, you'll have both working authentication and users in your Backstage app to match to the users logging in!
+
+There are multiple authentication providers available for you to use with Backstage, feel free to follow [their instructions for adding authentication](../../auth/index--old.md).
+
+:::note Note
+
+The default Backstage app comes with a guest Sign In Resolver. This resolver makes all users share a single "guest" identity and is only intended as a minimum requirement to quickly get up and running. You can read more about how [Sign In Resolvers](../../auth/identity-resolver.md#sign-in-resolvers) play a role in creating a [Backstage User Identity](../../auth/identity-resolver.md#backstage-user-identity) for logged in users.
+
+:::
+
+## Setting up authentication
+
+For this tutorial we choose to use GitHub, a free service most of you might be familiar with, and we'll be using an OAuth app. For detailed options, see
+[the GitHub auth provider documentation](../../auth/github/provider.md#create-an-oauth-app-on-github).
+
+Go to [https://github.com/settings/applications/new](https://github.com/settings/applications/new) to create your OAuth App. The "Homepage URL" should point to Backstage's frontend, in our tutorial it would be `http://localhost:3000`. The "Authorization callback URL" will point to the auth backend, which will most likely be `http://localhost:7007/api/auth/github/handler/frame`.
+
+
+
+Take note of the `Client ID` and the `Client Secret` (clicking the "Generate a new client secret" button will get this value for you). Open `app-config.yaml`, and add them as `clientId` and `clientSecret` in this file. It should end up looking like this:
+
+```yaml title="app-config.yaml"
+auth:
+ # see https://backstage.io/docs/auth/ to learn about auth providers
+ /* highlight-add-start */
+ environment: development
+ /* highlight-add-end */
+ providers:
+ # See https://backstage.io/docs/auth/guest/provider
+ guest: {}
+ /* highlight-add-start */
+ github:
+ development:
+ clientId: YOUR CLIENT ID
+ clientSecret: YOUR CLIENT SECRET
+ /* highlight-add-end */
+```
+
+## Add sign-in option to the frontend
+
+The next step is to change the sign-in page. For this, you'll actually need to write some code.
+
+Open `packages/app/src/App.tsx` and below the last `import` line, add:
+
+```typescript title="packages/app/src/App.tsx"
+import { githubAuthApiRef } from '@backstage/core-plugin-api';
+```
+
+Search for `const app = createApp({` in this file, and replace:
+
+```tsx title="packages/app/src/App.tsx"
+components: {
+ SignInPage: props => ,
+},
+```
+
+with
+
+```tsx title="packages/app/src/App.tsx"
+components: {
+ SignInPage: props => (
+
+ ),
+},
+```
+
+## Add sign-in resolver(s)
+
+Next we need to add the sign-in resolver to our configuration. Here's how:
+
+```yaml title="app-config.yaml"
+auth:
+ # see https://backstage.io/docs/auth/ to learn about auth providers
+ environment: development
+ providers:
+ # See https://backstage.io/docs/auth/guest/provider
+ guest: {}
+ github:
+ development:
+ clientId: YOUR CLIENT ID
+ clientSecret: YOUR CLIENT SECRET
+ /* highlight-add-start */
+ signIn:
+ resolvers:
+ # Matches the GitHub username with the Backstage user entity name.
+ # See https://backstage.io/docs/auth/github/provider#resolvers for more resolvers.
+ - resolver: usernameMatchingUserEntityName
+ /* highlight-add-end */
+```
+
+What this will do is take the user details provided by the auth provider and match that against a User in the Catalog. In this case - `usernameMatchingUserEntityName` - will match the GitHub user name with the `metadata.name` value of a User in the Catalog, if none is found you will get an "Failed to sign-in, unable to resolve user identity" message. We'll cover this in the next few sections.
+
+Learn more about this topic in the [Sign-in Resolvers](../../auth/identity-resolver.md#sign-in-resolvers) documentation.
+
+## Add the auth provider to the backend
+
+To add the auth provider to the backend, we will first need to install the package by running this command:
+
+```bash title="from your Backstage root directory"
+yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-github-provider
+```
+
+Then we will need to add this line:
+
+```ts title="in packages/backend/src/index.ts"
+backend.add(import('@backstage/plugin-auth-backend'));
+/* highlight-add-start */
+backend.add(import('@backstage/plugin-auth-backend-module-github-provider'));
+/* highlight-add-end */
+```
+
+Restart Backstage from the terminal, by stopping it with `Ctrl+C`, and starting it with `yarn start`. You should be welcomed by a login prompt! If you try to login at this point you will get a "Failed to sign-in, unable to resolve user identity" message, read on as we'll fix that next.
+
+:::note Note
+
+Sometimes the frontend starts before the backend resulting in errors on the sign in page. Wait for the backend to start and then reload Backstage to proceed.
+
+:::
+
+## Adding a User
+
+The recommended approach for adding Users, and Groups, into your Catalog is to use one of the existing Org Entity Providers - [like this one for GitHub](https://backstage.io/docs/integrations/github/org) - or if those don't work you may need to [create one](https://backstage.io/docs/features/software-catalog/external-integrations#custom-entity-providers) that fits your Organization's needs.
+
+For the sake of this guide we'll simply step you though adding a User to the `org.yaml` file that is included when you create a new Backstage instance. Let's do that:
+
+1. First open the `/examples/org.yaml` file in your text editor of choice
+2. At the bottom we'll add the following YAML:
+
+ ```yaml
+ ---
+ apiVersion: backstage.io/v1alpha1
+ kind: User
+ metadata:
+ name: YOUR GITHUB USERNAME
+ spec:
+ memberOf: [guests]
+ ```
+
+3. Now make sure to replace the text "YOUR GITHUB USERNAME" with your actual GitHub User name.
+
+Let's restart Backstage from the terminal once more, by stopping it with `Ctrl+C`, and starting it with `yarn start`. You should now be able to log into Backstage and see items in your Catalog.
+
+To learn more about Authentication in Backstage, here are some docs you
+could read:
+
+- [Authentication in Backstage](../../auth/index.md)
+- [Using organizational data from GitHub](../../integrations/github/org.md)
+
+## Setting up a GitHub Integration
+
+The GitHub integration supports loading catalog entities from GitHub or GitHub Enterprise. Entities can be added to static catalog configuration, registered with the catalog-import plugin, or discovered from a GitHub organization. Users and Groups can also be loaded from an organization. While using [GitHub Apps](../../integrations/github/github-apps.md) might be the best way to set up integrations, for this tutorial you'll use a Personal Access Token.
+
+Create your Personal Access Token by opening [the GitHub token creation page](https://github.com/settings/tokens/new). Use a name to identify this token and put it in the notes field. Choose a number of days for expiration. If you have a hard time picking a number, we suggest to go for 7 days, it's a lucky number.
+
+
+
+Set the scope to your likings. For this tutorial, selecting `repo` and `workflow` is required as the scaffolding job in this guide configures a GitHub actions workflow for the newly created project.
+
+For this tutorial, we will be writing the token to `app-config.local.yaml`. This file might not exist for you, so if it doesn't go ahead and create it alongside the `app-config.yaml` at the root of the project. This file should also be excluded in `.gitignore`, to avoid accidental committing of this file. More details on this file can be found in the [Static Configuration documentation](../../conf/index.md).
+
+In your `app-config.local.yaml` go ahead and add the following:
+
+```yaml title="app-config.local.yaml"
+integrations:
+ github:
+ - host: github.com
+ token: ghp_urtokendeinfewinfiwebfweb # this should be the token from GitHub
+```
+
+That's settled. This information will be leveraged by other plugins.
+
+If you're looking for a more production way to manage this secret, then you can do the following with the token being stored in an environment variable called `GITHUB_TOKEN`.
+
+```yaml title="app-config.local.yaml"
+integrations:
+ github:
+ - host: github.com
+ token: ${GITHUB_TOKEN} # this will use the environment variable GITHUB_TOKEN
+```
+
+:::note Note
+
+If you've updated the configuration for your integration, it's likely that the backend will need a restart to apply these changes. To do this, stop the running instance in your terminal with `Control-C`, then start it again with `yarn start`. Once the backend has restarted, retry the operation.
+
+:::
+
+Some helpful links, for if you want to learn more about:
+
+- [Other available integrations](../../integrations/index.md)
+- [Using GitHub Apps instead of a Personal Access Token](../../integrations/github/github-apps.md)
diff --git a/docs/getting-started/config/authentication.md b/docs/getting-started/config/authentication.md
index e25f357ff8..3fc5fbe69e 100644
--- a/docs/getting-started/config/authentication.md
+++ b/docs/getting-started/config/authentication.md
@@ -4,6 +4,10 @@ title: Authentication
description: How to setup authentication for your Backstage app
---
+:::info
+This documentation is written for [the new frontend system](../../frontend-system/index.md). If you are on the old frontend system you may want to read [its own article](./authentication--old.md) instead.
+:::
+
Audience: Admins or Developers
## Summary
@@ -50,37 +54,63 @@ auth:
The next step is to change the sign-in page. For this, you'll actually need to write some code.
-Open `packages/app/src/App.tsx` and below the last `import` line, add:
+First let's add the packages we need, do this from the root:
+
+```shell
+yarn --cwd packages/app add @backstage/core-plugin-api @backstage/plugin-app-react
+```
+
+Then open `packages/app/src/App.tsx` and below the last `import` line, add:
```typescript title="packages/app/src/App.tsx"
import { githubAuthApiRef } from '@backstage/core-plugin-api';
+import { SignInPageBlueprint } from '@backstage/plugin-app-react';
+import { SignInPage } from '@backstage/core-components';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
```
-Search for `const app = createApp({` in this file, and replace:
+Now below this we are going to use the `SignInPageBlueprint` to create an extension, add this code block to do that:
+
+```tsx
+const signInPage = SignInPageBlueprint.make({
+ params: {
+ loader: async () => props =>
+ (
+
+ ),
+ },
+});
+```
+
+Search for the `createApp()` function call in this file, and replace:
```tsx title="packages/app/src/App.tsx"
-components: {
- SignInPage: props => ,
-},
+export default createApp({
+ features: [catalogPlugin, navModule],
+});
```
with
```tsx title="packages/app/src/App.tsx"
-components: {
- SignInPage: props => (
-
- ),
-},
+export default createApp({
+ features: [
+ catalogPlugin,
+ navModule,
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [signInPage],
+ }),
+ ],
+});
```
## Add sign-in resolver(s)
@@ -107,7 +137,7 @@ auth:
/* highlight-add-end */
```
-What this will do is take the user details provided by the auth provider and match that against a User in the Catalog. In this case - `usernameMatchingUserEntityName` - will match the GitHub user name with the `metadata.name` value of a User in the Catalog, if none is found you will get an "Failed to sign-in, unable to resolve user identity" message. We'll cover this in the next few sections.
+What this will do is take the user details provided by the auth provider and match that against a User in the Catalog. In this case - `usernameMatchingUserEntityName` - will match the GitHub user name with the `metadata.name` value of a User in the Catalog, if none is found you will get a "Failed to sign-in, unable to resolve user identity" message. We'll cover this in the next few sections.
Learn more about this topic in the [Sign-in Resolvers](../../auth/identity-resolver.md#sign-in-resolvers) documentation.
@@ -140,7 +170,7 @@ Sometimes the frontend starts before the backend resulting in errors on the sign
The recommended approach for adding Users, and Groups, into your Catalog is to use one of the existing Org Entity Providers - [like this one for GitHub](https://backstage.io/docs/integrations/github/org) - or if those don't work you may need to [create one](https://backstage.io/docs/features/software-catalog/external-integrations#custom-entity-providers) that fits your Organization's needs.
-For the sake of this guide we'll simply step you though adding a User to the `org.yaml` file that is included when you create a new Backstage instance. Let's do that:
+For the sake of this guide we'll simply step you through adding a User to the `org.yaml` file that is included when you create a new Backstage instance. Let's do that:
1. First open the `/examples/org.yaml` file in your text editor of choice
2. At the bottom we'll add the following YAML:
@@ -206,4 +236,4 @@ If you've updated the configuration for your integration, it's likely that the b
Some helpful links, for if you want to learn more about:
- [Other available integrations](../../integrations/index.md)
-- [Using GitHub Apps instead of a Personal Access Token](../../integrations/github/github-apps.md#docsNav)
+- [Using GitHub Apps instead of a Personal Access Token](../../integrations/github/github-apps.md)
diff --git a/docs/getting-started/config/database.md b/docs/getting-started/config/database.md
index 7f85e173c9..cfad91fc62 100644
--- a/docs/getting-started/config/database.md
+++ b/docs/getting-started/config/database.md
@@ -190,7 +190,7 @@ backend:
# highlight-remove-end
```
-[Start the Backstage app](../index.md#2-run-the-backstage-app):
+[Start the Backstage app](../index.md#creating-and-running-a-backstage-application):
```shell
yarn start
diff --git a/docs/getting-started/configure-app-with-plugins--old.md b/docs/getting-started/configure-app-with-plugins--old.md
new file mode 100644
index 0000000000..1c9898fe27
--- /dev/null
+++ b/docs/getting-started/configure-app-with-plugins--old.md
@@ -0,0 +1,131 @@
+---
+id: configure-app-with-plugins--old
+title: Configuring App with plugins (Old Frontend System)
+description: Documentation on How Configuring App with plugins
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./configure-app-with-plugins.md) instead.
+::::
+
+Audience: Developers
+
+:::note Note
+Backstage plugins are primarily written using [TypeScript](https://www.typescriptlang.org), [Node.js](https://nodejs.org) and [React](https://reactjs.org). Having an understanding of these technologies will be beneficial on your journey to customizing Backstage!
+:::
+
+## Summary
+
+Backstage plugins customize the app for your needs. There is a
+[plugin directory](https://backstage.io/plugins) with plugins for many common
+infrastructure needs - CI/CD, monitoring, auditing, and more.
+
+## Adding existing plugins to your app
+
+The following steps assume that you have
+[created a Backstage app](./index.md) and want to add an existing plugin
+to it.
+
+You can find many wonderful plugins out there for Backstage, for example through the [Community Plugins Repository](https://github.com/backstage/community-plugins) and the [Backstage Plugin Directory](https://backstage.io/plugins).
+
+Adding plugins to your Backstage app is generally a simple process, and ideally each plugin will come with its own documentation on how to install and configure it. In this example we will add the [Tech Radar plugin](https://github.com/backstage/community-plugins/tree/main/workspaces/tech-radar/plugins/tech-radar) to our Backstage app.
+
+1. Add the plugin's npm package to the repo:
+
+ ```bash title="From your Backstage root directory"
+ yarn --cwd packages/app add @backstage-community/plugin-tech-radar
+ ```
+
+ Note the plugin is added to the `app` package, rather than the root
+ `package.json`. Backstage Apps are set up as monorepos with
+ [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/). Frontend UI Plugins are generally added to the `app` folder, while Backend Plugins are added to the `backend` folder. In the example above, the plugin is added to the `app` package because we are adding the frontend plugin.
+
+2. Now, modify your app routes to include the Router component exported from the tech radar, for example:
+
+ ```tsx title="packages/app/src/App.tsx"
+ /* highlight-add-start */
+ import { TechRadarPage } from '@backstage-community/plugin-tech-radar';
+ /* highlight-add-end */
+
+ const routes = (
+
+ /* highlight-add-start */
+ }
+ />
+ /* highlight-add-end */
+
+ );
+ ```
+
+ This is just one example, and if you'd like to continue adding the Tech Radar plugin you can do so by going [here](https://github.com/backstage/community-plugins/tree/main/workspaces/tech-radar/plugins/tech-radar), keep in mind each Backstage instance may integrate content or
+ cards to suit their needs on different pages, tabs, etc. In addition, while some
+ plugins such as this example are designed to be used in a stand-alone fashion,
+ others may be intended to annotate or support specific software catalog entities
+ and would be added elsewhere in the app.
+
+### Adding a plugin page to the Sidebar
+
+In a standard Backstage app created with
+[@backstage/create-app](./index.md), the sidebar is managed inside
+`packages/app/src/components/Root/Root.tsx`. The file exports the entire
+`Sidebar` element of your app, which you can extend with additional entries by
+adding new `SidebarItem` elements.
+
+For example, if you install the `api-docs` plugin, a matching `SidebarItem`
+could be something like this:
+
+```tsx title="packages/app/src/components/Root/Root.tsx"
+// Import icon from Material UI
+import ExtensionIcon from '@material-ui/icons/Extension';
+
+// ... inside the AppSidebar component
+ ;
+```
+
+You can also use your own SVGs directly as icon components. Just make sure they
+are sized according to the Material UI's
+[SvgIcon](https://material-ui.com/api/svg-icon/) default of 24x24px, and wrap
+the SVG elements in a `SvgIcon` component like this:
+
+```tsx
+import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
+
+export const ExampleIcon = (props: SvgIconProps) => (
+
+
+
+
+
+
+);
+```
+
+On mobile devices the `Sidebar` is displayed at the bottom of the screen. For
+customizing the experience you can group `SidebarItems` in a `SidebarGroup`
+(Example 1) or create a `SidebarGroup` with a link (Example 2). All
+`SidebarGroup`s are displayed in the bottom navigation with an icon.
+
+```tsx
+// Example 1
+ } label="Menu">
+ ...
+
+ ...
+
+```
+
+```tsx
+// Example 2
+ } to="/search">
+ ...
+
+ ...
+
+```
+
+If no `SidebarGroup` is provided a default menu will display the `Sidebar`
+content.
diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md
index 010917f6c8..0bd69d4144 100644
--- a/docs/getting-started/configure-app-with-plugins.md
+++ b/docs/getting-started/configure-app-with-plugins.md
@@ -4,6 +4,13 @@ title: Configuring App with plugins
description: Documentation on How Configuring App with plugins
---
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./configure-app-with-plugins--old.md)
+instead.
+::::
+
Audience: Developers
:::note Note
@@ -26,100 +33,64 @@ You can find many wonderful plugins out there for Backstage, for example through
Adding plugins to your Backstage app is generally a simple process, and ideally each plugin will come with its own documentation on how to install and configure it. In this example we will add the [Tech Radar plugin](https://github.com/backstage/community-plugins/tree/main/workspaces/tech-radar/plugins/tech-radar) to our Backstage app.
-1. Add the plugin's npm package to the repo:
+### 1. Install the plugin package
- ```bash title="From your Backstage root directory"
- yarn --cwd packages/app add @backstage-community/plugin-tech-radar
- ```
+Add the plugin's npm package to the repo:
- Note the plugin is added to the `app` package, rather than the root
- `package.json`. Backstage Apps are set up as monorepos with
- [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/). Frontend UI Plugins are generally added to the `app` folder, while Backend Plugins are added to the `backend` folder. In the example above, the plugin is added to the `app` package because we are adding the frontend plugin.
-
-2. Now, modify your app routes to include the Router component exported from the tech radar, for example:
-
- ```tsx title="packages/app/src/App.tsx"
- /* highlight-add-start */
- import { TechRadarPage } from '@backstage-community/plugin-tech-radar';
- /* highlight-add-end */
-
- const routes = (
-
- /* highlight-add-start */
- }
- />
- /* highlight-add-end */
-
- );
- ```
-
- This is just one example, and if you'd like to continue adding the Tech Radar plugin you can do so by going [here](https://github.com/backstage/community-plugins/tree/main/workspaces/tech-radar/plugins/tech-radar), keep in mind each Backstage instance may integrate content or
- cards to suit their needs on different pages, tabs, etc. In addition, while some
- plugins such as this example are designed to be used in a stand-alone fashion,
- others may be intended to annotate or support specific software catalog entities
- and would be added elsewhere in the app.
-
-### Adding a plugin page to the Sidebar
-
-In a standard Backstage app created with
-[@backstage/create-app](./index.md), the sidebar is managed inside
-`packages/app/src/components/Root/Root.tsx`. The file exports the entire
-`Sidebar` element of your app, which you can extend with additional entries by
-adding new `SidebarItem` elements.
-
-For example, if you install the `api-docs` plugin, a matching `SidebarItem`
-could be something like this:
-
-```tsx title="packages/app/src/components/Root/Root.tsx"
-// Import icon from Material UI
-import ExtensionIcon from '@material-ui/icons/Extension';
-
-// ... inside the AppSidebar component
- ;
+```bash title="From your Backstage root directory"
+yarn --cwd packages/app add @backstage-community/plugin-tech-radar
```
-You can also use your own SVGs directly as icon components. Just make sure they
-are sized according to the Material UI's
-[SvgIcon](https://material-ui.com/api/svg-icon/) default of 24x24px, and wrap
-the SVG elements in a `SvgIcon` component like this:
+Note the plugin is added to the `app` package, rather than the root
+`package.json`. Backstage Apps are set up as monorepos with
+[Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/). Frontend UI plugins are generally added to the `app` folder, while backend plugins are added to the `backend` folder.
-```tsx
-import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
+### 2. Verify the plugin is available
-export const ExampleIcon = (props: SvgIconProps) => (
-
-
-
-
-
-
-);
+With the new frontend system, plugins are automatically discovered and installed in your app through [feature discovery](../frontend-system/building-apps/05-installing-plugins.md#feature-discovery). This is enabled by the default `app-config.yaml` setting:
+
+```yaml title="app-config.yaml"
+app:
+ packages: all
```
-On mobile devices the `Sidebar` is displayed at the bottom of the screen. For
-customizing the experience you can group `SidebarItems` in a `SidebarGroup`
-(Example 1) or create a `SidebarGroup` with a link (Example 2). All
-`SidebarGroup`s are displayed in the bottom navigation with an icon.
+Once the package is installed, the plugin is ready to use — no code changes needed! Start your app with `yarn start` and navigate to `/tech-radar` to see the plugin in action.
-```tsx
-// Example 1
- } label="Menu">
- ...
-
- ...
-
+This is just one example, and if you'd like to continue adding the Tech Radar plugin you can do so by going [here](https://github.com/backstage/community-plugins/tree/main/workspaces/tech-radar/plugins/tech-radar). Keep in mind each Backstage instance may integrate content or cards to suit their needs on different pages, tabs, etc. In addition, while some plugins such as this example are designed to be used in a stand-alone fashion, others may be intended to annotate or support specific software catalog entities and would be added elsewhere in the app.
+
+### 3. Optional: Configure the plugin
+
+Plugins can be further customized through configuration in your `app-config.yaml`. For example, you can configure extension settings under the `app.extensions` section:
+
+```yaml title="app-config.yaml"
+app:
+ extensions:
+ - page:tech-radar:
+ config:
+ path: /tech-radar
```
-```tsx
-// Example 2
- } to="/search">
- ...
-
- ...
-
+For details on what configuration options are available, refer to the plugin's own documentation or the [Configuring Extensions](../frontend-system/building-apps/02-configuring-extensions.md) guide.
+
+### 4. Optional: Manually install a plugin
+
+If you need more control over plugin installation, or if [feature discovery](../frontend-system/building-apps/05-installing-plugins.md#feature-discovery) is not enabled, you can install plugins manually by importing them and passing them to `createApp`:
+
+```tsx title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/frontend-defaults';
+import techRadarPlugin from '@backstage-community/plugin-tech-radar/alpha';
+
+const app = createApp({
+ features: [techRadarPlugin],
+});
+
+export default app.createRoot();
```
-If no `SidebarGroup` is provided a default menu will display the `Sidebar`
-content.
+For more details and alternative installation methods, see [Installing Plugins](../frontend-system/building-apps/05-installing-plugins.md).
+
+## How the sidebar works
+
+In the new frontend system, plugins that provide a page automatically register a navigation item in the sidebar. You don't need to manually add `SidebarItem` elements for most plugins.
+
+If you want to customize sidebar behavior — for example, reordering items, grouping them, or adding custom entries — you can override the built-in `app/nav` extension. See the [migration guide's sidebar section](../frontend-system/building-apps/08-migrating.md#app-root-sidebar) for details on how to create a custom sidebar layout.
diff --git a/docs/getting-started/create-a-component.md b/docs/getting-started/create-a-component.md
index 29ba99db83..b4e5c6ab19 100644
--- a/docs/getting-started/create-a-component.md
+++ b/docs/getting-started/create-a-component.md
@@ -6,38 +6,92 @@ description: Leverage the scaffolder to start creating components with best prac
Audience: Developers
-## Summary
+## Overview
-This guide will walk you through how to use Software Templates to create new components with baked in best practices.
+Components can be created in the Software Catalog using software templates. Templates load skeletons of code, which can include some variables, and incorporate your company's best practices. The templates are published to a location, such as GitHub or GitLab.
+
+The standalone Backstage application includes the `Example Node.js Template`, which is an example template for the scaffolder that creates and registers a simple Node.js service. You can also [create your own templates](../features/software-templates/adding-templates.md).
## Prerequisites
-:::note
+For this example, the default Node.js template will be used. The template creates a repository in GitHub and adds the necessary files to it so that the component is integrated into the Software Catalog. Because you are creating a repository, you must first create an integration between Backstage and GitHub.
-If you're running Backstage with Node 20 or later, you'll need to pass the flag `--no-node-snapshot` to Node in order to use the templates feature. One way to do this is to specify the `NODE_OPTIONS` environment variable before starting Backstage: `export NODE_OPTIONS=--no-node-snapshot`
+- You should have already [installed a standalone app](./index.md).
-:::
+- Register the [GitHub Scaffolder Action module](../features/software-templates/builtin-actions.md#installing-action-modules).
-You should already have [a standalone app](./index.md).
+- [Set up a GitHub Integration](../getting-started/config/authentication.md#setting-up-a-github-integration) with Backstage, using a GitHub Personal Access Token.
-You will also need to register the [GitHub Scaffolder Action module](../features/software-templates/builtin-actions.md#installing-action-modules) before moving forward.
+## Creating the component
-## Creating your component
+To create the component:
-- Go to `create` and choose to create a website with the `Example Node.js Template`
-- Type in a name, let's use `tutorial` and click `Next Step`
+1. Select `Create`.
-
+ 
-- You should see the following screen:
+2. Select `Service` in the `CATEGORIES` dropdown list.
+3. Select the `Owner`. For this example, you can select `guest`.
+4. Select `Choose` in the `Example Node.js Template`.
-
+ 
-- For host, it should default to github.com
-- As owner, type your GitHub username
-- For the repository name, type `tutorial`. Go to the next step
+5. For this example, enter `tutorial` for the `Name` of the service and select `NEXT`.
-- Review the details of this new service, and press `Create` if you want to
- deploy it like this.
-- You can follow along with the progress, and as soon as every step is
- finished, you can take a look at your new service
+ 
+
+6. Enter your GitHub user name as the `Owner`.
+7. Enter `tutorial` for the `Repository` and select `REVIEW`.
+
+ 
+
+8. Review the information and select `CREATE`.
+
+ 
+
+If you see an error message, similar to the following,
+
+ 
+
+Perform the following steps:
+
+1. Close the Backstage app.
+2. Enter `CTRL-C` in the terminal window to stop the Backstage frontend and backend.
+3. In the terminal window, enter:
+
+ ```
+ export NODE_OPTIONS=--no-node-snapshot
+ ```
+
+ > **NOTE:**
+ > The [no-node-snapshot](../features/software-templates/index.md#prerequisites) `NODE_OPTIONS` environment variable is required in order to use the templates.
+
+4. Enter `yarn start` to restart the Backstage application.
+5. Repeat steps to create the component.
+
+Otherwise, you can follow along with the progress, and as soon as every step is finished, you can take a look at your new service in either the repository or the Catalog.
+
+
+
+Selecting `REPOSITORY` displays the `catalog-info.yaml`file and other project setup files that were created for the new component in the main branch of the `tutorial` repository.
+
+The `catalog-info.yaml` file describes the entity for the Software Catalog. [Descriptor Format of Catalog Entities](../features/software-catalog/descriptor-format.md) provides additional information.
+
+```
+ apiVersion: backstage.io/v1alpha1
+ kind: Component
+ metadata:
+ name: "tutorial"
+ spec:
+ type: service
+ owner: user:guest
+ lifecycle: experimental
+```
+
+Selecting `OPEN IN CATALOG` displays details of the new component, such as its relationships, links, and subcomponents.
+
+
+
+Selecting `Home` in the sidebar, displays the new `tutorial` component in the Catalog.
+
+
diff --git a/docs/getting-started/filter-catalog.md b/docs/getting-started/filter-catalog.md
new file mode 100644
index 0000000000..3e0de8cdeb
--- /dev/null
+++ b/docs/getting-started/filter-catalog.md
@@ -0,0 +1,57 @@
+---
+id: filter-catalog
+title: Filtering the Catalog
+description: Filtering the Catalog.
+---
+
+Audience: All
+
+## Overview
+
+The Catalog can be filtered by any combination of owner, kind, type, lifecycle, processing status, namespace, and name. [Customize Filters](../features/software-catalog/catalog-customization.md#customize-filters) provides information on how to modify the available filter criteria.
+
+
+
+The [Technical Overview](../overview/technical-overview.md#software-catalog-system-model) provides a description of the types of entities displayed in the Catalog.
+
+## Filtering the Catalog
+
+You can filter the Catalog using a combination of the following:
+
+- **Filter by name**
+
+ Enter one or more consecutive letters into the `Filter` field. As you type the letters, the entities whose names do not contain that string will be filtered out of the displayed list.
+
+ 
+
+- **Filter by kind**
+
+ Use the `Kind` dropdown list to select which kind of entity to show in the list:
+
+ - API
+ - Component
+ - Group
+ - Location
+ - System
+ - Template
+ - User
+
+- **Filter by Type**
+
+ Use the `Type` dropdown list to select which type of entity to show in the list. The selections available in the dropdown list depend on the kind of entity selected in the `Kind` list, and the types of entity you have registered for that kind.
+
+- **Filter by Owner**
+
+ Use the `Owner` dropdown to filter the Catalog list by who owns the entity.
+
+- **Filter by Lifecycle**
+
+ Use the `Lifecycle` dropdown to filter the Catalog list by lifecycle.
+
+- **Filter by Processing Status**
+
+ Use the `Processing Status` dropdown to restrict the displayed list to only include those entities which are [orphaned](../features/software-catalog/life-of-an-entity.md#orphaning) or [in error](../features/software-catalog/life-of-an-entity.md#errors).
+
+- **Filter by Namespace**
+
+ Use the `Namespace` dropdown to filter the catalog list by namespace associated with the entity.
diff --git a/docs/getting-started/homepage--old.md b/docs/getting-started/homepage--old.md
new file mode 100644
index 0000000000..34a3afdbbf
--- /dev/null
+++ b/docs/getting-started/homepage--old.md
@@ -0,0 +1,201 @@
+---
+id: homepage--old
+title: Backstage homepage - Setup and Customization (Old Frontend System)
+description: Documentation on setting up and customizing Backstage homepage
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current homepage guide](./homepage.md) instead.
+::::
+
+## Homepage
+
+Having a good Backstage homepage can significantly improve the discoverability
+of the platform. You want your users to find all the things they need right
+from the homepage and never have to remember direct URLs in Backstage. The
+[Home plugin](https://github.com/backstage/backstage/tree/master/plugins/home)
+introduces a system for composing a homepage for Backstage in order to surface
+relevant info and provide convenient shortcuts for common tasks. It's designed
+with composability in mind with an open ecosystem that allows anyone to
+contribute with any component, to be included in any homepage.
+
+For App Integrators, the system is designed to be composable to give total
+freedom in designing a Homepage that suits the needs of the organization. From
+the perspective of a Component Developer who wishes to contribute with building
+blocks to be included in Homepages, there's a convenient interface for bundling
+the different parts and exporting them with both error boundary and lazy
+loading handled under the surface.
+
+At the end of this tutorial, you can expect:
+
+- Your Backstage app to have a dedicated homepage instead of Software Catalog.
+- Understand the composability of homepage and how to start customizing it for
+ your own organization.
+
+### Prerequisites
+
+Before we begin, make sure
+
+- You have created your own standalone Backstage app using
+ [`@backstage/create-app`](./index.md#creating-and-running-a-backstage-application) and not
+ using a fork of the [backstage](https://github.com/backstage/backstage)
+ repository.
+- You do not have an existing homepage, and by default you are redirected to
+ Software Catalog when you open Backstage.
+
+Now, let's get started by installing the home plugin and creating a simple
+homepage for your Backstage app.
+
+## Setup
+
+### 1. Install the plugin
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/app add @backstage/plugin-home
+```
+
+### 2. Create a new HomePage component
+
+Inside your `packages/app` directory, create a new file where our new homepage
+component is going to live. Create `packages/app/src/components/home/HomePage.tsx`
+with the following initial code
+
+```tsx
+export const HomePage = () => (
+ /* We will shortly compose a pretty homepage here. */
+ Welcome to Backstage!
+);
+```
+
+### 3. Update router for the root `/` route
+
+If you don't have a homepage already, most likely you have a redirect setup to
+use the Catalog homepage as a homepage.
+
+Inside your `packages/app/src/App.tsx`, look for
+
+```tsx title="packages/app/src/App.tsx"
+const routes = (
+
+
+ {/* ... */}
+
+);
+```
+
+Let's replace the `` line and use the new component we created in the
+previous step as the new homepage.
+
+```tsx title="packages/app/src/App.tsx"
+/* highlight-add-start */
+import { HomepageCompositionRoot } from '@backstage/plugin-home';
+import { HomePage } from './components/home/HomePage';
+/* highlight-add-end */
+
+const routes = (
+
+ {/* highlight-remove-next-line */}
+
+ {/* highlight-add-start */}
+ }>
+
+
+ {/* highlight-add-end */}
+ {/* ... */}
+
+);
+```
+
+### 4. Update sidebar items
+
+Let's update the route for "Home" in the Backstage sidebar to point to the new
+homepage. We'll also add a Sidebar item to quickly open Catalog.
+
+| Before | After |
+| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
+|  |  |
+
+The code for the Backstage sidebar is most likely inside your
+[`packages/app-legacy/src/components/Root/Root.tsx`](https://github.com/backstage/backstage/blob/master/packages/app-legacy/src/components/Root/Root.tsx).
+
+Let's make the following changes
+
+```tsx title="packages/app/src/components/Root/Root.tsx"
+/* highlight-add-next-line */
+import CategoryIcon from '@material-ui/icons/Category';
+
+export const Root = ({ children }: PropsWithChildren<{}>) => (
+
+
+
+ {/* ... */}
+ }>
+ {/* Global nav, not org-specific */}
+ {/* highlight-remove-next-line */}
+
+ {/* highlight-add-start */}
+
+
+ {/* highlight-add-end */}
+
+
+
+
+ {/* End global nav */}
+
+ {/* ... */}
+
+
+
+);
+```
+
+That's it! You should now have _(although slightly boring)_ a homepage!
+
+
+
+
+
+In the next steps, we will make it interesting and useful!
+
+#### Use the default template
+
+There is a default homepage template
+([storybook link](https://backstage.io/storybook/?path=/story/plugins-home-templates--default-template))
+which we will use to set up our homepage. Checkout the
+[blog post announcement](https://backstage.io/blog/2022/01/25/backstage-homepage-templates)
+about the Backstage homepage templates for more information.
+
+
+
+#### Composing your homepage
+
+Composing a homepage is no different from creating a regular React Component,
+i.e. the App Integrator is free to include whatever content they like. However,
+there are components developed with the homepage in mind. If you are looking
+for components to use when composing your homepage, you can take a look at the
+[collection of Homepage components](https://backstage.io/storybook?path=/story/plugins-home-components)
+in storybook. If you don't find a component that suits your needs but want to
+contribute, check the
+[Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing).
+
+> If you want to use one of the available homepage templates you can find the
+> [templates](https://backstage.io/storybook/?path=/story/plugins-home-templates)
+> in the storybook under the "Home" plugin. And if you would like to contribute
+> a template, please see the
+> [Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing)
+
+```tsx
+import Grid from '@material-ui/core/Grid';
+import { HomePageCompanyLogo } from '@backstage/plugin-home';
+
+export const HomePage = () => (
+
+
+
+
+
+);
+```
diff --git a/docs/getting-started/homepage.md b/docs/getting-started/homepage.md
index 05e422610a..810b891d65 100644
--- a/docs/getting-started/homepage.md
+++ b/docs/getting-started/homepage.md
@@ -4,6 +4,13 @@ title: Backstage homepage - Setup and Customization
description: Documentation on setting up and customizing Backstage homepage
---
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./homepage--old.md)
+instead.
+::::
+
## Homepage
Having a good Backstage homepage can significantly improve the discoverability of the platform. You want your users to find all the things they need right from the homepage and never have to remember direct URLs in Backstage. The [Home plugin](https://github.com/backstage/backstage/tree/master/plugins/home) introduces a system for composing a homepage for Backstage in order to surface relevant info and provide convenient shortcuts for common tasks. It's designed with composability in mind with an open ecosystem that allows anyone to contribute with any component, to be included in any homepage.
@@ -19,44 +26,22 @@ At the end of this tutorial, you can expect:
Before we begin, make sure
-- You have created your own standalone Backstage app using [`@backstage/create-app`](./index.md#1-create-your-backstage-app) and not using a fork of the [backstage](https://github.com/backstage/backstage) repository.
+- You have created your own standalone Backstage app using [`@backstage/create-app`](./index.md#creating-and-running-a-backstage-application) and not using a fork of the [backstage](https://github.com/backstage/backstage) repository.
- You do not have an existing homepage, and by default you are redirected to Software Catalog when you open Backstage.
Now, let's get started by installing the home plugin and creating a simple homepage for your Backstage app.
-## Setup Methods
+## Setup
-There are two ways to set up the home plugin, depending on which frontend system your Backstage app uses:
-
-1. **New Frontend System (Recommended)** - For apps using the new plugin system with extensions and blueprints
-2. **Legacy Frontend System** - For existing apps using the legacy plugin architecture
-
-### New Frontend System Setup
-
-If your Backstage app uses the [new frontend system](../frontend-system/index.md), follow these steps:
-
-#### 1. Install the plugin
+### 1. Install the plugin
```bash title="From your Backstage root directory"
yarn --cwd packages/app add @backstage/plugin-home
```
-#### 2. Add the plugin to your app configuration
+Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](../frontend-system/building-apps/05-installing-plugins.md).
-Update your `packages/app/src/app.tsx` to include the home plugin:
-
-```tsx title="packages/app/src/app.tsx"
-import homePlugin from '@backstage/plugin-home/alpha';
-
-const app = createApp({
- features: [
- // ... other plugins
- homePlugin,
- ],
-});
-```
-
-#### 3. Configure the homepage as your root route
+### 2. Configure the homepage as your root route
By default, the homepage will be available at `/home`. To make it your app's landing page at `/`, add this configuration to your `app-config.yaml`:
@@ -70,7 +55,7 @@ app:
The plugin will automatically add a "Home" navigation item to your sidebar and provide a basic homepage layout.
-#### 4. Optional: Enable visit tracking
+### 3. Optional: Enable visit tracking
Visit tracking is an optional feature that allows users to see their recently visited and most visited pages on the homepage. This feature is **disabled by default** to give you control over what data is collected and stored.
@@ -88,156 +73,12 @@ app:
- app-root-element:home/visit-listener: true
```
-#### 5. Customizing your homepage
+### 4. Customizing your homepage
-The New Frontend System provides powerful customization options:
+The home plugin provides powerful customization options:
**Custom Homepage Layouts**: Use the `HomePageLayoutBlueprint` from `@backstage/plugin-home-react/alpha` to create custom homepage layouts with your own design and widget arrangements. A layout receives the installed widgets and is responsible for rendering them. If no custom layout is installed, the plugin provides a built-in default.
**Adding Homepage Widgets**: Register custom widgets using the `HomePageWidgetBlueprint` from the `@backstage/plugin-home-react/alpha` package.
For detailed instructions on creating custom layouts, registering widgets, and advanced configuration options, see the [Home plugin documentation](https://github.com/backstage/backstage/tree/master/plugins/home#readme).
-
-### Legacy Frontend System Setup
-
-If your Backstage app uses the legacy frontend system, follow these steps:
-
-#### 1. Install the plugin
-
-```bash title="From your Backstage root directory"
-yarn --cwd packages/app add @backstage/plugin-home
-```
-
-#### 2. Create a new HomePage component
-
-Inside your `packages/app` directory, create a new file where our new homepage component is going to live. Create `packages/app/src/components/home/HomePage.tsx` with the following initial code
-
-```tsx
-export const HomePage = () => (
- /* We will shortly compose a pretty homepage here. */
- Welcome to Backstage!
-);
-```
-
-#### 3. Update router for the root `/` route
-
-If you don't have a homepage already, most likely you have a redirect setup to use the Catalog homepage as a homepage.
-
-Inside your `packages/app/src/App.tsx`, look for
-
-```tsx title="packages/app/src/App.tsx"
-const routes = (
-
-
- {/* ... */}
-
-);
-```
-
-Let's replace the `` line and use the new component we created in the previous step as the new homepage.
-
-```tsx title="packages/app/src/App.tsx"
-/* highlight-add-start */
-import { HomepageCompositionRoot } from '@backstage/plugin-home';
-import { HomePage } from './components/home/HomePage';
-/* highlight-add-end */
-
-const routes = (
-
- {/* highlight-remove-next-line */}
-
- {/* highlight-add-start */}
- }>
-
-
- {/* highlight-add-end */}
- {/* ... */}
-
-);
-```
-
-#### 4. Update sidebar items
-
-Let's update the route for "Home" in the Backstage sidebar to point to the new homepage. We'll also add a Sidebar item to quickly open Catalog.
-
-| Before | After |
-| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
-|  |  |
-
-The code for the Backstage sidebar is most likely inside your [`packages/app-legacy/src/components/Root/Root.tsx`](https://github.com/backstage/backstage/blob/master/packages/app-legacy/src/components/Root/Root.tsx).
-
-Let's make the following changes
-
-```tsx title="packages/app/src/components/Root/Root.tsx"
-/* highlight-add-next-line */
-import CategoryIcon from '@material-ui/icons/Category';
-
-export const Root = ({ children }: PropsWithChildren<{}>) => (
-
-
-
- {/* ... */}
- }>
- {/* Global nav, not org-specific */}
- {/* highlight-remove-next-line */}
-
- {/* highlight-add-start */}
-
-
- {/* highlight-add-end */}
-
-
-
-
- {/* End global nav */}
-
- {/* ... */}
-
-
-
-);
-```
-
-That's it! You should now have _(although slightly boring)_ a homepage!
-
-
-
-
-
-In the next steps, we will make it interesting and useful!
-
-### Use the default template
-
-There is a default homepage template ([storybook link](https://backstage.io/storybook/?path=/story/plugins-home-templates--default-template)) which we will use to set up our homepage. Checkout the [blog post announcement](https://backstage.io/blog/2022/01/25/backstage-homepage-templates) about the Backstage homepage templates for more information.
-
-
-
-### Composing your homepage
-
-Composing a homepage is no different from creating a regular React Component,
-i.e. the App Integrator is free to include whatever content they like. However,
-there are components developed with the homepage in mind. If you are looking
-for components to use when composing your homepage, you can take a look at the
-[collection of Homepage components](https://backstage.io/storybook?path=/story/plugins-home-components)
-in storybook. If you don't find a component that suits your needs but want to
-contribute, check the
-[Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing).
-
-> If you want to use one of the available homepage templates you can find the
-> [templates](https://backstage.io/storybook/?path=/story/plugins-home-templates)
-> in the storybook under the "Home" plugin. And if you would like to contribute
-> a template, please see the
-> [Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing)
-
-```tsx
-import Grid from '@material-ui/core/Grid';
-import { HomePageCompanyLogo } from '@backstage/plugin-home';
-
-export const HomePage = () => (
-
-
-
-
-
-);
-```
diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md
index d7c4ee96c8..5893877a55 100644
--- a/docs/getting-started/index.md
+++ b/docs/getting-started/index.md
@@ -184,8 +184,14 @@ Choose the correct next steps for your user role, if you're likely to be deployi
- Using your Backstage instance
- [Logging into Backstage](./logging-in.md)
+ - [Viewing the Catalog](./viewing-catalog.md)
+ - [Viewing what you own](./view-what-you-own.md)
+ - [Viewing entity relationships](./viewing-entity-relationships.md)
+ - [Filtering the Catalog](./filter-catalog.md)
- [Register a component](./register-a-component.md)
- [Create a new component](./create-a-component.md)
+ - [Update a component](./update-a-component.md)
+ - [Unregistering and deleting a component](./unregister-delete-component.md)
Share your experiences, comments, or suggestions with us:
[on discord](https://discord.gg/backstage-687207715902193673), file issues for any
diff --git a/docs/getting-started/keeping-backstage-updated.md b/docs/getting-started/keeping-backstage-updated.md
index c93c5200f6..243760b69a 100644
--- a/docs/getting-started/keeping-backstage-updated.md
+++ b/docs/getting-started/keeping-backstage-updated.md
@@ -151,12 +151,14 @@ down the number of duplicate packages.
## Proxy
-The Backstage CLI uses [global-agent](https://www.npmjs.com/package/global-agent) and `undici` to configure HTTP/HTTPS proxy settings using environment variables. This allows you to route the CLI’s network traffic through a proxy server, which can be useful in environments with restricted internet access.
+On Node.js 22.21.0+, the Backstage CLI respects the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables when `NODE_USE_ENV_PROXY=1` is set. See the [corporate proxy guide](../tutorials/corporate-proxy.md) for full details.
-Additionally, yarn needs a proxy too (sometimes), when in environments with restricted internet access. It uses different settings than the other modules. If you decide to use the backstage yarn plugin [mentioned above](#plugin), you will need to set additional proxy values.
+On older Node.js versions, the CLI falls back to [global-agent](https://www.npmjs.com/package/global-agent) and `undici` for proxy support, which require their own environment variables (prefixed with `GLOBAL_AGENT_`). This allows you to route the CLI’s network traffic through a proxy server, which can be useful in environments with restricted internet access.
+
+Additionally, yarn needs a proxy too (sometimes), when in environments with restricted internet access. It uses different settings than the other modules. If you decide to use the backstage yarn plugin [mentioned above](#managing-package-versions-with-the-backstage-yarn-plugin), you will need to set additional proxy values.
If you will always need proxy settings in all environments and situations, you can add `httpProxy` and `httpsProxy` values to [the yarnrc.yml file](https://yarnpkg.com/configuration/yarnrc). If some environments need it (say a developer workstation) but other environments do not (perhaps a CI build server running on AWS), then you may not want to update the yarnrc.yml file but just set environment variables `YARN_HTTP_PROXY` and `YARN_HTTPS_PROXY` in the environments/situations where you need to proxy.
-**If you plan to use the backstage yarn plugin, you will need these extra yarn proxy settings to both install the plugin and run the `versions:bump` command**. If you do not plan to use the backstage yarn plugin, it seems like the global agent proxy settings alone are sufficient.
+**If you plan to use the backstage yarn plugin, you will need these extra yarn proxy settings to both install the plugin and run the `versions:bump` command**. If you do not plan to use the backstage yarn plugin, it seems like the proxy settings alone are sufficient.
### Example Configuration
@@ -164,9 +166,10 @@ If you will always need proxy settings in all environments and situations, you c
export HTTP_PROXY=http://proxy.company.com:8080
export HTTPS_PROXY=https://secure-proxy.company.com:8080
export NO_PROXY=localhost,internal.company.com
-export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY}
-export GLOBAL_AGENT_HTTPS_PROXY=${HTTPS_PROXY}
-export GLOBAL_AGENT_NO_PROXY=${NO_PROXY}
+export NODE_USE_ENV_PROXY=1 # Node.js 22.21.0+
+export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY} # Node.js < 22.21.0
+export GLOBAL_AGENT_HTTPS_PROXY=${HTTPS_PROXY} # Node.js < 22.21.0
+export GLOBAL_AGENT_NO_PROXY=${NO_PROXY} # Node.js < 22.21.0
export YARN_HTTP_PROXY=${HTTP_PROXY} # optional
export YARN_HTTPS_PROXY=${HTTPS_PROXY} # optional
```
diff --git a/docs/getting-started/register-a-component.md b/docs/getting-started/register-a-component.md
index 4e2076ee8d..1f2fda2871 100644
--- a/docs/getting-started/register-a-component.md
+++ b/docs/getting-started/register-a-component.md
@@ -8,38 +8,53 @@ Audience: Developers
:::note Note
Entity files are stored in YAML format, if you are not familiar with YAML, you can learn more about it [here](https://yaml.org).
+
+[Descriptor Format of Catalog Entities](../features/software-catalog/descriptor-format.md) provides additional information on the format of the YAML entity files.
:::
-## Summary
+## Overview
This guide will walk you through how to pull Backstage data from other locations manually. There are integrations that will automatically do this for you.
+When registering a component, you can:
+
+- Link to an existing entity file: The file is analyzed to determine which entities are defined, and the entities are added to the Scaffolded Backstage App Catalog. For example, `https://github.com/backstage/backstage/blob/master/catalog-info.yaml`.
+
+- Link to a repository: All `catalog-info.yaml` files are discovered in the repository and their defined entities are added to the Scaffolded Backstage App Catalog. For example, `https://github.com/backstage/backstage`.
+
+ :::note Note
+ If no entities are found, a Pull Request is created that adds an example `catalog-info.yaml` file to the repository. When the Pull Request is merged, the Scaffolded Backstage App Catalog loads all of the defined entities.
+
+ :::
+
## Prerequisites
-You should have already [have a standalone app](./index.md).
+- You should have already [installed a standalone app](./index.md).
-## 1. Finding our template
+## Registering a component
-Register a new component, by going to `create` and choose `Register existing component`
+To manually register a component in the Software Catalog:
-
+1. Select `Create`.
+2. Select `REGISTER EXISTING COMPONENT`.
-
+ 
-## 2. Filling out the template
+3. Fill out the template.
-For repository URL, use `https://github.com/backstage/backstage/blob/master/catalog-info.yaml`. This is used in our [demo site](https://demo.backstage.io) catalog.
+ The standalone Backstage application includes one template. For this example, enter the repository URL to the entity file, `https://github.com/backstage/backstage/blob/master/catalog-info.yaml`. This is used in the Backstage [demo site](https://demo.backstage.io) catalog.
-
+ 
-Hit `Analyze` and review the changes.
+4. Select `ANALYZE`.
+5. If the changes from `ANALYZE` are correct, select `IMPORT`.
-## 3. Import the entity
+ 
-If the changes from `Analyze` are correct, click `Apply`.
+ If your entity was successfully imported, the details will be displayed.
-
+ 
-You should receive a message that your entities have been added.
+6. Select `Home` to view your new entity in the Software Catalog.
-If you go back to `Home`, you should be able to find `backstage`. You can click it and see the details for this new entity.
+ 
diff --git a/docs/getting-started/unregister-delete-component.md b/docs/getting-started/unregister-delete-component.md
new file mode 100644
index 0000000000..f8034bf3ed
--- /dev/null
+++ b/docs/getting-started/unregister-delete-component.md
@@ -0,0 +1,66 @@
+---
+id: unregister-delete-component
+title: Unregistering and deleting a component
+description: Unregistering and deleting a component from the catalog
+---
+
+Audience: Developers
+
+## Overview
+
+URLs to YAML files that you registered either using the `Create` button or by adding to your app-config file are both handled by entity providers.
+
+[Implicit deletion](../features/software-catalog/life-of-an-entity.md#implicit-deletion) occurs when an entity provider issues a deletion of an entity. That entity, as well as the entire tree of entities processed out of it are considered for immediate deletion.
+
+However, you are also able to manually unregister an entity from the Catalog or perform a direct, [explicit deletion](../features/software-catalog/life-of-an-entity.md#explicit-deletion) of individual entities.
+
+## Unregistering an entity
+
+You can unregister an entity so it will not be displayed in the Catalog but still keep its `catalog-info.yaml` file in the repository. This provides the ability to register the entity with the Catalog again in the future.
+
+To unregister an entity:
+
+1. In the Catalog, select the entity you want to unregister. In this example, `mytutorial` is being unregistered.
+
+2. Select the three dots.
+
+3. Select `Unregister entity` in the dropdown menu.
+
+ 
+
+4. Select `UNREGISTER LOCATION`. The entity is removed from the Catalog.
+
+ 
+
+## Deleting an entity
+
+You can also delete an entity from the Catalog. However, this requires that you also delete the `catalog-info.yaml` entity definition file associated with the entity.
+
+To delete an entity:
+
+1. Delete the following entity definition files for the entity in the repository:
+
+ - catalog-info.yaml
+ - index.js
+ - package.json
+
+2. In the Backstage App Catalog view, select the entity being deleted. In this example, `mytutorial` is being deleted.
+
+ Since you have deleted the entity definition files, an error is displayed that states the `catalog-info.yaml` file cannot be found.
+
+ 
+
+3. Select the three dots.
+4. Select `Unregister entity` in the dropdown menu.
+
+ 
+
+5. Select `ADVANCED OPTIONS`.
+
+ 
+
+6. Select `DELETE ENTITY`.
+
+ 
+
+A confirmation message that the entity has been successfully deleted is briefly displayed. The entity is no longer displayed in the Catalog.
diff --git a/docs/getting-started/update-a-component.md b/docs/getting-started/update-a-component.md
new file mode 100644
index 0000000000..ffe21bc8b3
--- /dev/null
+++ b/docs/getting-started/update-a-component.md
@@ -0,0 +1,33 @@
+---
+id: update-a-component
+title: Update a Component
+description: Update an existing component.
+---
+
+Audience: Developers
+
+## Overview
+
+Components in the Software Catalog are created using a software template. The template generates a `catalog-info.yaml` file in either GitHub or GitLab that defines the entity. To update the component, you must edit its corresponding `catalog-info.yaml` entity definition file.
+
+## Updating the component
+
+To update a component using the Backstage UI:
+
+1. Select the "Edit" icon associated with the component. In this example, the `tutorial` entity is selected.
+
+ 
+
+ The associated `catalog-info.yaml` file is displayed.
+
+ 
+
+2. Make your changes to the YAML file. In this example, the name of the component is changed to `mytutorial`.
+
+ 
+
+3. Select `Commit changes` to commit your changes to the appropriate branch and go through your normal PR review procedure.
+
+4. Once the updated `catalog-info.yaml` file has been merged into the branch associated with the component, then you will see the updated information in the Software Catalog.
+
+ 
diff --git a/docs/getting-started/view-what-you-own.md b/docs/getting-started/view-what-you-own.md
new file mode 100644
index 0000000000..fc8b1feeae
--- /dev/null
+++ b/docs/getting-started/view-what-you-own.md
@@ -0,0 +1,22 @@
+---
+id: view-what-you-own
+title: Viewing what you own
+description: View the entities that you own either directly or through a group
+---
+
+Audience: All
+
+You can own entities either directly or through a group that you're part of.
+
+To view the entities that you own:
+
+1. Select `Home` in the sidebar.
+2. Select `User` in the `Kind` dropdown list.
+3. Select your username in the `All Users` list.
+
+A page is displayed that shows the entities of which you have ownership, either directly, or through a group of which you are a member. You can toggle between showing:
+
+- `Direct Relations` - entities that you directly own
+- `Aggregated Relations` - entities that you own through your group
+
+
diff --git a/docs/getting-started/viewing-catalog.md b/docs/getting-started/viewing-catalog.md
new file mode 100644
index 0000000000..6ca9075629
--- /dev/null
+++ b/docs/getting-started/viewing-catalog.md
@@ -0,0 +1,85 @@
+---
+id: viewing-catalog
+title: Viewing the Catalog
+sidebar_label: Viewing the Catalog
+description: Viewing the Catalog
+---
+
+Audience: All
+
+## Overview
+
+Initially, when you log into your standalone Backstage App, `Home` is selected in the sidebar, which displays the Catalog in the main panel.
+
+There are four main entities that you should become familiar with:
+
+- `Components` - Individual pieces of software that can be tracked in source control and can implement APIs for other components to consume.
+- `Resources` - The physical or virtual infrastructure needed to operate a component.
+- `Systems` - A collection of resources and components that cooperate to perform a function by exposing one or several public APIs. It hides the resources and private APIs between the components from the consumer.
+- `Domains` - A collection of systems that share terminology, domain models, metrics, KPIs, business purpose, or documentation.
+
+The [Technical Overview](../overview/technical-overview.md#software-catalog-system-model) provides a description of all of the types of entities displayed in the Catalog.
+
+It should be noted that you can also [create your own kinds of entities](../features/software-catalog/extending-the-model.md#adding-a-new-kind), if you need to model something in your organization that does not map to one of the existing entity types.
+
+Initially, the Catalog displays registered entities matching the following filter settings:
+
+- `Kind` - Component
+- `Type` - all
+- `Owner` - Owned
+- `Lifecycle` - list of [lifecycle](../features/software-catalog/descriptor-format.md#speclifecycle-required) values of entities in the Catalog
+- `Processing Status` - normal
+- `Namespace` - The ID of a [namespace](../features/software-catalog/descriptor-format.md#namespace-optional) to which the entity belongs
+
+You can change the initial setting for the [Owner](../features/software-catalog/catalog-customization.md#initially-selected-filter) and [Kind](../features/software-catalog/catalog-customization.md#initially-selected-kind) filters.
+
+## Informational columns for each entity
+
+For each kind of entity, a set of columns display information regarding the entity. For example, the default set of information for a `Component` is:
+
+- `Name` - the name of the component
+- `System` - an optional field that references the system to which the component belongs
+- `Owner` - the owner of the component
+- `Type` - common types are as follows, but you can [create a new type](../features/software-catalog/extending-the-model.md#adding-a-new-type-of-an-existing-kind) to meet your organization's needs
+ - `service` - a backend service, typically exposing an API
+ - `website` - a website
+ - `library` - a software library, such as an npm module or a Java library
+- `Lifecycle`
+ - `experimental` - an experiment or early, non-production component, signaling
+ that users may not prefer to consume it over other more established
+ components, or that there are low or no reliability guarantees
+ - `production` - an established, owned, maintained component
+ - `deprecated` - a component that is at the end of its lifecycle, and may
+ disappear at a later point in time
+- `Description` - an optional field that describes the component.
+- `Tags` - an optional field that can be used for searching
+- `Actions` - see [Catalog Actions](#catalog-actions)
+
+You can modify the columns associated with each kind of entity, following the instructions in [Customize Columns](../features/software-catalog/catalog-customization.md#customize-columns).
+
+## Catalog Actions
+
+For each entity, there are a set of actions that are available.
+
+
+
+From left to right, the actions are:
+
+- View - View the `catalog-info.yaml` file that defines the entity.
+- Edit - Edit the `catalog-info.yaml` file that defines the entity. See [Updating a Component](../getting-started/update-a-component.md)
+- Star - Designate the entity as a favorite. You can [filter](../getting-started/filter-catalog.md) the catalog for starred entities.
+
+[Customize Actions](../features/software-catalog/catalog-customization.md#customize-actions) describes how you can modify the actions that are displayed.
+
+## Viewing entity details
+
+Selecting an entity in the main panel displays details of the entity. The type of details depends on the type of entity. For example, selecting a Component, such as `example-website`, displays the following details:
+
+- `About` - Metadata for the entity, such as description, owner, tags, and domain.
+- `Relations` - see [Viewing entity relationships](../getting-started/viewing-entity-relationships.md)
+- `Links` - any links associated with the entity
+- `Has subcomponents` - An entity reference to another component of which the component is a part
+
+Selecting a System, such as `examples`, displays `About`, `Relations`, and `Links` similar to a Component, but it also includes `Has components`, `APIs` and `Has Resources`.
+
+
diff --git a/docs/getting-started/viewing-entity-relationships.md b/docs/getting-started/viewing-entity-relationships.md
new file mode 100644
index 0000000000..c791ab5bc7
--- /dev/null
+++ b/docs/getting-started/viewing-entity-relationships.md
@@ -0,0 +1,98 @@
+---
+id: viewing-entity-relationships
+title: Viewing entity relationships
+description: View the relationships between the entities in the Catalog
+---
+
+Audience: All
+
+Each of the entities in the Catalog has various relationships to each other. For example, the demo data includes an API that provides data to a website. `guests` is the owner of the API and the website, and anyone signed in as part of the `guests` group can maintain them.
+
+To see these relationships:
+
+1. Select the name of the component in the main panel, in this example, `example-website`.
+
+ 
+
+ A page is displayed that includes a `Relations` section. This section displays the selected entity and any other types of entities to which it is related. Each relationship is also designated, such as `hasPart/partOf` and `apiProvidedBy/providesApi`. [Well-known Relations between Catalog Entities](../features/software-catalog/well-known-relations.md) describes the most common relationships, but you can also [create your own relationships](../features/software-catalog/extending-the-model.md#adding-a-new-relation-type).
+
+2. Selecting any of the related entities allows you to drill down further through the system model.
+
+ 
+
+## Filtering the relationships
+
+You can also view the relationships in the [Catalog Graph](../features/software-catalog/creating-the-catalog-graph.md). This view allows you to filter what entities and relationships to display.
+
+To display the Catalog Graph:
+
+1. Select the name of the component in the main panel, in this example, `example-website`.
+
+ A page is displayed that includes a `Relations` section.
+
+2. Select `View graph` in the `Relations` section.
+
+ 
+
+ The `Catalog Graph` is displayed.
+
+ 
+
+### Setting the filters
+
+The `Catalog Graph` automatically reflects any changes you make to the filter settings. You can set the following filters:
+
+- `MAX DEPTH`
+
+ - `MAX DEPTH` = 1
+
+ 
+
+ - `MAX DEPTH` = infinite
+
+ 
+
+- `KINDS` - select what kinds of entities you want to view, default is all kinds
+- `RELATIONS` - select which relationships you want to view, default is all relationships
+- `Direction` - orientation in which you want to view the entity and its associated nodes
+ - Top to bottom
+ - Bottom to top
+ - Left to right
+ - Right to left
+- `Curve`
+
+ - `Curve` = Monotone
+
+ 
+
+ - `Curve` = Step Before
+
+ 
+
+You can also toggle:
+
+- `Simplified`
+ - On = simple view
+ - Off = detailed view
+- `Merge relations`
+
+ - On = You see the relationship from the selected entity to the nodes and from the nodes to the selected entity.
+ - Off = You only see relations from the selected entity to its nodes.
+
+ The following graphics illustrate the view of the nodes and relationships, based on the combination of the settings of `Simplified` and `Merge relations`.
+
+ - `Simplified` = On and `Merge Relations` = On
+
+ 
+
+ - `Simplified` = On and `Merge Relations` = Off
+
+ 
+
+ - `Simplified` = Off and `Merge Relations` = On
+
+ 
+
+ - `Simplified` = Off and `Merge Relations` = Off
+
+ 
diff --git a/docs/golden-path/adoption/001-getting-started.md b/docs/golden-path/adoption/001-getting-started.md
index 574380f263..df5577527b 100644
--- a/docs/golden-path/adoption/001-getting-started.md
+++ b/docs/golden-path/adoption/001-getting-started.md
@@ -2,6 +2,7 @@
id: getting-started
sidebar_label: 001 - Getting started
title: Getting started with Backstage
+description: An introduction to the Backstage adoption journey and what to expect
---
The adoption journey is a bit different than the other Golden Paths. The goal of this guide is to prepare you for a successful implementation of Backstage in your organization. A technical understanding of Backstage is not needed for this Golden Path, just a desire to help the technical team that will be owning your Backstage instance.
diff --git a/docs/golden-path/adoption/002-leadership-buy-in.md b/docs/golden-path/adoption/002-leadership-buy-in.md
index 502970d3dc..ecb937c6e2 100644
--- a/docs/golden-path/adoption/002-leadership-buy-in.md
+++ b/docs/golden-path/adoption/002-leadership-buy-in.md
@@ -2,6 +2,7 @@
id: leadership-buy-in
sidebar_label: 002 - Leadership buy-in
title: Getting leadership buy-in
+description: How to build a case for Backstage adoption and secure leadership support
---
## Summary
diff --git a/docs/golden-path/adoption/003-setting-up-a-poc.md b/docs/golden-path/adoption/003-setting-up-a-poc.md
index 155098c357..6d444de293 100644
--- a/docs/golden-path/adoption/003-setting-up-a-poc.md
+++ b/docs/golden-path/adoption/003-setting-up-a-poc.md
@@ -2,6 +2,7 @@
id: setting-up-a-poc
sidebar_label: 003 - Setting up a PoC
title: Setting up a PoC
+description: How to set up a proof of concept Backstage instance for your organization
---
If you're non-technical, this section should be completed by your technical partner.
diff --git a/docs/golden-path/adoption/004-first-stakeholder-feedback.md b/docs/golden-path/adoption/004-first-stakeholder-feedback.md
index 6207a49d10..d6cefe9461 100644
--- a/docs/golden-path/adoption/004-first-stakeholder-feedback.md
+++ b/docs/golden-path/adoption/004-first-stakeholder-feedback.md
@@ -2,6 +2,7 @@
id: first-stakeholder-feedback
sidebar_label: 004 - Stakeholder Feedback
title: First round of stakeholder feedback
+description: How to gather and incorporate stakeholder feedback on your Backstage PoC
---
Now that you have a PoC running, let's walk through how to get good feedback. You likely aren't the first person to hear about Backstage or maybe not even the first person to set up a PoC. There may be common pitfalls unique to your company that are worth knowing about - political, organizational or otherwise.
diff --git a/docs/golden-path/adoption/005-customizing-your-instance.md b/docs/golden-path/adoption/005-customizing-your-instance.md
index 9cc9a98d7a..ca33cba63a 100644
--- a/docs/golden-path/adoption/005-customizing-your-instance.md
+++ b/docs/golden-path/adoption/005-customizing-your-instance.md
@@ -2,6 +2,7 @@
id: customize-your-instance
sidebar_label: 005 - Customizing your instance
title: Customizing your instance
+description: How to customize your Backstage instance based on user feedback
---
You now have the knowledge of what your users want and the go from leadership to continue investing in Backstage. Your job now is to customize your instance for your users to really get the value from. Let's dive in!
diff --git a/docs/golden-path/adoption/006-preparing-for-ga.md b/docs/golden-path/adoption/006-preparing-for-ga.md
index 0b62f0fa25..0c6b820dbb 100644
--- a/docs/golden-path/adoption/006-preparing-for-ga.md
+++ b/docs/golden-path/adoption/006-preparing-for-ga.md
@@ -2,18 +2,130 @@
id: preparing-for-ga
sidebar_label: 006 - Preparing for GA
title: Preparing for GA
+description: How to prepare your Backstage instance for a company-wide general availability launch
---
We hope at this point that the developers you're working with have read the [golden path on deploying Backstage](../deployment/index.md). Your Backstage instance should be ready for the scale that comes with a full company launch.
-## Launch Announcements
+## Launch announcements
-
+Getting the word out is one of the most important parts of a successful general
+availability (GA) launch. A good announcement does two things: it tells
+developers that Backstage is available, and it tells them why they should care.
+
+### Use your internal communication channels
+
+Start with the channels your developers already use. Post in Slack or Teams
+channels that reach engineering broadly, share a short write-up in your company
+newsletter or engineering blog, and send a targeted email to team leads who can
+cascade the message to their reports. Tailor the message for each channel; a
+Slack announcement can be shorter and more conversational than a written post.
+
+Lead with the problem Backstage solves for your developers, not with a list of
+features. Developers respond better to "you no longer need to chase down
+ownership information across five different tools" than "we have launched a
+software catalog."
+
+### Host an internal meetup or demo
+
+A live demo builds trust in a way that a written announcement cannot. Developers
+can see the product in action, ask questions, and leave with a concrete
+impression of how Backstage fits into their day-to-day work.
+
+Keep the demo short and focused. Walk through one or two high-value workflows,
+ideally ones that address the pain points you identified during your stakeholder
+feedback sessions. Leave time for questions. If the session is recorded, share
+the recording in your communication channels so developers who couldn't attend can catch
+up.
+
+Consider running multiple sessions to reach different time zones or teams.
+Smaller, more targeted demos for specific teams can be more effective than a
+single large all-hands.
+
+:::tip
+Invite a developer from the teams that participated in your proof of concept
+(PoC) to co-present. Peer endorsement carries more weight than a platform team
+talking about their own product.
+:::
## What to expect in the coming months
-
+The period immediately after GA is often slower than you expect. This is normal.
+Developers are busy, habits are hard to change, and adoption takes time to build
+momentum. The goal in these early months is not to force adoption but to remove
+the friction that prevents it.
+
+### Listen to feedback early and often
+
+Set up a clear feedback channel from day one. This could be a dedicated Slack
+channel, a form, or a recurring office hours session. Make it visible in your
+launch announcement so developers know where to go. The feedback you receive in
+the first few weeks is some of the most valuable you will ever get, because it
+reflects the real first-time experience.
+
+Pay attention to the things developers are struggling with, not just the feature
+requests. Struggles point to gaps in documentation, confusing workflows, or
+missing integrations that are blocking adoption.
+
+### Use analytics to guide your decisions
+
+Backstage does not ship with usage analytics by default, but you can integrate
+an analytics provider through the plugin ecosystem, you can find more information on this in the [Analytics](../../plugins/analytics.md) section of the Backstage documentation. Tracking which parts of
+Backstage developers actually use helps you make informed decisions about where
+to invest your time.
+
+Look for patterns: Are developers landing on the catalog but not drilling into
+entity pages? Is the search feature underused? Are Software Templates being
+triggered but not completed? These signals tell you where the experience is
+breaking down and where it is working well.
+
+### Talk to your users directly
+
+Analytics tell you what is happening. Conversations tell you why. Schedule
+regular check-ins with developers across different teams, especially those who
+were early adopters. Ask them what they use, what they avoid, and what would
+make Backstage more useful in their daily work.
+
+Short, informal conversations are often more revealing than formal surveys.
+A five-minute chat with a developer who almost never opens Backstage can give
+you more actionable insight than a month of dashboards.
## How to keep iterating
-
+GA is not the finish line. The most successful Backstage instances are the ones
+that keep improving after launch, driven by a clear process for taking feedback
+and turning it into changes.
+
+### Make decisions based on feedback
+
+Prioritize work that addresses real developer pain points over work that feels
+interesting to build. When you receive recurring feedback about the same issue,
+treat that as a strong signal. When feedback is mixed or unclear, go back to
+your users and dig deeper before committing to a direction.
+
+Document the decisions you make and the reasoning behind them. This helps your
+team stay aligned and gives you a record to reference when revisiting past
+choices.
+
+### Look for processes that could be improved
+
+As Backstage matures in your organization, you will start to notice patterns in
+how developers interact with it. Some of those patterns will reveal manual steps
+that Backstage could automate, or workflows that exist outside Backstage that
+would benefit from being brought in.
+
+Review your catalog regularly. Are entities going stale? Is ownership information
+drifting out of date? These are signs that the processes around Backstage need
+attention, not the product itself. Work with teams to build the habits and
+automation that keep the data accurate.
+
+### Keep stakeholders informed
+
+Your leadership and key stakeholders backed this investment. Keep them updated
+with regular progress reports that connect Backstage's impact to the outcomes
+they care about: developer productivity, reduced toil, faster onboarding, and
+improved system reliability.
+
+Share wins, but also be transparent about what is not working and what you plan
+to do about it. Stakeholders who trust your judgment are more likely to continue
+investing in the platform over time.
diff --git a/docs/golden-path/adoption/007-plugin-ownership.md b/docs/golden-path/adoption/007-plugin-ownership.md
index 17455f19e7..34aca366a4 100644
--- a/docs/golden-path/adoption/007-plugin-ownership.md
+++ b/docs/golden-path/adoption/007-plugin-ownership.md
@@ -2,16 +2,85 @@
id: plugin-ownership
sidebar_label: 007 - Plugin Ownership
title: Plugin Ownership
+description: How to establish and manage plugin ownership across your organization
---
-You're now well on your way to a healthy Backstage instance! It's been launched to the whole company and you're loving the feedback developers are giving you. Some developers have even started broaching writing their own plugins.
+You're now well on your way to a healthy Backstage instance! It's been launched to the whole company and you're loving the feedback developers are giving you. Some developers have even started expressing interest in writing their own plugins.
## Inner source
-Accepting internal contributions from other teams is a good sign that you are on the road to a developer portal tailored for your developers. This is a well paved path with many upsides, but a few downsides as well. As your Backstage instance grows in size and age, those same developers may be difficult to find. Your team may start to experience more and more struggle updating Backstage.
+Accepting internal contributions from other teams is a good sign that you are on the road to a developer portal tailored for your developers. This is a well-paved path with many upsides, but a few downsides as well. As your Backstage instance grows in size and age, those same developers may be difficult to find and your team may start to experience more friction updating Backstage.
-
+### Readiness before you start
-## Registering Plugins in Your Catalog
+Inner sourcing is a genuinely rewarding initiative, but it works best when some
+groundwork is already in place. Jumping into it too early can create unnecessary
+friction for both the contributing teams and the team that owns Backstage.
-
+Before inviting contributions, its a good idea to have the following in place:
+
+- **A consistent starting point for new plugins.** Running `yarn new` out of the
+ box will scaffold a working Backstage plugin, which is a great starting point.
+ However, the CLI also supports
+ [custom templates](../../tooling/cli/04-templates.md#creating-your-own-cli-templates)
+ that you define yourself. With a custom template you can bake in your
+ company's best practices, coding conventions, and shared components from the
+ start, so every plugin begins from a baseline that already meets your
+ standards rather than having contributors piece that together themselves.
+- **Style guides.** Document the coding conventions your plugins follow. This
+ includes TypeScript standards, test coverage expectations, and naming
+ conventions.
+- **UI and UX guidelines.** Contributors should know how their plugin is
+ expected to look and behave within Backstage. Share your design system, any
+ component libraries you use, and patterns for things like loading states and
+ error handling.
+
+Sharing these resources with a contributing team before they start is far more
+effective than reviewing them in a pull request after the fact.
+
+### Have a conversation before a team starts building
+
+When a team comes to you wanting to build a plugin, that is a great sign. Take
+some time to chat with them about what they have in mind. This is not a review
+process, it is a chance to understand what they are building and make sure you
+can set them up for success.
+
+Some useful things to explore together:
+
+- What does the plugin do, and what problem does it solve?
+- Is this something specific to their team, or would it be useful to a broader
+ audience across the organization?
+- How do they plan to keep it maintained over time?
+
+These conversations give you the context you need to point them toward the right
+starting template, flag any overlap with existing plugins, and think about where
+it fits.
+
+## Registering plugins in your catalog
+
+Once a team has built a plugin and it is ready for wider use, they should register it in the Software Catalog. This makes the plugin discoverable, gives
+it a clear owner, and surfaces any dependencies that other teams need to be
+aware of.
+
+### Fill out catalog-info.yaml completely
+
+When the plugin is registered in the catalog, the contributing team should make
+sure their `catalog-info.yaml` is complete. At a minimum this means listing
+themselves as the owner and documenting any dependencies on backend APIs,
+external services, or other Backstage plugins. This gives other teams a clear
+point of contact for questions or feedback, and an accurate picture of what the
+plugin depends on.
+
+```yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: my-plugin
+ description: A short description of what this plugin does.
+spec:
+ type: plugin
+ lifecycle: production
+ owner: team-name
+ dependsOn:
+ - component:my-backend-api
+```
diff --git a/docs/golden-path/adoption/008-full-catalog.md b/docs/golden-path/adoption/008-full-catalog.md
index 1725bf6dd2..aa3fae41be 100644
--- a/docs/golden-path/adoption/008-full-catalog.md
+++ b/docs/golden-path/adoption/008-full-catalog.md
@@ -2,6 +2,7 @@
id: full-catalog
sidebar_label: 008 - A Full Catalog
title: Ensuring your catalog stays complete
+description: Strategies for maintaining a complete and up-to-date software catalog
---
Along your Backstage journey (and any workflow migration journey), you will hit a point where incremental adoption is no longer easy. The new developers are no longer flowing into your tool like they once did. More and more projects are not being listed in your catalog. Something has to change.
diff --git a/docs/golden-path/create-app/customize-theme--old.md b/docs/golden-path/create-app/customize-theme--old.md
new file mode 100644
index 0000000000..3bae103379
--- /dev/null
+++ b/docs/golden-path/create-app/customize-theme--old.md
@@ -0,0 +1,619 @@
+---
+id: custom-theme--old
+title: 005 - Customize your App's theme (Old Frontend System)
+description: Documentation on customizing the look and feel of your Backstage app.
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./customize-theme.md) instead.
+::::
+
+Backstage ships with a default theme with a light and dark mode variant. The themes are provided as a part of the [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme) package, which also includes utilities for customizing the default theme, or creating completely new themes.
+
+## Creating a Custom Theme
+
+The easiest way to create a new theme is to use the `createUnifiedTheme` function exported by the [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme) package. You can use it to override some basic parameters of the default theme such as the color palette and font.
+
+For example, you can create a new theme based on the default light theme like this:
+
+```ts title="packages/app/src/theme/myTheme.ts"
+import {
+ createBaseThemeOptions,
+ createUnifiedTheme,
+ palettes,
+} from '@backstage/theme';
+
+export const myTheme = createUnifiedTheme({
+ ...createBaseThemeOptions({
+ palette: palettes.light,
+ }),
+ fontFamily: 'Comic Sans MS',
+ defaultPageTheme: 'home',
+});
+```
+
+:::note Note
+
+we recommend creating a `theme` folder in `packages/app/src` to place your theme file to keep things nicely organized.
+
+:::
+
+You can also create a theme from scratch that matches the `BackstageTheme` type exported by [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme). See the
+[Material UI docs on theming](https://material-ui.com/customization/theming/) for more information about how that can be done.
+
+## Using your Custom Theme
+
+To add a custom theme to your Backstage app, you pass it as configuration to `createApp`.
+
+For example, adding the theme that we created in the previous section can be done like this:
+
+```tsx title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/app-defaults';
+import { ThemeProvider } from '@material-ui/core/styles';
+import CssBaseline from '@material-ui/core/CssBaseline';
+import LightIcon from '@material-ui/icons/WbSunny';
+import { UnifiedThemeProvider} from '@backstage/theme';
+import { myTheme } from './themes/myTheme';
+
+const app = createApp({
+ apis: ...,
+ plugins: ...,
+ themes: [{
+ id: 'my-theme',
+ title: 'My Custom Theme',
+ variant: 'light',
+ icon: ,
+ Provider: ({ children }) => (
+
+ ),
+ }]
+})
+```
+
+Note that your list of custom themes overrides the default themes. If you still want to use the default themes, they are exported as `themes.light` and `themes.dark` from [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme).
+
+## Example of a custom theme
+
+```ts title="packages/app/src/theme/myTheme.ts"
+import {
+ createBaseThemeOptions,
+ createUnifiedTheme,
+ genPageTheme,
+ palettes,
+ shapes,
+} from '@backstage/theme';
+
+export const myTheme = createUnifiedTheme({
+ ...createBaseThemeOptions({
+ palette: {
+ ...palettes.light,
+ primary: {
+ main: '#343b58',
+ },
+ secondary: {
+ main: '#565a6e',
+ },
+ error: {
+ main: '#8c4351',
+ },
+ warning: {
+ main: '#8f5e15',
+ },
+ info: {
+ main: '#34548a',
+ },
+ success: {
+ main: '#485e30',
+ },
+ background: {
+ default: '#d5d6db',
+ paper: '#d5d6db',
+ },
+ banner: {
+ info: '#34548a',
+ error: '#8c4351',
+ text: '#343b58',
+ link: '#565a6e',
+ },
+ errorBackground: '#8c4351',
+ warningBackground: '#8f5e15',
+ infoBackground: '#343b58',
+ navigation: {
+ background: '#343b58',
+ indicator: '#8f5e15',
+ color: '#d5d6db',
+ selectedColor: '#ffffff',
+ },
+ },
+ }),
+ defaultPageTheme: 'home',
+ fontFamily: 'Comic Sans MS',
+ /* below drives the header colors */
+ pageTheme: {
+ home: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),
+ documentation: genPageTheme({
+ colors: ['#8c4351', '#343b58'],
+ shape: shapes.wave2,
+ }),
+ tool: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.round }),
+ service: genPageTheme({
+ colors: ['#8c4351', '#343b58'],
+ shape: shapes.wave,
+ }),
+ website: genPageTheme({
+ colors: ['#8c4351', '#343b58'],
+ shape: shapes.wave,
+ }),
+ library: genPageTheme({
+ colors: ['#8c4351', '#343b58'],
+ shape: shapes.wave,
+ }),
+ other: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),
+ app: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),
+ apis: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),
+ },
+});
+```
+
+For a more complete example of a custom theme including Backstage and Material UI component overrides, see the [Aperture theme](https://github.com/backstage/demo/blob/master/packages/app/src/theme/aperture.ts) from the [Backstage demo site](https://demo.backstage.io).
+
+## Custom Typography
+
+When creating a custom theme you can also customize various aspects of the default typography, here's an example using simplified theme:
+
+```ts title="packages/app/src/theme/myTheme.ts"
+import {
+ createBaseThemeOptions,
+ createUnifiedTheme,
+ palettes,
+} from '@backstage/theme';
+
+export const myTheme = createUnifiedTheme({
+ ...createBaseThemeOptions({
+ palette: palettes.light,
+ typography: {
+ htmlFontSize: 16,
+ fontFamily: 'Arial, sans-serif',
+ h1: {
+ fontSize: 54,
+ fontWeight: 700,
+ marginBottom: 10,
+ },
+ h2: {
+ fontSize: 40,
+ fontWeight: 700,
+ marginBottom: 8,
+ },
+ h3: {
+ fontSize: 32,
+ fontWeight: 700,
+ marginBottom: 6,
+ },
+ h4: {
+ fontWeight: 700,
+ fontSize: 28,
+ marginBottom: 6,
+ },
+ h5: {
+ fontWeight: 700,
+ fontSize: 24,
+ marginBottom: 4,
+ },
+ h6: {
+ fontWeight: 700,
+ fontSize: 20,
+ marginBottom: 2,
+ },
+ },
+ defaultPageTheme: 'home',
+ }),
+});
+```
+
+If you wanted to only override a sub-set of the typography setting, for example just `h1` then you would do this:
+
+```ts title="packages/app/src/theme/myTheme.ts"
+import {
+ createBaseThemeOptions,
+ createUnifiedTheme,
+ defaultTypography,
+ palettes,
+} from '@backstage/theme';
+
+export const myTheme = createUnifiedTheme({
+ ...createBaseThemeOptions({
+ palette: palettes.light,
+ typography: {
+ ...defaultTypography,
+ htmlFontSize: 16,
+ fontFamily: 'Roboto, sans-serif',
+ h1: {
+ fontSize: 72,
+ fontWeight: 700,
+ marginBottom: 10,
+ },
+ },
+ defaultPageTheme: 'home',
+ }),
+});
+```
+
+## Custom Fonts
+
+To add custom fonts, you first need to store the font so that it can be imported. We suggest creating the `assets/fonts` directory in your front-end application `src` folder.
+
+You can then declare the font style following the `@font-face` syntax from [Material UI Typography](https://mui.com/material-ui/customization/typography/).
+
+After that you can then utilize the `styleOverrides` of `MuiCssBaseline` under components to add a font to the `@font-face` array.
+
+```ts title="packages/app/src/theme/myTheme.ts"
+import MyCustomFont from '../assets/fonts/My-Custom-Font.woff2';
+
+const myCustomFont = {
+ fontFamily: 'My-Custom-Font',
+ fontStyle: 'normal',
+ fontDisplay: 'swap',
+ fontWeight: 300,
+ src: `
+ local('My-Custom-Font'),
+ url(${MyCustomFont}) format('woff2'),
+ `,
+};
+
+export const myTheme = createUnifiedTheme({
+ fontFamily: 'My-Custom-Font',
+ palette: palettes.light,
+ components: {
+ MuiCssBaseline: {
+ styleOverrides: {
+ '@font-face': [myCustomFont],
+ },
+ },
+ },
+});
+```
+
+If you want to utilize different or multiple fonts, then you can set the top level `fontFamily` to what you want for your body, and then override `fontFamily` in `typography` to control fonts for various headings.
+
+```ts title="packages/app/src/theme/myTheme.ts"
+import MyCustomFont from '../assets/fonts/My-Custom-Font.woff2';
+import myAwesomeFont from '../assets/fonts/My-Awesome-Font.woff2';
+
+const myCustomFont = {
+ fontFamily: 'My-Custom-Font',
+ fontStyle: 'normal',
+ fontDisplay: 'swap',
+ fontWeight: 300,
+ src: `
+ local('My-Custom-Font'),
+ url(${MyCustomFont}) format('woff2'),
+ `,
+};
+
+const myAwesomeFont = {
+ fontFamily: 'My-Awesome-Font',
+ fontStyle: 'normal',
+ fontDisplay: 'swap',
+ fontWeight: 300,
+ src: `
+ local('My-Awesome-Font'),
+ url(${myAwesomeFont}) format('woff2'),
+ `,
+};
+
+export const myTheme = createUnifiedTheme({
+ fontFamily: 'My-Custom-Font',
+ components: {
+ MuiCssBaseline: {
+ styleOverrides: {
+ '@font-face': [myCustomFont, myAwesomeFont],
+ },
+ },
+ },
+ ...createBaseThemeOptions({
+ palette: palettes.light,
+ typography: {
+ ...defaultTypography,
+ htmlFontSize: 16,
+ fontFamily: 'My-Custom-Font',
+ h1: {
+ fontSize: 72,
+ fontWeight: 700,
+ marginBottom: 10,
+ fontFamily: 'My-Awesome-Font',
+ },
+ },
+ defaultPageTheme: 'home',
+ }),
+});
+```
+
+## Overriding Backstage and Material UI components styles
+
+When creating a custom theme you would be applying different values to component's CSS rules that use the theme object. For example, a Backstage component's styles might look like this:
+
+```tsx
+const useStyles = makeStyles(
+ theme => ({
+ header: {
+ padding: theme.spacing(3),
+ boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)',
+ backgroundImage: theme.page.backgroundImage,
+ },
+ }),
+ { name: 'BackstageHeader' },
+);
+```
+
+Notice how the `padding` is getting its value from `theme.spacing`, that means that setting a value for spacing in your custom theme would affect this component padding property and the same goes for `backgroundImage` which uses `theme.page.backgroundImage`. However, the `boxShadow` property doesn't reference any value from the theme, that means that creating a custom theme wouldn't be enough to alter the `box-shadow` property or to add css rules that aren't already defined like a margin. For these cases you should also create an override.
+
+Here's how you would do that:
+
+```ts title="packages/app/src/theme/myTheme.ts"
+import {
+ createBaseThemeOptions,
+ createUnifiedTheme,
+ palettes,
+} from '@backstage/theme';
+
+export const myTheme = createUnifiedTheme({
+ ...createBaseThemeOptions({
+ palette: palettes.light,
+ }),
+ fontFamily: 'Comic Sans MS',
+ defaultPageTheme: 'home',
+ components: {
+ BackstageHeader: {
+ styleOverrides: {
+ header: ({ theme }) => ({
+ width: 'auto',
+ margin: '20px',
+ boxShadow: 'none',
+ borderBottom: `4px solid ${theme.palette.primary.main}`,
+ }),
+ },
+ },
+ },
+});
+```
+
+## Custom Logo
+
+In addition to a custom theme, you can also customize the logo displayed at the far top left of the site.
+
+In your frontend app, locate `src/components/Root/` folder. You'll find two components:
+
+- `LogoFull.tsx` - A larger logo used when the Sidebar navigation is opened.
+- `LogoIcon.tsx` - A smaller logo used when the Sidebar navigation is closed.
+
+To replace the images, you can simply replace the relevant code in those components with raw SVG definitions.
+
+You can also use another web image format such as PNG by importing it. To do this, place your new image into a new subdirectory such as `src/components/Root/logo/my-company-logo.png`, and then add this code:
+
+```tsx
+import MyCustomLogoFull from './logo/my-company-logo.png';
+
+const LogoFull = () => {
+ return ;
+};
+```
+
+## Icons
+
+So far you've seen how to create your own theme and add your own logo, in the following sections you'll be shown how to override the existing icons and how to add more icons
+
+### Custom Icons
+
+You can also customize the Project's _default_ icons.
+
+You can change the following [icons](https://github.com/backstage/backstage/blob/master/packages/app-defaults/src/defaults/icons.tsx).
+
+#### Requirements
+
+- Files in `.svg` format
+- React components created for the icons
+
+#### Create React Component
+
+In your front-end application, locate the `src` folder. We suggest creating the `assets/icons` directory and `CustomIcons.tsx` file.
+
+```tsx title="customIcons.tsx"
+import { SvgIcon, SvgIconProps } from '@material-ui/core';
+
+export const ExampleIcon = (props: SvgIconProps) => (
+
+
+
+);
+```
+
+#### Using the custom icon
+
+Supply your custom icon in `packages/app/src/App.tsx`
+
+```tsx title="packages/app/src/App.tsx"
+/* highlight-add-next-line */
+import { ExampleIcon } from './assets/customIcons'
+
+
+const app = createApp({
+ apis,
+ components: {
+ {/* ... */}
+ },
+ themes: [
+ {/* ... */}
+ ],
+ /* highlight-add-start */
+ icons: {
+ github: ExampleIcon,
+ },
+ /* highlight-add-end */
+ bindRoutes({ bind }) {
+ {/* ... */}
+ }
+})
+```
+
+### Adding Icons
+
+You can add more icons, if the [default icons](https://github.com/backstage/backstage/blob/master/packages/app-defaults/src/defaults/icons.tsx) do not fit your needs, so that they can be used in other places like for Links in your entities. For this example we'll be using icons from[Material UI](https://v4.mui.com/components/material-icons/) and specifically the `AlarmIcon`. Here's how to do that:
+
+1. First you will want to open your `App.tsx` in `/packages/app/src`
+2. Then you want to import your icon, add this to the rest of your imports: `import AlarmIcon from '@material-ui/icons/Alarm';`
+3. Next you want to add the icon like this to your `createApp`:
+
+ ```tsx title="packages/app/src/App.tsx"
+ const app = createApp({
+ apis: ...,
+ plugins: ...,
+ /* highlight-add-start */
+ icons: {
+ alert: AlarmIcon,
+ },
+ /* highlight-add-end */
+ themes: ...,
+ components: ...,
+ });
+ ```
+
+4. Now we can reference `alert` for our icon in our entity links like this:
+
+ ```yaml
+ apiVersion: backstage.io/v1alpha1
+ kind: Component
+ metadata:
+ name: artist-lookup
+ description: Artist Lookup
+ links:
+ - url: https://example.com/alert
+ title: Alerts
+ icon: alert
+ ```
+
+ And this is the result:
+
+ 
+
+ Another way you can use these icons is from the `AppContext` like this:
+
+ ```ts
+ import { useApp } from '@backstage/core-plugin-api';
+
+ const app = useApp();
+ const alertIcon = app.getSystemIcon('alert');
+ ```
+
+ You might want to use this method if you have an icon you want to use in several locations.
+
+:::note Note
+
+If the icon is not available as one of the default icons or one you've added then it will fall back to Material UI's `LanguageIcon`
+
+:::
+
+## Custom Sidebar
+
+As you've seen there are many ways that you can customize your Backstage app. The following section will show you how you can customize the sidebar.
+
+### Sidebar Sub-menu
+
+For this example we'll show you how you can expand the sidebar with a sub-menu:
+
+1. Open the `Root.tsx` file located in `packages/app/src/components/Root` as this is where the sidebar code lives
+2. Then we want to add the following import for `useApp`:
+
+ ```tsx title="packages/app/src/components/Root/Root.tsx"
+ import { useApp } from '@backstage/core-plugin-api';
+ ```
+
+3. Then update the `@backstage/core-components` import like this:
+
+ ```tsx title="packages/app/src/components/Root/Root.tsx"
+ import {
+ Sidebar,
+ sidebarConfig,
+ SidebarDivider,
+ SidebarGroup,
+ SidebarItem,
+ SidebarPage,
+ SidebarScrollWrapper,
+ SidebarSpace,
+ useSidebarOpenState,
+ Link,
+ /* highlight-add-start */
+ GroupIcon,
+ SidebarSubmenu,
+ SidebarSubmenuItem,
+ /* highlight-add-end */
+ } from '@backstage/core-components';
+ ```
+
+4. Finally replace ` ` with this:
+
+ ```tsx title="packages/app/src/components/Root/Root.tsx"
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+
+When you startup your Backstage app and hover over the Home option on the sidebar you'll now see a nice sub-menu appear with links to the various Kinds in your Catalog. It would look like this:
+
+
+
+You can see more ways to use this in the [Storybook Sidebar examples](https://backstage.io/storybook/?path=/story/layout-sidebar--sample-scalable-sidebar)
+
+## Custom Homepage
+
+In addition to a custom theme, a custom logo, you can also customize the
+homepage of your app. Read the full guide on the [next page](../../getting-started/homepage.md).
+
+## Migrating to Material UI v5
+
+We now support Material UI v5 in Backstage. Check out our [migration guide](../../tutorials/migrate-to-mui5.md) to get started.
diff --git a/docs/golden-path/create-app/customize-theme.md b/docs/golden-path/create-app/customize-theme.md
index a79507de59..2c118b52ab 100644
--- a/docs/golden-path/create-app/customize-theme.md
+++ b/docs/golden-path/create-app/customize-theme.md
@@ -4,6 +4,13 @@ title: 005 - Customize your App's theme
description: Documentation on customizing the look and feel of your Backstage app.
---
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./customize-theme--old.md)
+instead.
+::::
+
Backstage ships with a default theme with a light and dark mode variant. The themes are provided as a part of the [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme) package, which also includes utilities for customizing the default theme, or creating completely new themes.
## Creating a Custom Theme
@@ -30,7 +37,7 @@ export const myTheme = createUnifiedTheme({
:::note Note
-we recommend creating a `theme` folder in `packages/app/src` to place your theme file to keep things nicely organized.
+We recommend creating a `theme` folder in `packages/app/src` to place your theme file to keep things nicely organized.
:::
@@ -39,34 +46,59 @@ You can also create a theme from scratch that matches the `BackstageTheme` type
## Using your Custom Theme
-To add a custom theme to your Backstage app, you pass it as configuration to `createApp`.
+In the new frontend system, themes are installed as extensions using `ThemeBlueprint` from `@backstage/plugin-app-react`. The theme extension is then bundled into a frontend module and passed to `createApp`.
-For example, adding the theme that we created in the previous section can be done like this:
+First, install the required packages:
-```tsx title="packages/app/src/App.tsx"
-import { createApp } from '@backstage/app-defaults';
-import { ThemeProvider } from '@material-ui/core/styles';
-import CssBaseline from '@material-ui/core/CssBaseline';
-import LightIcon from '@material-ui/icons/WbSunny';
-import { UnifiedThemeProvider} from '@backstage/theme';
-import { myTheme } from './themes/myTheme';
-
-const app = createApp({
- apis: ...,
- plugins: ...,
- themes: [{
- id: 'my-theme',
- title: 'My Custom Theme',
- variant: 'light',
- icon: ,
- Provider: ({ children }) => (
-
- ),
- }]
-})
+```bash title="From your Backstage root directory"
+yarn --cwd packages/app add @backstage/frontend-plugin-api @backstage/plugin-app-react
```
-Note that your list of custom themes overrides the default themes. If you still want to use the default themes, they are exported as `themes.light` and `themes.dark` from [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme).
+Then create the theme extension and install it in your app:
+
+```tsx title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/frontend-defaults';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import { ThemeBlueprint } from '@backstage/plugin-app-react';
+import { UnifiedThemeProvider } from '@backstage/theme';
+import LightIcon from '@material-ui/icons/WbSunny';
+import { myTheme } from './theme/myTheme';
+
+const myThemeExtension = ThemeBlueprint.make({
+ name: 'my-theme',
+ params: {
+ theme: {
+ id: 'my-theme',
+ title: 'My Custom Theme',
+ variant: 'light',
+ icon: ,
+ Provider: ({ children }) => (
+
+ ),
+ },
+ },
+});
+
+const app = createApp({
+ features: [
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [myThemeExtension],
+ }),
+ ],
+});
+
+export default app.createRoot();
+```
+
+Your custom theme extension will be added alongside the built-in light and dark themes. If you want your custom theme to _replace_ the default themes, you can disable them in `app-config.yaml`:
+
+```yaml title="app-config.yaml"
+app:
+ extensions:
+ - theme:app/light: false
+ - theme:app/dark: false
+```
## Example of a custom theme
@@ -396,117 +428,98 @@ const LogoFull = () => {
## Icons
-So far you've seen how to create your own theme and add your own logo, in the following sections you'll be shown how to override the existing icons and how to add more icons
+So far you've seen how to create your own theme and add your own logo. In the following sections you'll be shown how to override the existing icons and how to add more icons.
### Custom Icons
-You can also customize the Project's _default_ icons.
-
-You can change the following [icons](https://github.com/backstage/backstage/blob/master/packages/app-defaults/src/defaults/icons.tsx).
-
-#### Requirements
-
-- Files in `.svg` format
-- React components created for the icons
-
-#### Create React Component
-
-In your front-end application, locate the `src` folder. We suggest creating the `assets/icons` directory and `CustomIcons.tsx` file.
-
-```tsx title="customIcons.tsx"
-import { SvgIcon, SvgIconProps } from '@material-ui/core';
-
-export const ExampleIcon = (props: SvgIconProps) => (
-
-
-
-);
-```
-
-#### Using the custom icon
-
-Supply your custom icon in `packages/app/src/App.tsx`
+You can customize the app's default icons using `IconBundleBlueprint` from `@backstage/plugin-app-react`. This creates an extension that overrides built-in icons.
```tsx title="packages/app/src/App.tsx"
-/* highlight-add-next-line */
-import { ExampleIcon } from './assets/customIcons'
+import { createApp } from '@backstage/frontend-defaults';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import { IconBundleBlueprint } from '@backstage/plugin-app-react';
+import { ExampleIcon } from './assets/customIcons';
+const customIconBundle = IconBundleBlueprint.make({
+ name: 'custom-icons',
+ params: {
+ icons: {
+ github: ExampleIcon,
+ },
+ },
+});
const app = createApp({
- apis,
- components: {
- {/* ... */}
- },
- themes: [
- {/* ... */}
+ features: [
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [customIconBundle],
+ }),
],
- /* highlight-add-start */
- icons: {
- github: ExampleIcon,
- },
- /* highlight-add-end */
- bindRoutes({ bind }) {
- {/* ... */}
- }
-})
+});
+
+export default app.createRoot();
```
### Adding Icons
-You can add more icons, if the [default icons](https://github.com/backstage/backstage/blob/master/packages/app-defaults/src/defaults/icons.tsx) do not fit your needs, so that they can be used in other places like for Links in your entities. For this example we'll be using icons from[Material UI](https://v4.mui.com/components/material-icons/) and specifically the `AlarmIcon`. Here's how to do that:
+You can register additional icons so that they can be used in other places like entity links. For example, to add an `alert` icon:
-1. First you will want to open your `App.tsx` in `/packages/app/src`
-2. Then you want to import your icon, add this to the rest of your imports: `import AlarmIcon from '@material-ui/icons/Alarm';`
-3. Next you want to add the icon like this to your `createApp`:
+```tsx title="packages/app/src/App.tsx"
+import { createApp } from '@backstage/frontend-defaults';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import AlarmIcon from '@material-ui/icons/Alarm';
+import { IconBundleBlueprint } from '@backstage/plugin-app-react';
- ```tsx title="packages/app/src/App.tsx"
- const app = createApp({
- apis: ...,
- plugins: ...,
- /* highlight-add-start */
- icons: {
- alert: AlarmIcon,
- },
- /* highlight-add-end */
- themes: ...,
- components: ...,
- });
- ```
+const extraIcons = IconBundleBlueprint.make({
+ name: 'extra-icons',
+ params: {
+ icons: {
+ alert: AlarmIcon,
+ },
+ },
+});
-4. Now we can reference `alert` for our icon in our entity links like this:
+const app = createApp({
+ features: [
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [extraIcons],
+ }),
+ ],
+});
- ```yaml
- apiVersion: backstage.io/v1alpha1
- kind: Component
- metadata:
- name: artist-lookup
- description: Artist Lookup
- links:
- - url: https://example.com/alert
- title: Alerts
- icon: alert
- ```
+export default app.createRoot();
+```
- And this is the result:
+You can then reference `alert` for your icon in entity links like this:
- 
+```yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: artist-lookup
+ description: Artist Lookup
+ links:
+ - url: https://example.com/alert
+ title: Alerts
+ icon: alert
+```
- Another way you can use these icons is from the `AppContext` like this:
+And this is the result:
- ```ts
- import { useApp } from '@backstage/core-plugin-api';
+
- const app = useApp();
- const alertIcon = app.getSystemIcon('alert');
- ```
+Another way you can use these icons is from the `AppContext` like this:
- You might want to use this method if you have an icon you want to use in several locations.
+```ts
+import { useApp } from '@backstage/core-plugin-api';
+
+const app = useApp();
+const alertIcon = app.getSystemIcon('alert');
+```
+
+You might want to use this method if you have an icon you want to use in several locations.
:::note Note
@@ -516,92 +529,7 @@ If the icon is not available as one of the default icons or one you've added the
## Custom Sidebar
-As you've seen there are many ways that you can customize your Backstage app. The following section will show you how you can customize the sidebar.
-
-### Sidebar Sub-menu
-
-For this example we'll show you how you can expand the sidebar with a sub-menu:
-
-1. Open the `Root.tsx` file located in `packages/app/src/components/Root` as this is where the sidebar code lives
-2. Then we want to add the following import for `useApp`:
-
- ```tsx title="packages/app/src/components/Root/Root.tsx"
- import { useApp } from '@backstage/core-plugin-api';
- ```
-
-3. Then update the `@backstage/core-components` import like this:
-
- ```tsx title="packages/app/src/components/Root/Root.tsx"
- import {
- Sidebar,
- sidebarConfig,
- SidebarDivider,
- SidebarGroup,
- SidebarItem,
- SidebarPage,
- SidebarScrollWrapper,
- SidebarSpace,
- useSidebarOpenState,
- Link,
- /* highlight-add-start */
- GroupIcon,
- SidebarSubmenu,
- SidebarSubmenuItem,
- /* highlight-add-end */
- } from '@backstage/core-components';
- ```
-
-4. Finally replace ` ` with this:
-
- ```tsx title="packages/app/src/components/Root/Root.tsx"
-
-
-
-
-
-
-
-
-
-
-
-
-
- ```
-
-When you startup your Backstage app and hover over the Home option on the sidebar you'll now see a nice sub-menu appear with links to the various Kinds in your Catalog. It would look like this:
-
-
-
-You can see more ways to use this in the [Storybook Sidebar examples](https://backstage.io/storybook/?path=/story/layout-sidebar--sample-scalable-sidebar)
+In the new frontend system, the sidebar is managed by the built-in `app/nav` extension. You can customize it by creating a `NavContentBlueprint` extension. See the [sidebar customization](../../frontend-system/building-apps/08-migrating.md#app-root-sidebar) documentation for detailed instructions on creating a custom sidebar layout with sub-menus and custom grouping.
## Custom Homepage
diff --git a/docs/golden-path/create-app/index.md b/docs/golden-path/create-app/index.md
index ebd736594d..caa26c2a3a 100644
--- a/docs/golden-path/create-app/index.md
+++ b/docs/golden-path/create-app/index.md
@@ -1,6 +1,7 @@
---
id: index
title: 'Creating your first Backstage app'
+description: A guided walkthrough for creating and setting up your first Backstage application
---
### Prerequisites
diff --git a/docs/golden-path/create-app/installing-plugins.md b/docs/golden-path/create-app/installing-plugins.md
index ab1e1a68c5..f5f5931220 100644
--- a/docs/golden-path/create-app/installing-plugins.md
+++ b/docs/golden-path/create-app/installing-plugins.md
@@ -2,6 +2,7 @@
id: installing-plugins
sidebar_label: 003 - Installing plugins
title: 003 - Installing plugins
+description: How to find and install plugins in your Backstage app
---
Now that you have a working Backstage app, let's walk through the most valuable part of the Backstage ecosystem - plugins!
diff --git a/docs/golden-path/create-app/keeping-backstage-updated.md b/docs/golden-path/create-app/keeping-backstage-updated.md
index de7d99b2b8..540b200f1b 100644
--- a/docs/golden-path/create-app/keeping-backstage-updated.md
+++ b/docs/golden-path/create-app/keeping-backstage-updated.md
@@ -2,6 +2,7 @@
id: keeping-backstage-updated
sidebar_label: 006 - Keep Backstage updated
title: 006 - Keeping Backstage up to date
+description: How to keep your Backstage instance up to date with the latest releases
---
Audience: Developers and Admins
@@ -141,12 +142,14 @@ down the number of duplicate packages.
## Proxy
-The Backstage CLI uses [global-agent](https://www.npmjs.com/package/global-agent) and `undici` to configure HTTP/HTTPS proxy settings using environment variables. This allows you to route the CLI’s network traffic through a proxy server, which can be useful in environments with restricted internet access.
+On Node.js 22.21.0+, the Backstage CLI respects the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables when `NODE_USE_ENV_PROXY=1` is set. See the [corporate proxy guide](../../tutorials/corporate-proxy.md) for full details.
-Additionally, `yarn` needs a proxy too (sometimes), when in environments with restricted internet access. It uses different settings than the other modules. If you decide to use the backstage yarn plugin [mentioned above](#plugin), you will need to set additional proxy values.
+On older Node.js versions, the CLI falls back to [global-agent](https://www.npmjs.com/package/global-agent) and `undici` for proxy support, which require their own environment variables (prefixed with `GLOBAL_AGENT_`). This allows you to route the CLI’s network traffic through a proxy server, which can be useful in environments with restricted internet access.
+
+Additionally, `yarn` needs a proxy too (sometimes), when in environments with restricted internet access. It uses different settings than the other modules. If you decide to use the backstage yarn plugin [mentioned above](#managing-package-versions-with-the-backstage-yarn-plugin), you will need to set additional proxy values.
If you will always need proxy settings in all environments and situations, you can add `httpProxy` and `httpsProxy` values to [the yarnrc.yml file](https://yarnpkg.com/configuration/yarnrc). If some environments need it (say a developer workstation) but other environments do not (perhaps a CI build server running on AWS), then you may not want to update the yarnrc.yml file but just set environment variables `YARN_HTTP_PROXY` and `YARN_HTTPS_PROXY` in the environments/situations where you need to proxy.
-**If you plan to use the backstage yarn plugin, you will need these extra yarn proxy settings to both install the plugin and run the `versions:bump` command**. If you do not plan to use the backstage yarn plugin, it seems like the global agent proxy settings alone are sufficient.
+**If you plan to use the backstage yarn plugin, you will need these extra yarn proxy settings to both install the plugin and run the `versions:bump` command**. If you do not plan to use the backstage yarn plugin, it seems like the proxy settings alone are sufficient.
### Example Configuration
@@ -154,9 +157,10 @@ If you will always need proxy settings in all environments and situations, you c
export HTTP_PROXY=http://proxy.company.com:8080
export HTTPS_PROXY=https://secure-proxy.company.com:8080
export NO_PROXY=localhost,internal.company.com
-export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY}
-export GLOBAL_AGENT_HTTPS_PROXY=${HTTPS_PROXY}
-export GLOBAL_AGENT_NO_PROXY=${NO_PROXY}
+export NODE_USE_ENV_PROXY=1 # Node.js 22.21.0+
+export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY} # Node.js < 22.21.0
+export GLOBAL_AGENT_HTTPS_PROXY=${HTTPS_PROXY} # Node.js < 22.21.0
+export GLOBAL_AGENT_NO_PROXY=${NO_PROXY} # Node.js < 22.21.0
export YARN_HTTP_PROXY=${HTTP_PROXY} # optional
export YARN_HTTPS_PROXY=${HTTPS_PROXY} # optional
```
diff --git a/docs/golden-path/create-app/local-development.md b/docs/golden-path/create-app/local-development.md
index 2c81c2c643..ff23a7c652 100644
--- a/docs/golden-path/create-app/local-development.md
+++ b/docs/golden-path/create-app/local-development.md
@@ -1,6 +1,7 @@
---
id: local-development
title: 002 - Local development
+description: How to run and develop your Backstage app locally
---
Your Backstage app is fully installed and ready to be run! Now that the installation is complete, you can go to the application directory and start the app using the `yarn start` command. The `yarn start` command will run both the frontend and backend as separate processes (named `[0]` and `[1]`) in the same window.
diff --git a/docs/golden-path/create-app/npx-create-app.md b/docs/golden-path/create-app/npx-create-app.md
index 8ee016f41f..d33c037424 100644
--- a/docs/golden-path/create-app/npx-create-app.md
+++ b/docs/golden-path/create-app/npx-create-app.md
@@ -1,6 +1,7 @@
---
id: npx-create-app
title: '001 - Scaffolding'
+description: How to scaffold a new Backstage app using create-app
---
Audience: Developers and Admins
diff --git a/docs/golden-path/plugins/backend/001-first-steps.md b/docs/golden-path/plugins/backend/001-first-steps.md
index 8a0defe183..ed2c8e98ed 100644
--- a/docs/golden-path/plugins/backend/001-first-steps.md
+++ b/docs/golden-path/plugins/backend/001-first-steps.md
@@ -2,6 +2,7 @@
id: first-steps
sidebar_label: 001 - Scaffolding the plugin
title: How to scaffold a new plugin?
+description: How to scaffold a new Backstage backend plugin using the CLI
---
# Scaffolding a new plugin
diff --git a/docs/golden-path/plugins/backend/002-poking-around.md b/docs/golden-path/plugins/backend/002-poking-around.md
index 596de01b4b..e7636d3749 100644
--- a/docs/golden-path/plugins/backend/002-poking-around.md
+++ b/docs/golden-path/plugins/backend/002-poking-around.md
@@ -2,6 +2,7 @@
id: poking-around
sidebar_label: 002 - Poking around
title: 002 - Poking around
+description: Exploring the default backend plugin structure and functionality
---
## Default plugin functionality
diff --git a/docs/golden-path/plugins/backend/003-persistence.md b/docs/golden-path/plugins/backend/003-persistence.md
index ba00b2f417..60d275d7ec 100644
--- a/docs/golden-path/plugins/backend/003-persistence.md
+++ b/docs/golden-path/plugins/backend/003-persistence.md
@@ -2,6 +2,7 @@
id: persistence
sidebar_label: 003 - Persisting your TODOs
title: 003 - Persisting your TODOs
+description: How to add database persistence to your backend plugin
---
## Saving Plugin State Indefinitely
diff --git a/docs/golden-path/plugins/backend/004-source-tracked.md b/docs/golden-path/plugins/backend/004-source-tracked.md
index 074cc85cf1..54013ccead 100644
--- a/docs/golden-path/plugins/backend/004-source-tracked.md
+++ b/docs/golden-path/plugins/backend/004-source-tracked.md
@@ -2,6 +2,7 @@
id: source-tracked
sidebar_label: 004 - Integrating with SCMs
title: 004 - Git-tracked TODOs
+description: How to ingest TODOs from source code repositories into your plugin
---
Problem: You have TODOs in your source code that you want to ingest with your plugin.
diff --git a/docs/golden-path/plugins/backend/005-testing.md b/docs/golden-path/plugins/backend/005-testing.md
index 9643e8a487..075b81f1fc 100644
--- a/docs/golden-path/plugins/backend/005-testing.md
+++ b/docs/golden-path/plugins/backend/005-testing.md
@@ -2,6 +2,7 @@
id: testing
sidebar_label: 005 - Unit testing your plugin
title: 005 - Testing
+description: How to write unit tests for your Backstage backend plugin
---
## Testing is important
diff --git a/docs/golden-path/plugins/frontend/001-first-steps.md b/docs/golden-path/plugins/frontend/001-first-steps.md
index d6c21ed053..313a990b62 100644
--- a/docs/golden-path/plugins/frontend/001-first-steps.md
+++ b/docs/golden-path/plugins/frontend/001-first-steps.md
@@ -2,6 +2,7 @@
id: first-steps
sidebar_label: 001 - Scaffolding the plugin
title: How to scaffold a new plugin?
+description: How to scaffold a new Backstage frontend plugin using the CLI
---
Running `yarn new` -> `frontend-plugin`.
diff --git a/docs/golden-path/plugins/frontend/002-poking-around.md b/docs/golden-path/plugins/frontend/002-poking-around.md
index e85ea235f3..33223019d0 100644
--- a/docs/golden-path/plugins/frontend/002-poking-around.md
+++ b/docs/golden-path/plugins/frontend/002-poking-around.md
@@ -2,6 +2,7 @@
id: poking-around
sidebar_label: 002 - Poking around
title: 002 - Poking around
+description: Exploring the default frontend plugin structure and components
---
Our frontend TODO plugin is a bit more simplistic than the backend one. We need to implement a new UI to replace the example components we have.
diff --git a/docs/golden-path/plugins/frontend/003-dynamic-config.md b/docs/golden-path/plugins/frontend/003-dynamic-config.md
index 8eecd72c2f..8f3b7ff1ef 100644
--- a/docs/golden-path/plugins/frontend/003-dynamic-config.md
+++ b/docs/golden-path/plugins/frontend/003-dynamic-config.md
@@ -2,6 +2,7 @@
id: dynamic-config
sidebar_label: 003 - Dynamic Config
title: 003 - Dynamic Config
+description: How to use dynamic configuration to control frontend plugin components
---
Your plugin should have been generated by default for the New Frontend System which is config-first. That means you can easily control your frontend components through your `app-config.yaml`.
diff --git a/docs/golden-path/plugins/frontend/004-http-client.md b/docs/golden-path/plugins/frontend/004-http-client.md
index b6190f0f5f..00678df1d6 100644
--- a/docs/golden-path/plugins/frontend/004-http-client.md
+++ b/docs/golden-path/plugins/frontend/004-http-client.md
@@ -2,6 +2,7 @@
id: http-client
sidebar_label: 004 - HTTP Client
title: 004 - HTTP Client
+description: How to build an HTTP client for your frontend plugin to fetch backend data
---
Now, let's really make our page dynamic. We'll start by writing an HTTP client by hand.
diff --git a/docs/golden-path/plugins/frontend/005-testing.md b/docs/golden-path/plugins/frontend/005-testing.md
index fe385d7169..6a672998aa 100644
--- a/docs/golden-path/plugins/frontend/005-testing.md
+++ b/docs/golden-path/plugins/frontend/005-testing.md
@@ -2,6 +2,7 @@
id: testing
sidebar_label: 005 - Testing
title: 005 - Testing
+description: How to write tests for your Backstage frontend plugin components
---
Everyone's favorite part! Let's make sure our components continue to work even when we're not able to validate the changes.
diff --git a/docs/golden-path/plugins/index.md b/docs/golden-path/plugins/index.md
index 616c2c0c36..43ffe612e8 100644
--- a/docs/golden-path/plugins/index.md
+++ b/docs/golden-path/plugins/index.md
@@ -2,6 +2,7 @@
id: index
sidebar_label: Backstage Plugins!
title: How to create plugins with Backstage
+description: A guided walkthrough for building frontend and backend plugins in Backstage
---
### Prerequisites
diff --git a/docs/golden-path/plugins/integrations/001-catalog.md b/docs/golden-path/plugins/integrations/001-catalog.md
index f5ed04116e..4825a08ca8 100644
--- a/docs/golden-path/plugins/integrations/001-catalog.md
+++ b/docs/golden-path/plugins/integrations/001-catalog.md
@@ -2,6 +2,7 @@
id: catalog
sidebar_label: 001 - Catalog
title: Integrating with Catalog
+description: How to integrate your plugin with the Backstage Software Catalog
---
## Software Catalog
diff --git a/docs/golden-path/plugins/integrations/002-search.md b/docs/golden-path/plugins/integrations/002-search.md
index b0ef762f27..b3d23de4d9 100644
--- a/docs/golden-path/plugins/integrations/002-search.md
+++ b/docs/golden-path/plugins/integrations/002-search.md
@@ -2,6 +2,7 @@
id: search
sidebar_label: 002 - Search
title: Integrating with Search
+description: How to integrate your plugin with Backstage Search
---
## Search
diff --git a/docs/golden-path/plugins/integrations/003-permissions.md b/docs/golden-path/plugins/integrations/003-permissions.md
index 167d40d47f..3cbba161fb 100644
--- a/docs/golden-path/plugins/integrations/003-permissions.md
+++ b/docs/golden-path/plugins/integrations/003-permissions.md
@@ -2,22 +2,309 @@
id: permissions
sidebar_label: 003 - Permissions
title: Integrating with the Permission framework
+description: How to integrate your plugin with the Backstage Permission framework
---
## Permissions
### What is the Permissions framework?
-
+The [Backstage permissions framework](../../../permissions/overview.md) gives you a structured way to control who can do what inside your plugin. Rather than scattering authorization logic across your route handlers, you define permissions declaratively and let a central policy decide whether to allow or deny each action.
+
+There are two kinds of permissions:
+
+_Basic permissions_ apply to actions that don't relate to a specific resource. Creating a todo is a good example: the action either is or isn't allowed, regardless of which todo you're creating. The policy returns a definitive ALLOW or DENY.
+
+_Resource permissions_ apply to actions on a specific resource. Reading a particular todo is a good example: whether you're allowed might depend on whether you created it. In addition to the basic ALLOW or DENY, the policy can return a CONDITIONAL decision. CONDITIONAL decisions are required to be evaluated against a specific resource and will produce a per-resource ALLOW or DENY.
+
+The framework sits between your route handlers and your business logic. Your handler asks "is this allowed?", the framework consults the active policy, and your handler either proceeds or throws a `NotAllowedError`.
### Common integration points
-
+Most plugins integrate at two levels:
+
+**The backend plugin** is where you define your permissions, register them with the framework, and enforce them inside your route handlers.
+
+**A common package** (for example, `@internal/plugin-todo-common`) is where you export the permission definitions so they can be referenced from anywhere: your backend, your frontend, and any policy that an adopter writes.
+
+The split matters because policy authors need to reference your permission objects when writing their own policies. If those definitions live inside your backend package, you're forcing a dependency on backend code where it doesn't belong.
## Creating private TODOs
-
+The goal here is to ensure users can only read their own todos. This is a resource permission because the decision depends on a property of the resource itself.
+
+### Define the permission
+
+In your common package, define a resource permission for reading todos:
+
+```ts
+// plugins/todo-common/src/permissions.ts
+import { createPermission } from '@backstage/plugin-permission-common';
+
+export const TODO_RESOURCE_TYPE = 'todo-item';
+
+export const todoReadPermission = createPermission({
+ name: 'todo.read',
+ attributes: { action: 'read' },
+ resourceType: TODO_RESOURCE_TYPE,
+});
+
+export const todoPermissions = [todoReadPermission];
+```
+
+The `resourceType` field ties this permission to a specific kind of resource. Exporting the string as a named constant (`TODO_RESOURCE_TYPE`) means you can import it in your backend rules rather than repeating the raw string, which prevents subtle mismatches.
+
+### Define a permission rule
+
+Rules are the conditions that the framework evaluates against a resource. Each rule has two parts: `apply`, which checks an in-memory resource, and `toQuery`, which converts the condition to a filter your database can use.
+
+```ts
+// plugins/todo-backend/src/service/rules.ts
+import {
+ createPermissionResourceRef,
+ createPermissionRule,
+} from '@backstage/plugin-permission-node';
+import { TODO_RESOURCE_TYPE } from '@internal/plugin-todo-common';
+import { z } from 'zod/v3';
+import type { TodoItem } from './services/TodoListService';
+
+export const todoResourceRef = createPermissionResourceRef<
+ TodoItem,
+ { createdBy: string }
+>().with({
+ pluginId: 'todo',
+ resourceType: TODO_RESOURCE_TYPE,
+});
+
+export const isCreator = createPermissionRule({
+ name: 'IS_CREATOR',
+ description: 'Allow if the todo was created by the current user',
+ resourceRef: todoResourceRef,
+ paramsSchema: z.object({
+ userRef: z.string().describe('The entity ref of the user'),
+ }),
+ apply(todo, { userRef }) {
+ return todo.createdBy === userRef;
+ },
+ toQuery({ userRef }) {
+ return { property: 'createdBy', values: [userRef] };
+ },
+});
+
+export const rules = { isCreator };
+```
+
+The `apply` and `toQuery` functions must always have logically identical outcomes. If they diverge, users will see inconsistent results depending on whether the framework checks the database or a loaded resource.
+
+### Register the resource type
+
+In your plugin setup, register the resource type alongside its rules:
+
+```ts
+// plugins/todo-backend/src/plugin.ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+import { todoReadPermission } from '@internal/plugin-todo-common';
+import { todoResourceRef, rules } from './service/rules';
+import { todoListServiceRef } from './services/TodoListService';
+
+export const todoPlugin = createBackendPlugin({
+ pluginId: 'todo',
+ register(env) {
+ env.registerInit({
+ deps: {
+ httpRouter: coreServices.httpRouter,
+ httpAuth: coreServices.httpAuth,
+ permissions: coreServices.permissions,
+ permissionsRegistry: coreServices.permissionsRegistry,
+ todoList: todoListServiceRef,
+ },
+ async init({
+ httpRouter,
+ httpAuth,
+ permissions,
+ permissionsRegistry,
+ todoList,
+ }) {
+ permissionsRegistry.addResourceType({
+ resourceRef: todoResourceRef,
+ permissions: [todoReadPermission],
+ rules: Object.values(rules),
+ getResources: async resourceRefs => {
+ return Promise.all(
+ resourceRefs.map(ref =>
+ todoList.getTodo({ id: ref }).catch(() => undefined),
+ ),
+ );
+ },
+ });
+
+ const router = await createRouter({ httpAuth, permissions, todoList });
+ httpRouter.use(router);
+ },
+ });
+ },
+});
+```
+
+`getResources` is called by the framework when it needs to load a resource to evaluate a conditional decision. Return `undefined` for any ref that doesn't exist.
+
+### Enforce the permission in a route handler
+
+In your route handler, use `authorizeConditional` for resource permissions. Unlike `authorize`, this can return a conditional decision that you apply as a filter rather than a hard stop:
+
+```ts
+// plugins/todo-backend/src/service/router.ts
+import {
+ HttpAuthService,
+ PermissionsService,
+} from '@backstage/backend-plugin-api';
+import { NotAllowedError } from '@backstage/errors';
+import { AuthorizeResult } from '@backstage/plugin-permission-common';
+import { todoReadPermission } from '@internal/plugin-todo-common';
+import { todoListServiceRef } from './services/TodoListService';
+
+export async function createRouter({
+ httpAuth,
+ permissions,
+ todoList,
+}: {
+ httpAuth: HttpAuthService;
+ permissions: PermissionsService;
+ todoList: typeof todoListServiceRef.T;
+}): Promise {
+ const router = Router();
+ router.use(express.json());
+
+ router.get('/todos', async (req, res) => {
+ const credentials = await httpAuth.credentials(req, { allow: ['user'] });
+
+ const decision = (
+ await permissions.authorizeConditional(
+ [{ permission: todoReadPermission }],
+ { credentials },
+ )
+ )[0];
+
+ if (decision.result === AuthorizeResult.DENY) {
+ throw new NotAllowedError();
+ }
+
+ // If CONDITIONAL, pass the conditions to your data layer as a filter.
+ // If ALLOW, pass no filter (return everything).
+ const result = await todoList.listTodos(
+ decision.result === AuthorizeResult.CONDITIONAL
+ ? decision.conditions
+ : undefined,
+ );
+
+ res.json(result);
+ });
+
+ // ... other routes
+ return router;
+}
+```
+
+The conditional path means users only see the data the policy allows, without the handler needing to know what the policy actually is. The policy is the adopter's concern.
+
+### Export condition helpers for policy authors
+
+Adopters who write their own permission policy need to be able to express conditions using your rules. Export helpers from your backend package:
+
+```ts
+// plugins/todo-backend/src/conditionExports.ts
+import { createConditionExports } from '@backstage/plugin-permission-node';
+import { todoResourceRef, rules } from './service/rules';
+
+const { conditions, createConditionalDecision } = createConditionExports({
+ resourceRef: todoResourceRef,
+ rules,
+});
+
+export const todoConditions = conditions;
+export const createTodoConditionalDecision = createConditionalDecision;
+```
+
+Re-export these from your package's `src/index.ts`. An adopter can then write a policy like this:
+
+```ts
+import {
+ todoConditions,
+ createTodoConditionalDecision,
+} from '@internal/plugin-todo-backend';
+import { todoReadPermission } from '@internal/plugin-todo-common';
+
+class MyPolicy implements PermissionPolicy {
+ async handle(request: PolicyQuery, user?: PolicyQueryUser) {
+ if (isPermission(request.permission, todoReadPermission)) {
+ return createTodoConditionalDecision(
+ request.permission,
+ todoConditions.isCreator({ userRef: user?.info.userEntityRef ?? '' }),
+ );
+ }
+ return { result: AuthorizeResult.ALLOW };
+ }
+}
+```
+
+This gives adopters a typed, discoverable API for customizing your plugin's access control without having to understand the internals of your data layer.
## Restricting who can create TODOs
-
+Restricting who can create todos is simpler. There is no resource involved yet, so this is a basic permission. The policy returns a definitive ALLOW or DENY.
+
+### Define the create permission
+
+Add a create permission to your common package:
+
+```ts
+// plugins/todo-common/src/permissions.ts
+export const todoCreatePermission = createPermission({
+ name: 'todo.create',
+ attributes: { action: 'create' },
+});
+
+export const todoPermissions = [todoReadPermission, todoCreatePermission];
+```
+
+### Register the permission with the framework
+
+In your plugin setup, register basic permissions with `addPermissions` rather than `addResourceType`:
+
+```ts
+permissionsRegistry.addPermissions([todoCreatePermission]);
+```
+
+### Enforce the permission in the create handler
+
+For basic permissions, use `authorize` instead of `authorizeConditional`. The result is always definitive:
+
+```ts
+router.post('/todos', async (req, res) => {
+ const parsed = todoSchema.safeParse(req.body);
+ if (!parsed.success) {
+ throw new InputError(parsed.error.toString());
+ }
+
+ const credentials = await httpAuth.credentials(req, { allow: ['user'] });
+
+ const decision = (
+ await permissions.authorize([{ permission: todoCreatePermission }], {
+ credentials,
+ })
+ )[0];
+
+ if (decision.result !== AuthorizeResult.ALLOW) {
+ throw new NotAllowedError('You are not permitted to create todos');
+ }
+
+ const result = await todoList.createTodo(parsed.data, { credentials });
+
+ res.status(201).json(result);
+});
+```
+
+An adopter's policy can now control this permission however they like: restrict it to a specific group, require a certain annotation on the user entity, or leave it open to everyone. Your plugin does not need to know.
diff --git a/docs/golden-path/plugins/integrations/004-notifications.md b/docs/golden-path/plugins/integrations/004-notifications.md
index 23c44b4721..d28f08da49 100644
--- a/docs/golden-path/plugins/integrations/004-notifications.md
+++ b/docs/golden-path/plugins/integrations/004-notifications.md
@@ -2,6 +2,7 @@
id: notifications
sidebar_label: 004 - Notifications
title: Integrating with Notifications
+description: How to integrate your plugin with Backstage Notifications
---
## Notifications
diff --git a/docs/golden-path/plugins/integrations/__meta__.md b/docs/golden-path/plugins/integrations/__meta__.md
index 2d8df8136e..de9c90befc 100644
--- a/docs/golden-path/plugins/integrations/__meta__.md
+++ b/docs/golden-path/plugins/integrations/__meta__.md
@@ -1,19 +1,19 @@
-## Integrations
+# Integrations
Now that our plugin is ready for prime time, let's see how we can really leverage the rest of the Backstage ecosystem. Backstage provides a set of core features out of the box, namely, the Software Catalog, Search, Permissions, and Notifications.
-### Catalog
+## Catalog
We want to show our todos as separate Catalog entities. How can we make this happen?
-### Search
+## Search
We want to make our todos searchable.
-### Permissions
+## Permissions
We only want users to be able to find their own todos.
-### Notifications
+## Notifications
We want to set an alarm time for todos that sends a notification when the time is met.
diff --git a/docs/golden-path/plugins/sustainable-plugin-development.md b/docs/golden-path/plugins/sustainable-plugin-development.md
index 82d529d6e1..d19d5b7ac8 100644
--- a/docs/golden-path/plugins/sustainable-plugin-development.md
+++ b/docs/golden-path/plugins/sustainable-plugin-development.md
@@ -2,28 +2,59 @@
id: sustainable-plugin-development
sidebar_label: Sustainable plugin development
title: Sustainably developing plugins in Backstage
+description: Best practices for maintaining and iterating on Backstage plugins long-term
---
Plugins are not created in a vacuum, they generally solve a customer ask, be that
- a business problem, like showing cloud spend
-- a new integration, like showing data from an external vendor such as Pagerduty
+- a new integration, like showing data from an external vendor such as PagerDuty
- a developer pain point, like organizing information from disjoint or disorganized systems.
To ensure that your plugin lives the test of time, you'll need to figure out how to keep it up-to-date,
-### Finding your stakeholders
+## Finding your stakeholders
-
+Your internal developer teams are your customers. They are the ones who will use your plugin day-to-day, and they are the best source of truth for whether it's solving the right problems. Before writing a single line of code, it's worth spending time talking to them.
-### Iterating on your plugin
+Start by identifying which teams are most affected by the problem you're trying to solve. If you're building a cloud spend plugin, talk to the teams who own cloud infrastructure. If you're consolidating on-call information, find the teams that own your incident response process. These are your primary stakeholders.
-In many cases, your first version of a plugin will cut a few corners - this is a good sign, you're more focused on delivering a strong use case to continue development than over-indexing on your initial code. It may be temporary after all, if you don't get the response you're looking for!
+When you meet with them, listen for:
+
+- **Pain points**: What manual steps are slowing them down? What information do they have to hunt for across multiple tools?
+- **Frequency**: How often do they hit this problem? A daily frustration is worth more investment than a quarterly one.
+- **Workarounds**: What are they doing today instead? Existing workarounds often reveal what the minimum viable solution needs to cover.
+
+Secondary stakeholders like platform teams, engineering managers, or team leads can give you a broader view of organizational needs and help you prioritize across teams. They can also act as champions who drive adoption once your plugin ships.
+
+Keep these conversations ongoing. A quick check-in after you ship an early version is often more valuable than a long requirements-gathering session upfront. Your stakeholders will give you much clearer feedback once they've seen something working.
+
+## Iterating on your plugin
+
+In many cases, your first version of a plugin will cut a few corners. This is a good sign; you're more focused on delivering a strong use case to continue development than over-indexing on your initial code. It may be temporary after all, if you don't get the response you're looking for!
So, how do you decide when you should iterate on your plugin?
-
+The clearest signal is feedback from the teams using it. Go back to the stakeholders you identified earlier. Are they actually using the plugin? Are they hitting friction you didn't anticipate? Direct conversations and usage patterns will tell you far more than assumptions made during initial development.
-### Ensuring the success of your plugin
+Some common triggers for iteration:
-
+- **Stakeholder feedback reveals a gap**: A team is using your plugin but still switching to another tool for one specific thing. That gap is your next iteration.
+- **Adoption is lower than expected**: If teams aren't using it, find out why before adding features. The problem is often discoverability, missing context, or a workflow mismatch rather than missing functionality.
+- **The underlying data or service has changed**: External systems evolve. If your plugin surfaces data from another service, keep a line open with the team that owns it so you're not caught off guard.
+- **Analytics surface unexpected patterns**: Backstage has built-in support for analytics events. If you've instrumented your plugin, usage data can reveal which parts of your plugin are heavily used, which are ignored, and where users drop off. A page with high traffic but short visit times might indicate users aren't finding what they need.
+
+Not every piece of feedback warrants an immediate change. Weigh requests against how broadly they apply; a request from one team may not justify the complexity it adds for everyone else. Your stakeholders are your best guide for prioritization here too.
+
+## Ensuring the success of your plugin
+
+A successful plugin is one that continues to be used, trusted, and improved over time. That doesn't happen by accident. It's the result of staying connected to the people who depend on it.
+
+The foundations are straightforward:
+
+- **Keep your stakeholder relationships active.** Don't treat the initial conversations as a one-time exercise. Check in regularly, especially after shipping new versions. The teams using your plugin are the fastest way to find out what's working and what isn't.
+- **Let feedback drive your priorities.** It can be tempting to iterate on the parts of your plugin you find most interesting technically. Let usage patterns and stakeholder feedback anchor your roadmap instead.
+- **Instrument your plugin.** Analytics give you signal between conversations. If a feature is being ignored, or a page has high drop-off, that's worth investigating before investing further in that direction.
+- **Treat it like a product, not a project.** A plugin that ships and gets abandoned quickly loses the trust of its users. Even small, regular improvements signal that the plugin is maintained and worth relying on.
+
+The common thread across all of this is that your developers are your customers. Keeping that relationship healthy is what separates a plugin that becomes a critical part of your developer portal from one that quietly gets bypassed.
diff --git a/docs/golden-path/plugins/why-build-plugins.md b/docs/golden-path/plugins/why-build-plugins.md
index b0e1be0a98..36a74158c2 100644
--- a/docs/golden-path/plugins/why-build-plugins.md
+++ b/docs/golden-path/plugins/why-build-plugins.md
@@ -2,6 +2,7 @@
id: why-build-plugins
sidebar_label: Why build plugins?
title: Introduction to the Value and Impact of Plugins within Backstage
+description: Understanding the benefits and impact of building plugins for Backstage
---
Backstage plugins are essential components that enable the integration of various tools and services into a unified developer portal. Here’s a detailed look at why building plugins for Backstage can be highly beneficial:
diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md
index fcba5ca04f..74e2fde6a5 100644
--- a/docs/integrations/aws-s3/discovery.md
+++ b/docs/integrations/aws-s3/discovery.md
@@ -5,10 +5,6 @@ sidebar_label: Discovery
description: Automatically discovering catalog entities from an AWS S3 Bucket
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/aws-s3/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
The AWS S3 integration has a special entity provider for discovering catalog
entities located in an S3 Bucket. If you have a bucket that contains multiple
catalog files, and you want to automatically discover them, you can use this
diff --git a/docs/integrations/azure-blobStorage/discovery.md b/docs/integrations/azure-blobStorage/discovery.md
index bed97441f9..f0019a5c01 100644
--- a/docs/integrations/azure-blobStorage/discovery.md
+++ b/docs/integrations/azure-blobStorage/discovery.md
@@ -5,10 +5,6 @@ sidebar_label: Discovery
description: Automatically discovering catalog entities from an Azure Blob Storage account
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md).
-:::
-
The Azure Blob Storage account integration has a special entity provider for discovering catalog
entities located in a storage account container. If you have a container that contains multiple
catalog files, and you want to automatically discover them, you can use this
@@ -65,7 +61,7 @@ the Azure catalog plugin:
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure
```
-Then updated your backend by adding the following line:
+Then update your backend by adding the following line:
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend'));
diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md
index 7e4d376a7d..67d70c7f6d 100644
--- a/docs/integrations/azure/discovery.md
+++ b/docs/integrations/azure/discovery.md
@@ -5,10 +5,6 @@ sidebar_label: Discovery
description: Automatically discovering catalog entities from repositories in an Azure DevOps organization
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/azure/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
The Azure DevOps integration has a special entity provider for discovering catalog entities within an Azure DevOps. The provider will crawl your Azure DevOps organization and register entities matching the configured path. This can be useful as an alternative to static locations or manually adding things to the catalog.
This guide explains how to install and configure the Azure DevOps Entity Provider (recommended) or the Azure DevOps Processor.
@@ -101,7 +97,7 @@ the Azure catalog plugin:
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure
```
-Then updated your backend by adding the following line:
+Then update your backend by adding the following line:
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend'));
diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md
index efcd8ae612..3c82f6e2be 100644
--- a/docs/integrations/azure/org.md
+++ b/docs/integrations/azure/org.md
@@ -5,10 +5,6 @@ sidebar_label: Org Data
description: Importing users and groups from Microsoft Entra ID into Backstage
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/azure/org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
The Backstage catalog can be set up to ingest organizational data - users and
teams - directly from a tenant in Microsoft Entra ID via the
Microsoft Graph API.
@@ -45,7 +41,7 @@ catalog:
For large organizations, this plugin can take a long time, so be careful setting low frequency / timeouts and importing a large amount of users / groups for the first try.
:::
-Finally, updated your backend by adding the following line:
+Finally, update your backend by adding the following line:
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend'));
@@ -124,7 +120,7 @@ microsoftGraphOrg:
In addition to these groups, one additional group will be created for your organization.
All imported groups will be a child of this group.
-By default the provider will get groups using the msgraph `/group` endpoint, but it is possible to use different endpoints by setting the `path` configuration. All the endpoint containing `/microsoft.graph.group` will return the right type of group object. [See usage](#Using-path-parameter) for more details.
+By default the provider will get groups using the msgraph `/group` endpoint, but it is possible to use different endpoints by setting the `path` configuration. All the endpoint containing `/microsoft.graph.group` will return the right type of group object. [See usage](#using-path-parameter) for more details.
### Users
@@ -149,7 +145,7 @@ microsoftGraphOrg:
search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")'
```
-By default the provider will get user using the msgraph `/user` endpoint, but it is possible to use different endpoints by setting the `path` configuration. All the endpoint containing `/microsoft.graph.user` will return the right type of user object. [See usage](#Using-path-parameter) for more details.
+By default the provider will get user using the msgraph `/user` endpoint, but it is possible to use different endpoints by setting the `path` configuration. All the endpoint containing `/microsoft.graph.user` will return the right type of user object. [See usage](#using-path-parameter) for more details.
### Using `path` parameter
diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md
index 3f5920be5d..21b896e072 100644
--- a/docs/integrations/bitbucketCloud/discovery.md
+++ b/docs/integrations/bitbucketCloud/discovery.md
@@ -39,9 +39,9 @@ package.
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbucket-cloud
```
-### Installation with New Backend System
+Then add the following to your backend:
-```ts
+```ts title="packages/backend/src/index.ts"
// optional if you want HTTP endpoints to receive external events
// backend.add(import('@backstage/plugin-events-backend'));
// optional if you want to use AWS SQS instead of HTTP endpoints to receive external events
@@ -63,59 +63,6 @@ Further documentation:
-
-
-### Installation with Legacy Backend System
-
-Please follow the installation instructions at
-
--
--
-
-Additionally, you need to decide how you want to receive events from external sources like
-
-- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
-- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
-- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md)
-- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md)
-
-Set up your provider
-
-```ts title="packages/backend/src/plugins/catalog.ts"
-import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
-/* highlight-add-start */
-import { BitbucketCloudEntityProvider } from '@backstage/plugin-catalog-backend-module-bitbucket-cloud';
-/* highlight-add-end */
-
-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
-import { Router } from 'express';
-import { PluginEnvironment } from '../types';
-
-export default async function createPlugin(
- env: PluginEnvironment,
-): Promise {
- const builder = await CatalogBuilder.create(env);
- /* highlight-add-start */
- const bitbucketCloudProvider = BitbucketCloudEntityProvider.fromConfig(
- env.config,
- {
- auth: env.auth,
- catalogApi: new CatalogClient({ discoveryApi: env.discovery }),
- events: env.events,
- logger: env.logger,
- scheduler: env.scheduler,
- },
- );
- builder.addEntityProvider(bitbucketCloudProvider);
- /* highlight-add-end */
- const { processingEngine, router } = await builder.build();
- await processingEngine.start();
- return router;
-}
-```
-
-**Attention:**
-`catalogApi` and `tokenManager` are required at this variant
-compared to the one without events support.
-
## Configuration
To use the entity provider, you'll need a [Bitbucket Cloud integration set up](locations.md).
diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md
index bbdd105af4..ee96118f2a 100644
--- a/docs/integrations/bitbucketServer/discovery.md
+++ b/docs/integrations/bitbucketServer/discovery.md
@@ -5,10 +5,6 @@ sidebar_label: Discovery
description: Automatically discovering catalog entities from repositories in Bitbucket Server
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/bitbucketServer/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
The Bitbucket Server integration has a special entity provider for discovering
catalog files located in Bitbucket Server.
The provider will search your Bitbucket Server account and register catalog files matching the configured path
@@ -25,9 +21,9 @@ dependency to `@backstage/plugin-catalog-backend-module-bitbucket-server` to you
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbucket-server
```
-### Installation with New Backend System
+Then add the following to your backend:
-```ts
+```ts title="packages/backend/src/index.ts"
// optional if you want HTTP endpojnts to receive external events
// backend.add(import('@backstage/plugin-events-backend'));
// optional if you want to use AWS SQS instead of HTTP endpoints to receive external events
diff --git a/docs/integrations/gerrit/discovery.md b/docs/integrations/gerrit/discovery.md
index 06fb144e50..a9ea80e088 100644
--- a/docs/integrations/gerrit/discovery.md
+++ b/docs/integrations/gerrit/discovery.md
@@ -5,10 +5,6 @@ sidebar_label: Discovery
description: Automatically discovering catalog entities from Gerrit repositories
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/gerrit/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
The Gerrit integration has a special entity provider for discovering catalog entities
from Gerrit repositories. The provider uses the "List Projects" API in Gerrit to get
a list of repositories and will automatically ingest all `catalog-info.yaml` files
diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md
index 66fdaa503b..c4dbc2e10b 100644
--- a/docs/integrations/github/discovery.md
+++ b/docs/integrations/github/discovery.md
@@ -5,10 +5,6 @@ sidebar_label: Discovery
description: Automatically discovering catalog entities from repositories in a GitHub organization or App
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/github/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
## GitHub Provider
The GitHub integration has a discovery provider for discovering catalog
diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md
index 5321423432..1d5eac1581 100644
--- a/docs/integrations/github/org.md
+++ b/docs/integrations/github/org.md
@@ -5,10 +5,6 @@ sidebar_label: Org Data
description: Importing users and groups from a GitHub organization into Backstage
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/github/org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
The Backstage catalog can be set up to ingest organizational data - users and
teams - directly from an organization in GitHub or GitHub Enterprise. The result
is a hierarchy of
diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md
index 4d801bdd77..315cc9a4a7 100644
--- a/docs/integrations/gitlab/discovery.md
+++ b/docs/integrations/gitlab/discovery.md
@@ -23,8 +23,6 @@ the gitlab catalog plugin:
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gitlab
```
-### Installation with New Backend System
-
Then add the following to your backend initialization:
```ts title="packages/backend/src/index.ts"
@@ -51,87 +49,6 @@ Further documentation:
- [Events Plugin](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [GitLab Module for the Events Plugin](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md)
-### Installation with Legacy Backend System (skip if you are using Backstage v1.31.0 or later)
-
-#### Installation without Events Support
-
-Add the segment below to `packages/backend/src/plugins/catalog.ts`:
-
-```ts title="packages/backend/src/plugins/catalog.ts"
-/* highlight-add-next-line */
-import { GitlabDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab';
-
-export default async function createPlugin(
- env: PluginEnvironment,
-): Promise {
- const builder = await CatalogBuilder.create(env);
- /* highlight-add-start */
- builder.addEntityProvider(
- ...GitlabDiscoveryEntityProvider.fromConfig(env.config, {
- logger: env.logger,
- // optional: alternatively, use scheduler with schedule defined in app-config.yaml
- schedule: env.scheduler.createScheduledTaskRunner({
- frequency: { minutes: 30 },
- timeout: { minutes: 3 },
- }),
- // optional: alternatively, use schedule
- scheduler: env.scheduler,
- }),
- );
- /* highlight-add-end */
- // ..
-}
-```
-
-#### Installation with Events Support
-
-Please follow the installation instructions at
-
-- [Events Plugin](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
-- [GitLab Module for the Events Plugin](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md)
-
-Additionally, you need to decide how you want to receive events from external sources like
-
-- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
-- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
-- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md)
-- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md)
-
-Set up your provider
-
-```ts title="packages/backend/src/plugins/catalog.ts"
-import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
-/* highlight-add-next-line */
-import { GitlabDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab';
-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
-import { Router } from 'express';
-import { PluginEnvironment } from '../types';
-
-export default async function createPlugin(
- env: PluginEnvironment,
-): Promise {
- const builder = await CatalogBuilder.create(env);
- builder.addProcessor(new ScaffolderEntitiesProcessor());
- /* highlight-add-start */
- const gitlabProvider = GitlabDiscoveryEntityProvider.fromConfig(env.config, {
- logger: env.logger,
- // optional: alternatively, use scheduler with schedule defined in app-config.yaml
- schedule: env.scheduler.createScheduledTaskRunner({
- frequency: { minutes: 30 },
- timeout: { minutes: 3 },
- }),
- // optional: alternatively, use schedule
- scheduler: env.scheduler,
- events: env.events,
- });
- builder.addEntityProvider(gitlabProvider);
- /* highlight-add-end */
- const { processingEngine, router } = await builder.build();
- await processingEngine.start();
- return router;
-}
-```
-
## Configuration
To use the discovery provider, you'll need a GitLab integration
@@ -140,7 +57,7 @@ to the catalog configuration.
:::note Note
-If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below.
+The `schedule` has to be setup in the config, as shown below.
:::
diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md
index bddd1c13a1..6c5641924d 100644
--- a/docs/integrations/gitlab/org.md
+++ b/docs/integrations/gitlab/org.md
@@ -29,8 +29,6 @@ As this provider is not one of the default providers, you will first need to ins
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gitlab @backstage/plugin-catalog-backend-module-gitlab-org
```
-### Installation with New Backend System
-
Then add the following to your backend initialization:
```ts title="packages/backend/src/index.ts
@@ -58,92 +56,6 @@ Further documentation:
- [Events Plugin](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [GitLab Module for the Events Plugin](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md)
-### Installation with Legacy Backend System
-
-#### Installation without Events Support
-
-Add the plugin to the plugin catalog `packages/backend/src/plugins/catalog.ts`:
-
-```ts
-/* packages/backend/src/plugins/catalog.ts */
-/* highlight-add-next-line */
-import { GitlabOrgDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab';
-
-export default async function createPlugin(
- env: PluginEnvironment,
-): Promise {
- const builder = await CatalogBuilder.create(env);
- /** ... other processors and/or providers ... */
- /* highlight-add-start */
- builder.addEntityProvider(
- ...GitlabOrgDiscoveryEntityProvider.fromConfig(env.config, {
- logger: env.logger,
- // optional: alternatively, use scheduler with schedule defined in app-config.yaml
- schedule: env.scheduler.createScheduledTaskRunner({
- frequency: { minutes: 30 },
- timeout: { minutes: 3 },
- }),
- // optional: alternatively, use schedule
- scheduler: env.scheduler,
- }),
- );
- /* highlight-add-end */
- // ..
-}
-```
-
-#### Installation with Events Support
-
-Please follow the installation instructions at
-
-- [Events Plugin](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
-- [GitLab Module for the Events Plugin](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md)
-
-Additionally, you need to decide how you want to receive events from external sources like
-
-- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
-- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
-- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md)
-- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md)
-
-Set up your provider
-
-```ts title="packages/backend/src/plugins/catalog.ts"
-import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
-/* highlight-add-next-line */
-import { GitlabOrgDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab';
-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
-import { Router } from 'express';
-import { PluginEnvironment } from '../types';
-
-export default async function createPlugin(
- env: PluginEnvironment,
-): Promise {
- const builder = await CatalogBuilder.create(env);
- builder.addProcessor(new ScaffolderEntitiesProcessor());
- /* highlight-add-start */
- const gitlabOrgProvider = GitlabOrgDiscoveryEntityProvider.fromConfig(
- env.config,
- {
- logger: env.logger,
- // optional: alternatively, use scheduler with schedule defined in app-config.yaml
- schedule: env.scheduler.createScheduledTaskRunner({
- frequency: { minutes: 30 },
- timeout: { minutes: 3 },
- }),
- // optional: alternatively, use schedule
- scheduler: env.scheduler,
- events: env.events,
- },
- );
- builder.addEntityProvider(gitlabOrgProvider);
- /* highlight-add-end */
- const { processingEngine, router } = await builder.build();
- await processingEngine.start();
- return router;
-}
-```
-
## Configuration
To use the entity provider, you'll need a [Gitlab integration set up](https://backstage.io/docs/integrations/gitlab/locations).
@@ -163,7 +75,7 @@ will be those visible to the account which provisioned the token.
:::note Note
-If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below.
+The `schedule` has to be setup in the config, as shown below.
:::
diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md
index 8adc81184a..22a843009f 100644
--- a/docs/integrations/ldap/org.md
+++ b/docs/integrations/ldap/org.md
@@ -40,7 +40,7 @@ catalog:
timeout: PT15M
```
-Finally, updated your backend by adding the following line:
+Finally, update your backend by adding the following line:
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend'));
diff --git a/docs/integrations/okta/org.md b/docs/integrations/okta/org.md
new file mode 100644
index 0000000000..63769e7c85
--- /dev/null
+++ b/docs/integrations/okta/org.md
@@ -0,0 +1,23 @@
+---
+id: org
+title: Okta Organizational Data
+sidebar_label: Org Data
+description: Ingesting organizational data from Okta into Backstage
+---
+
+The Backstage catalog can be set up to ingest organizational data — users and
+groups — directly from Okta. The result is a hierarchy of
+[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and
+[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind
+entities that mirror your Okta organization.
+
+This integration is provided by the community-maintained
+[`@roadiehq/catalog-backend-module-okta`](https://github.com/RoadieHQ/roadie-backstage-plugins/tree/main/plugins/backend/catalog-backend-module-okta)
+plugin, owned and maintained by [Roadie](https://roadie.io/).
+
+## Installation and configuration
+
+For setup instructions, including authentication options (API token and OAuth
+2.0), user/group filtering, custom naming strategies, and entity transformers,
+see the
+[plugin documentation maintained by Roadie](https://github.com/RoadieHQ/roadie-backstage-plugins/tree/main/plugins/backend/catalog-backend-module-okta).
diff --git a/docs/landing-page/doc-landing-page.md b/docs/landing-page/doc-landing-page.md
index 8697144bd8..b7cc44c696 100644
--- a/docs/landing-page/doc-landing-page.md
+++ b/docs/landing-page/doc-landing-page.md
@@ -33,13 +33,26 @@ description: Documentation landing page.
Configure, Deploy, & Upgrade.
@@ -60,7 +73,7 @@ description: Documentation landing page.
Search
Software Catalog
Software Templates (aka Scaffolder)
- TechDocs
+ TechDocs - a docs-like-code solution
@@ -102,7 +115,7 @@ description: Documentation landing page.
diff --git a/docs/notifications/index--old.md b/docs/notifications/index--old.md
new file mode 100644
index 0000000000..d99f5144bc
--- /dev/null
+++ b/docs/notifications/index--old.md
@@ -0,0 +1,299 @@
+---
+id: index--old
+title: Getting Started (Old Frontend System)
+description: How to get started with the notifications and signals
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./index.md) instead.
+::::
+
+The Backstage Notifications System provides a way for plugins and external services to send notifications to Backstage users.
+These notifications are displayed in the dedicated page of the Backstage frontend UI or by frontend plugins per specific scenarios.
+Additionally, notifications can be sent to external channels (like email) via "processors" implemented within plugins.
+
+Notifications can be optionally extended with the signals plugin, which provides a push mechanism to ensure users receive notifications immediately.
+
+### Upgrade to the latest version of Backstage
+
+To ensure your version of Backstage has all the latest notifications and signals related functionality, it's important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you've made all the necessary changes during the upgrade!
+
+## About notifications
+
+Notifications are messages sent to either individual users or groups.
+They are not intended for inter-process communication of any kind.
+
+There are two basic types of notifications:
+
+- **Broadcast**: Messages sent to all users of Backstage.
+- **Entity**: Messages delivered to specific listed entities, such as Users or Groups.
+
+Example of use-cases:
+
+- System-wide announcements or alerts
+- Notifications for component owners, e.g. build failures, successful deployments, new vulnerabilities
+- Notifications for individuals, e.g. updates you have subscribed to, new required training courses
+- Notifications pertaining to a particular entity in the catalog: A notification might apply to an entity and the owning team.
+
+## Installation
+
+:::note
+
+As of the `1.42.0` release of Backstage, Notifications and Signals are installed as part of the default `@backstage/create-app` instance which means you won't need to follow the installation steps outlined here. The only exception to this is adding the [Notifications tab to User Settings](#user-specific-notification-settings) to allow managing these settings.
+
+:::
+
+The following sections will walk you through the installation of the various parts of the Backstage Notification System.
+
+### Add Notifications Backend
+
+First we need to add the backend package:
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/backend add @backstage/plugin-notifications-backend
+```
+
+Then we need to add it to our backend:
+
+```ts title="packages/backend/src/index.ts"
+const backend = createBackend();
+// ...
+backend.add(import('@backstage/plugin-notifications-backend'));
+```
+
+### Add Notifications Frontend
+
+First we need to add the frontend package:
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/app add @backstage/plugin-notifications
+```
+
+To add the notifications main menu, add the following:
+
+```tsx title="packages/app/src/components/Root/Root.tsx"
+import { NotificationsSidebarItem } from '@backstage/plugin-notifications';
+
+
+
+
+ // ...
+
+
+
+ ;
+```
+
+Also add the route to notifications:
+
+```tsx title="packages/app/src/App.tsx"
+import { NotificationsPage } from '@backstage/plugin-notifications';
+
+
+ // ...
+ } />
+ ;
+```
+
+### Optional: Add Signals
+
+The use of signals is optional but improves the user experience.
+
+#### Optional: Add Signals Backend
+
+Add signals to your backend by first adding the backend package:
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/backend add @backstage/plugin-signals-backend
+```
+
+Then add the signals plugin to your backend:
+
+```ts title="packages/backend/src/index.ts"
+const backend = createBackend();
+// ...
+backend.add(import('@backstage/plugin-signals-backend'));
+```
+
+#### Optional: Signals Frontend
+
+Start with adding the frontend package:
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/app add @backstage/plugin-signals
+```
+
+To install the plugin, add the `SignalsDisplay` to your app root:
+
+```tsx title="packages/app/src/App.tsx"
+import { SignalsDisplay } from '@backstage/plugin-signals';
+
+export default app.createRoot(
+ <>
+
+
+ {/* highlight-add-next-line */}
+
+
+
+ {routes}
+
+ >,
+);
+```
+
+If the signals plugin is properly configured, it will be automatically discovered by the notifications plugin and used.
+
+### User-specific notification settings
+
+The notifications plugin provides a way for users to manage their notification settings. To enable this, you must
+add the `UserNotificationSettingsCard` to your frontend.
+
+```tsx title="packages/app/src/App.tsx"
+ }>
+
+
+
+
+```
+
+
+
+You can customize the origin names shown in the UI by passing an object where the keys are the origins and the values are the names you want to show in the UI.
+
+Each notification processor will receive its own row in the settings page, where the user can enable or disable notifications from that processor.
+
+### Default notification settings
+
+You can configure default notification settings for all users in your `app-config.yaml` file. This allows you to set up notification preferences globally, such as disabling specific channels or origins by default, implementing an opt-in strategy instead of opt-out.
+
+#### Channel-level defaults
+
+You can set a default enabled state for an entire channel. When set to `false`, the channel uses an opt-in strategy where notifications are disabled by default unless explicitly enabled by the user or for specific origins.
+
+```yaml
+notifications:
+ defaultSettings:
+ channels:
+ - id: 'Web'
+ enabled: false # Opt-in strategy: channel disabled by default
+ - id: 'Email'
+ enabled: true # Opt-out strategy: channel enabled by default (default behavior)
+```
+
+#### Origin-level defaults
+
+You can also configure defaults for specific origins within a channel:
+
+```yaml
+notifications:
+ defaultSettings:
+ channels:
+ - id: 'Web'
+ enabled: true # Channel is enabled by default
+ origins:
+ - id: 'plugin:scaffolder'
+ enabled: false # Disable scaffolder notifications by default
+ - id: 'plugin:catalog'
+ enabled: true # Enable catalog notifications by default
+```
+
+#### Topic-level defaults
+
+For even more granular control, you can set defaults for specific topics within origins:
+
+```yaml
+notifications:
+ defaultSettings:
+ channels:
+ - id: 'Email'
+ enabled: false # Email is opt-in by default
+ origins:
+ - id: 'plugin:catalog'
+ enabled: true # But catalog notifications are enabled
+ topics:
+ - id: 'entity:validation:error'
+ enabled: false # Except validation errors
+```
+
+**Note:** If a channel's `enabled` flag is not set, it defaults to `true` for backwards compatibility. When a channel is set to `enabled: false`, all origins within that channel default to disabled unless explicitly enabled.
+
+### Automatic notification cleanup
+
+Notifications are deleted automatically after a certain period of time to prevent the database from growing indefinitely
+and to keep the user interface clean. The default retention period is set to 1 year, meaning that notifications older
+than that will be deleted automatically.
+
+The retention period can be configured by setting the `notifications.retention` in the `app-config.yaml` file.
+
+```yaml
+notifications:
+ retention: 1y
+```
+
+If the retention is set to false, notifications will not be automatically deleted.
+
+## Scaffolder Action
+
+:::note
+
+As of the `1.42.0` release of Backstage, the Notifications Scaffolder action is installed as part of the default `@backstage/create-app` instance which means you won't need to follow the installations steps outlined here. Feel free to skip to the [Basic Example](#basic-example).
+
+:::
+
+There is also a Scaffolder action that you can use to send a notification as part of your Software Template.
+
+First we need to add the backend package:
+
+```bash title="From your Backstage root directory"
+yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-notifications
+```
+
+Then we need to add it to our backend:
+
+```ts title="packages/backend/src/index.ts"
+const backend = createBackend();
+// ...
+backend.add(
+ import('@backstage/plugin-scaffolder-backend-module-notifications'),
+);
+```
+
+### Basic Example
+
+Here's an example of how you can use it in your Software Template, more details and examples can be found in the "Installed actions" screen in your Backstage instances:
+
+```yaml title="template.yaml"
+steps:
+ - id: notify
+ name: Notify
+ action: notification:send
+ input:
+ recipients: entity
+ entityRefs:
+ - user:default/guest
+ title: 'Template executed'
+ info: 'Your template has been executed'
+ severity: 'normal'
+```
+
+The example above would send a notification to the Guest user (`user:default/guest`)
+
+## Additional info
+
+An example of a backend plugin sending notifications can be found in the [`@backstage/plugin-scaffolder-backend-module-notifications` package](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-notifications).
+
+Sources of the notifications and signals plugins:
+
+- [notifications](https://github.com/backstage/backstage/blob/master/plugins/notifications)
+- [notifications-backend](https://github.com/backstage/backstage/blob/master/plugins/notifications-backend)
+- [notifications-common](https://github.com/backstage/backstage/blob/master/plugins/notifications-common)
+- [notifications-node](https://github.com/backstage/backstage/blob/master/plugins/notifications-node)
+- [signals-backend](https://github.com/backstage/backstage/blob/master/plugins/signals-backend)
+- [signals](https://github.com/backstage/backstage/blob/master/plugins/signals)
+- [signals-node](https://github.com/backstage/backstage/blob/master/plugins/signals-node)
+- [signals-react](https://github.com/backstage/backstage/blob/master/plugins/signals-react)
diff --git a/docs/notifications/index.md b/docs/notifications/index.md
index 3d7dc0eaac..fde24db7cb 100644
--- a/docs/notifications/index.md
+++ b/docs/notifications/index.md
@@ -4,6 +4,13 @@ title: Getting Started
description: How to get started with the notifications and signals
---
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./index--old.md)
+instead.
+::::
+
The Backstage Notifications System provides a way for plugins and external services to send notifications to Backstage users.
These notifications are displayed in the dedicated page of the Backstage frontend UI or by frontend plugins per specific scenarios.
Additionally, notifications can be sent to external channels (like email) via "processors" implemented within plugins.
@@ -12,7 +19,7 @@ Notifications can be optionally extended with the signals plugin, which provides
### Upgrade to the latest version of Backstage
-To ensure your version of Backstage has all the latest notifications and signals related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade!
+To ensure your version of Backstage has all the latest notifications and signals related functionality, it's important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you've made all the necessary changes during the upgrade!
## About notifications
@@ -35,7 +42,7 @@ Example of use-cases:
:::note
-As of the `1.42.0` release of Backstage, Notifications and Signals are installed as part of the default `@backstage/create-app` instance which means you won't need to follow the installations steps outlined here. The only exception to this is adding the [Notifications tab to User Settings](#user-specific-notification-settings) to allow managing these settings.
+As of the `1.42.0` release of Backstage, Notifications and Signals are installed as part of the default `@backstage/create-app` instance which means you won't need to follow the installation steps outlined here. The only exception to this is adding the [Notifications sidebar item](#add-notifications-sidebar-item) and optionally [Notifications tab to User Settings](#user-specific-notification-settings).
:::
@@ -65,35 +72,29 @@ First we need to add the frontend package:
yarn --cwd packages/app add @backstage/plugin-notifications
```
-To add the notifications main menu, add the following:
+Once installed, the notifications plugin is automatically available in your app through the default feature discovery. It provides a notifications page at `/notifications` and the notifications API. For more details and alternative installation methods, see [installing plugins](../frontend-system/building-apps/05-installing-plugins.md).
-```tsx title="packages/app/src/components/Root/Root.tsx"
+### Add Notifications Sidebar Item
+
+The notifications plugin does not yet include a built-in navigation item, so you need to add the `NotificationsSidebarItem` component to your sidebar manually. If you have a custom sidebar through a `NavContentBlueprint`, add the component there:
+
+```tsx
import { NotificationsSidebarItem } from '@backstage/plugin-notifications';
-
-
-
- // ...
-
-
-
- ;
-```
-
-Also add the route to notifications:
-
-```tsx title="packages/app/src/App.tsx"
-import { NotificationsPage } from '@backstage/plugin-notifications';
-
-
- // ...
- } />
- ;
+// Inside your NavContentBlueprint component:
+
+ }>
+ {/* ... other items ... */}
+
+ } to="/settings">
+
+
+ ;
```
### Optional: Add Signals
-The use of signals is optional but improves the user experience.
+The use of signals is optional but improves the user experience by providing real-time push updates.
#### Optional: Add Signals Backend
@@ -119,42 +120,51 @@ Start with adding the frontend package:
yarn --cwd packages/app add @backstage/plugin-signals
```
-To install the plugin, add the `SignalsDisplay` to your app root:
-
-```tsx title="packages/app/src/App.tsx"
-import { SignalsDisplay } from '@backstage/plugin-signals';
-
-export default app.createRoot(
- <>
-
-
- {/* highlight-add-next-line */}
-
-
-
- {routes}
-
- >,
-);
-```
-
-If the signals plugin is properly configured, it will be automatically discovered by the notifications plugin and used.
+Once installed, the signals plugin is automatically available in your app through the default feature discovery. No additional configuration is required. If the signals plugin is properly configured, it will be automatically discovered by the notifications plugin and used.
### User-specific notification settings
-The notifications plugin provides a way for users to manage their notification settings. To enable this, you must
-add the `UserNotificationSettingsCard` to your frontend.
+The notifications plugin provides a way for users to manage their notification settings. To enable this, you can create a frontend module that adds a settings tab to the user-settings plugin using the `SubPageBlueprint`:
-```tsx title="packages/app/src/App.tsx"
- }>
-
-
-
-
+```tsx title="packages/app/src/modules/NotificationSettingsPage.tsx"
+import { Content } from '@backstage/core-components';
+import { UserNotificationSettingsCard } from '@backstage/plugin-notifications';
+
+export function NotificationSettingsPage() {
+ return (
+
+
+
+ );
+}
```
+```tsx title="packages/app/src/modules/notificationSettings.tsx"
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import { SubPageBlueprint } from '@backstage/frontend-plugin-api';
+
+export const notificationSettingsModule = createFrontendModule({
+ pluginId: 'user-settings',
+ extensions: [
+ SubPageBlueprint.make({
+ name: 'notifications',
+ params: {
+ path: 'notifications',
+ title: 'Notifications',
+ loader: () =>
+ import('./NotificationSettingsPage').then(m => (
+
+ )),
+ },
+ }),
+ ],
+});
+```
+
+Then install the module in your app by adding it to the features array of `createApp`, or through default feature discovery if your app is set up for it.
+

You can customize the origin names shown in the UI by passing an object where the keys are the origins and the values are the names you want to show in the UI.
diff --git a/docs/notifications/usage--old.md b/docs/notifications/usage--old.md
new file mode 100644
index 0000000000..8e64e37c4d
--- /dev/null
+++ b/docs/notifications/usage--old.md
@@ -0,0 +1,283 @@
+---
+id: usage--old
+title: Usage (Old Frontend System)
+description: How to use the notifications and signals
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./usage.md) instead.
+::::
+
+## Notifications Backend
+
+The notifications backend plugin provides an API to create notifications, list notifications per logged-in user, and search based on parameters.
+
+The plugin uses a relational [database](https://backstage.io/docs/getting-started/config/database) for persistence; no specifics are introduced in this context.
+
+No additional configuration in the app-config is needed, except for optional additional modules for `processors`.
+
+## Notifications Frontend
+
+The recipients of notifications have to be entities in the catalog, e.g., of the User or Group kind.
+
+Otherwise, no specific configuration is needed for the front-end notifications plugin.
+
+All parametrization is done through component properties, such as the `NotificationsSidebarItem`, which can be used as an active left-side menu item in the front-end.
+
+
+
+In the `packages/app/src/components/Root/Root.tsx`, tweak the [properties](https://backstage.io/api/stable/functions/_backstage_plugin-notifications.index.NotificationsSidebarItem.html) of the ` ` per specific needs.
+
+## Usage
+
+New notifications can be sent either by a backend plugin or by an external service through the REST API.
+
+## Backend
+
+Regardless of technical feasibility, a backend plugin should avoid directly accessing the notifications REST API.
+Instead, it should integrate with the `@backstage/plugin-notifications-node` to `send` (create) a new notification.
+
+The reasons for this approach include the propagation of authorization in the API request and improved maintenance and backward compatibility in the future.
+
+```ts
+import { notificationService } from '@backstage/plugin-notifications-node';
+
+export const myPlugin = createBackendPlugin({
+ pluginId: 'myPlugin',
+ register(env) {
+ env.registerInit({
+ deps: {
+ // ...
+ notificationService: notificationService,
+ },
+ async init({
+ // ...
+ notificationService,
+ }) {
+ httpRouter.use(
+ await createRouter({
+ // ...
+ notificationService,
+ }),
+ );
+ },
+ });
+ },
+});
+```
+
+To emit a new notification:
+
+```ts
+await notificationService.send({
+ recipients /* of the broadcast or entity type */,
+ payload /* actual message */,
+});
+```
+
+Consult the [API documentation](https://github.com/backstage/backstage/blob/master/plugins/notifications-node/report.api.md) for further details.
+
+### External Services
+
+When the emitter of a notification is a Backstage backend plugin, it is mandatory to use the integration via `@backstage/plugin-notifications-node` as described above.
+
+If the emitter is a service external to Backstage, an HTTP POST request can be issued directly to the API, assuming that authentication is properly configured.
+Refer to the [service-to-service auth documentation](https://backstage.io/docs/auth/service-to-service-auth) for more details, focusing on the Static Tokens section for the simplest setup option.
+
+An example request for creating a broadcast notification might look like:
+
+```bash
+curl -X POST https://[BACKSTAGE_BACKEND]/api/notifications -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_BASE64_SHARED_KEY_TOKEN" -d '{"recipients":{"type":"broadcast"},"payload": {"title": "Title of broadcast message","link": "http://foo.com/bar","severity": "high","topic": "The topic"}}'
+```
+
+### Scaffolder Templates
+
+You can use the `@backstage/plugin-scaffolder-backend-module-notifications` to send notifications when scaffolder templates are run. To install the module, add it to your backend plugin:
+
+```bash
+yarn workspace backend add @backstage/plugin-scaffolder-backend-module-notifications
+```
+
+Then, add the module to your backend:
+
+```ts
+const backend = createBackend();
+// ...
+backend.add(
+ import('@backstage/plugin-scaffolder-backend-module-notifications'),
+);
+```
+
+In your template you can now use `notification:send` action as part of the steps:
+
+```yaml
+steps:
+ - id: notify
+ name: Notify
+ action: notification:send
+ input:
+ recipients: entity
+ entityRefs:
+ - component:default/backstage
+ title: 'Template executed'
+ info: 'Your template has been executed'
+ severity: 'info'
+ link: https://backstage.io
+```
+
+## Signals
+
+The use of signals with notifications is optional but generally enhances user experience and performance.
+
+When a notification is created, a new signal is emitted to a general-purpose message bus to announce it to subscribed listeners.
+
+The frontend maintains a persistent connection (WebSocket) to receive these announcements from the notifications channel.
+The specific details of the updated or created notification should be retrieved via a request to the notifications API, except for new notifications, where the payload is included in the signal for performance reasons.
+
+In a frontend plugin, to subscribe to notifications' signals:
+
+```ts
+import { useSignal } from '@backstage/plugin-signals-react';
+
+const { lastSignal } = useSignal('notifications');
+
+React.useEffect(() => {
+ /* ... */
+}, [lastSignal, notificationsApi]);
+```
+
+#### Using signals in your own plugin
+
+It's possible to use signals in your own plugin to deliver data from the backend to the frontend in near real-time.
+
+To use signals in your own frontend plugin, you need to add the `useSignal` hook from `@backstage/plugin-signals-react` from `@backstage/plugin-notifications-common` with optional generic type of the signal.
+
+```ts
+// To use the same type of signal in the backend, this should be placed in a shared common package
+export type MySignalType = {
+ user: string;
+ data: string;
+ // ....
+};
+
+const { lastSignal } = useSignal('my-plugin');
+
+useEffect(() => {
+ if (lastSignal) {
+ // Do something with the signal
+ }
+}, [lastSignal]);
+```
+
+To send signals from the backend plugin, you must add the `signalsServiceRef` to your plugin or module as a dependency.
+
+```ts
+import { signalsServiceRef } from '@backstage/plugin-signals-node';
+export const myPlugin = createBackendPlugin({
+ pluginId: 'my',
+ register(env) {
+ env.registerInit({
+ deps: {
+ httpRouter: coreServices.httpRouter,
+ signals: signalsServiceRef,
+ },
+ async init({ httpRouter, signals }) {
+ httpRouter.use(
+ await createRouter({
+ signals,
+ }),
+ );
+ },
+ });
+ },
+});
+```
+
+To send the signal using the service, you can use the `publish` method.
+
+```ts
+signals.publish({ user: 'user', data: 'test' });
+```
+
+## Consuming Notifications
+
+In a front-end plugin, the simplest way to query a notification is by its ID:
+
+```ts
+import { useApi } from '@backstage/core-plugin-api';
+import { notificationsApiRef } from '@backstage/plugin-notifications';
+
+const notificationsApi = useApi(notificationsApiRef);
+
+notificationsApi.getNotification(yourId);
+
+// or with connection to signals:
+notificationsApi.getNotification(lastSignal.notification_id);
+```
+
+## Metadata Field
+
+The metadata field is a freeform object that is designed to be used by processors.
+
+### Well-known Notification Metadata Fields
+
+Below are metadata fields that will be commonly used between processors and have defined schematics.
+
+#### backstage.io/body.markdown
+
+```ts
+# Example:
+const payload = {
+ title: 'Entities Require Attention',
+ description: 'Entities: Service A, Service B',
+ metadata: {
+ 'backstage.io/body.markdown': `
+ # Entities
+ - Service A
+ - System B
+ `
+ }
+}
+```
+
+This value of this metadata field should be the notification message in markdown format. This allows additional formatting options for processors that support markdown.
+
+### Usage
+
+Below is an example of using the `backstage.io/body.markdown` metadata field in a custom processor.
+
+When sending a notification:
+
+```ts
+notificationService.send({
+ recipients: { type: 'entity', entityRef: 'group/default:team-a' },
+ payload: {
+ title: 'Notification',
+ description: 'Description'
+ metadata: {
+ 'backstage.io/body.markdown': `
+ ### Notification
+ Description
+ `,
+ },
+ },
+});
+```
+
+In the processor, you can then use the metadata field accordingly:
+
+```ts
+async postProcess(notification: Notification): Promise {
+ // We suggest you parse the metadata field with a schema, i.e. Zod
+ const parseResult = CustomProcessorMetadataSchema.safeParse(notification.payload.metadata ?? {});
+ const metadata = parseResult.success ? parseResult.data : {};
+
+ customNotificationSender.send({
+ to: getUsers(notification.recipients),
+ subject: notification.payload.title,
+ markdownText: metadata['backstage.io/body.markdown'] ?? notification.payload.description,
+ });
+}
+```
diff --git a/docs/notifications/usage.md b/docs/notifications/usage.md
index 7437ae1cce..279a1953e1 100644
--- a/docs/notifications/usage.md
+++ b/docs/notifications/usage.md
@@ -4,6 +4,13 @@ title: Usage
description: How to use the notifications and signals
---
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./usage--old.md)
+instead.
+::::
+
## Notifications Backend
The notifications backend plugin provides an API to create notifications, list notifications per logged-in user, and search based on parameters.
@@ -18,11 +25,11 @@ The recipients of notifications have to be entities in the catalog, e.g., of the
Otherwise, no specific configuration is needed for the front-end notifications plugin.
-All parametrization is done through component properties, such as the `NotificationsSidebarItem`, which can be used as an active left-side menu item in the front-end.
+The `NotificationsSidebarItem` component can be used as an active left-side menu item in the front-end. Since the notifications plugin does not yet include a built-in navigation item, it needs to be added manually to your sidebar through a `NavContentBlueprint` in a custom app module. See the [Getting Started](./index.md#add-notifications-sidebar-item) guide for setup instructions.

-In the `packages/app/src/components/Root/Root.tsx`, tweak the [properties](https://backstage.io/api/stable/functions/_backstage_plugin-notifications.index.NotificationsSidebarItem.html) of the ` ` per specific needs.
+You can customize the sidebar item using its [properties](https://backstage.io/api/stable/functions/_backstage_plugin-notifications.index.NotificationsSidebarItem.html) to fit your specific needs.
## Usage
@@ -225,7 +232,7 @@ Below are metadata fields that will be commonly used between processors and have
# Example:
const payload = {
title: 'Entities Require Attention',
- description: 'Entities: Service A, Service B'
+ description: 'Entities: Service A, Service B',
metadata: {
'backstage.io/body.markdown': `
# Entities
diff --git a/docs/openapi/01-getting-started.md b/docs/openapi/01-getting-started.md
index 875a369dbf..c300ac1370 100644
--- a/docs/openapi/01-getting-started.md
+++ b/docs/openapi/01-getting-started.md
@@ -26,7 +26,14 @@ This tutorial assumes that you're already familiar with the following,
1. How to build a Backstage plugin.
2. `Express.js` and `Typescript`
-3. OpenAPI 3.0 schemas
+3. OpenAPI 3.1 schemas
+
+:::note OpenAPI Version Support
+Backstage supports both OpenAPI 3.0 and 3.1 specifications. If you have existing OpenAPI 3.0 specs, we recommend that you migrate them to 3.1. The main changes are:
+
+- Replace `nullable: true` with `type: ['string', 'null']` or use `anyOf`/`oneOf`
+- Remove `allowReserved` from path parameters (only valid on query/cookie parameters in 3.1)
+ :::
### Setting up
diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md
index 6acba915d5..5c33c5f8e6 100644
--- a/docs/overview/threat-model.md
+++ b/docs/overview/threat-model.md
@@ -25,12 +25,12 @@ An **external user** is a user that does not belong to the other three groups, f
:::info
This section assumes that you are using the
-[new backend system](../backend-system/index.md) and at least Backstage release [version 1.24](../releases/v1.24.0.md). Before that Backstage did not come with built-in protection against unauthorized access and you were required to deploy it in a protected environment.
+[backend system](../backend-system/index.md) and at least Backstage release [version 1.24](../releases/v1.24.0.md). Before that Backstage did not come with built-in protection against unauthorized access and you were required to deploy it in a protected environment.
:::
Backstage is primarily designed to be deployed in a protected environment rather than being exposed to the public internet. From a confidentiality and integrity perspective, Backstage is designed to protect against unauthorized access to data and to ensure that data is not tampered with. However, Backstage does not provide more than rudimentary protection against denial of service attacks, and it is the responsibility of the operator to ensure that the Backstage deployment is protected against such attacks. A common and recommended way to protect a Backstage deployment from unauthorized access is to deploy it behind an authenticating proxy such as AWS’s ALB, GCP’s IAP, or Cloudflare Access.
-Users that are signed-in in to Backstage generally have full access to all information and actions. If more fine-grained control is required, the [permissions system](../permissions/overview.md) should be enabled and configured to restrict access as necessary.
+Users that are signed in to Backstage generally have full access to all information and actions. If more fine-grained control is required, the [permissions system](../permissions/overview.md) should be enabled and configured to restrict access as necessary.
An operator is responsible for protecting the integrity of configuration files as it may otherwise be possible to introduce vulnerable configurations, as well as the confidentiality of configured secrets related to Backstage as these typically include authentication details to third party systems.
diff --git a/docs/permissions/concepts.md b/docs/permissions/concepts.md
index 7c8b9e9f69..e43c944eff 100644
--- a/docs/permissions/concepts.md
+++ b/docs/permissions/concepts.md
@@ -22,6 +22,6 @@ In many cases, a permission represents a user's interaction with another object.
### Conditional decisions
-[Rules](../references/glossary.md#rule-permission-plugin) need additional data before they can be used in a decision. Once a [rule](../references/glossary.md#rule-permission-plugin) is bound to relevant information it forms a [condition](../references/glossary.md#condition-permission-plugin). Conditional decisions tell the [permission framework](#permission) to delegate evaluation to the [plugin](#plugin) that owns the corresponding [resource](#resource-permission-plugin). Permission requests that result in a conditional decision are allowed if all of the provided conditions evaluate to be true.
+[Rules](../references/glossary.md#rule-permission-plugin) need additional data before they can be used in a decision. Once a [rule](../references/glossary.md#rule-permission-plugin) is bound to relevant information it forms a [condition](../references/glossary.md#condition-permission-plugin). Conditional decisions tell the [permission framework](#permission) to delegate evaluation to the [plugin](../references/glossary.md#plugin) that owns the corresponding [resource](../references/glossary.md#resource-permission-plugin). Permission requests that result in a conditional decision are allowed if all of the provided conditions evaluate to be true.
A good example would be the catalog plugin's "has annotation" rule which needs to know what annotation to look for on a given entity. The permission framework would respond to a request by the catalog plugin in this case with a condition decision. The catalog plugin would then need to correctly filter for entities matching the "has annotations" condition. This conditional behavior avoids coupling between policies and resource schemas, and allows plugins to evaluate complex rules in an efficient way. For example, a plugin may convert a conditional decision to a database query instead of loading and filtering objects in memory.
diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md
index b4ca86adf5..039805bcb4 100644
--- a/docs/permissions/custom-rules.md
+++ b/docs/permissions/custom-rules.md
@@ -4,10 +4,6 @@ title: Defining custom permission rules
description: How to define custom permission rules for existing resources
---
-:::info
-This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/custom-rules--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
-:::
-
For some use cases, you may want to define custom [rules](../references/glossary.md#rule-permission-plugin) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of.
## Define a custom rule
@@ -27,7 +23,7 @@ import {
createConditionFactory,
createPermissionRule,
} from '@backstage/plugin-permission-node';
-import { z } from 'zod';
+import { z } from 'zod/v3';
export const isInSystemRule = createPermissionRule({
name: 'IS_IN_SYSTEM',
diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md
index f5b9092ec5..3e40b4597c 100644
--- a/docs/permissions/getting-started.md
+++ b/docs/permissions/getting-started.md
@@ -4,10 +4,6 @@ title: Getting Started
description: How to get started with the permission framework as an integrator
---
-:::info
-This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/getting-started--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
-:::
-
Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others.
## Prerequisites
diff --git a/docs/permissions/plugin-authors/01-setup.md b/docs/permissions/plugin-authors/01-setup.md
index 743c5c9718..4ed348dae6 100644
--- a/docs/permissions/plugin-authors/01-setup.md
+++ b/docs/permissions/plugin-authors/01-setup.md
@@ -4,10 +4,6 @@ title: 1. Tutorial setup
description: How to get started with the permission framework as a plugin author
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/01-setup--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
The following tutorial is designed to help plugin authors add support for permissions to their plugins. We'll add support for permissions to example `todo-list` and `todo-list-backend` plugins, but the process should be similar for other plugins!
The rest of this page is focused on adding the `todo-list` and `todo-list-backend` plugins to your Backstage instance. If you want to add support for permissions to your own plugin instead, feel free to skip to the [next section](./02-adding-a-basic-permission-check.md).
diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md
index 8ba3ac3152..3790fc8577 100644
--- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md
+++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md
@@ -4,10 +4,6 @@ title: 2. Adding a basic permission check
description: Explains how to add a basic permission check to a Backstage plugin
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
If the outcome of a permission check doesn't need to change for different [resources](../../references/glossary.md#resource-permission-plugin), you can use a _basic permission check_. For this kind of check, we simply need to define a permission, and call `authorize` with it.
For this tutorial, we'll use a basic permission check to authorize the `create` endpoint in our todo-backend. This will allow Backstage integrators to control whether each of their users is authorized to create todos by adjusting their [permission policy](../../references/glossary.md#policy-permission-plugin).
diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md
index afa56307ce..afdb3b8c62 100644
--- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md
+++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md
@@ -4,10 +4,6 @@ title: 3. Adding a resource permission check
description: Explains how to add a resource permission check to a Backstage plugin
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
When performing updates (or other operations) on specific [resources](../../references/glossary.md#resource-permission-plugin), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise.
## Creating the update permission
@@ -130,7 +126,7 @@ import {
createPermissionRule,
} from '@backstage/plugin-permission-node';
import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common';
-import { z } from 'zod';
+import { z } from 'zod/v3';
import { Todo, TodoFilter } from './todos';
export const todoListPermissionResourceRef = createPermissionResourceRef<
diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md
index 9b79cec042..28ed56608c 100644
--- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md
+++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md
@@ -4,10 +4,6 @@ title: 4. Authorizing access to paginated data
description: Explains how to authorize access to paginated data in a Backstage plugin
---
-:::info
-This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
-:::
-
Authorizing `GET /todos` is similar to the update endpoint, in that it should be possible to authorize access based on the characteristics of each resource. However, we'll need to authorize a list of resources for this endpoint.
One possible solution may leverage the batching functionality to authorize all of the todos, and then returning only the ones for which the decision was `ALLOW`:
diff --git a/docs/permissions/writing-a-policy.md b/docs/permissions/writing-a-policy.md
index 2bf2bcade9..652c698be2 100644
--- a/docs/permissions/writing-a-policy.md
+++ b/docs/permissions/writing-a-policy.md
@@ -4,10 +4,6 @@ title: Writing a permission policy
description: How to write your own permission policy as a Backstage integrator
---
-:::info
-This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/writing-a-policy--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
-:::
-
In the [previous section](./getting-started.md), we were able to set up the permission framework and make a simple change to our `TestPermissionPolicy` to confirm that policy is indeed wired up correctly.
That policy looked like this:
diff --git a/docs/plugins/add-to-directory.md b/docs/plugins/add-to-directory.md
index 48affef489..a87062e207 100644
--- a/docs/plugins/add-to-directory.md
+++ b/docs/plugins/add-to-directory.md
@@ -4,6 +4,12 @@ title: Add to Directory
description: Documentation on Adding Plugin to Plugin Directory
---
+:::caution Legacy Documentation
+
+This section is part of the legacy plugins documentation. The process for adding plugins to the directory described here is still current.
+
+:::
+
## Adding a Plugin to the Directory
To add a new plugin to the [plugin directory](https://backstage.io/plugins) create a file with the following pattern `.yaml` where `` is the name of your plugin. This file will go in [`microsite/data/plugins`](https://github.com/backstage/backstage/tree/master/microsite/data/plugins) with your plugin's information. Example:
diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md
index 3e8d5b4111..784895ef49 100644
--- a/docs/plugins/analytics.md
+++ b/docs/plugins/analytics.md
@@ -4,6 +4,12 @@ title: Plugin Analytics
description: Measuring usage of your Backstage instance.
---
+:::caution Legacy Documentation
+
+This section is part of the legacy plugins documentation. For the new frontend system version, see [Plugin Analytics](../frontend-system/building-plugins/08-analytics.md). The concepts and events described here apply to both the old and new frontend systems.
+
+:::
+
Setting up, maintaining, and iterating on an instance of Backstage can be a
large investment. To help measure return on this investment, Backstage comes
with an event-based Analytics API that grants app integrators the flexibility to
@@ -36,7 +42,6 @@ choice below.
| Analytics Tool | Support Status |
| ------------------------------------- | -------------- |
-| [Google Analytics][ga] | Yes ✅ |
| [Google Analytics 4][ga4] | Yes ✅ |
| [New Relic Browser][newrelic-browser] | Community ✅ |
| [Matomo][matomo] | Community ✅ |
@@ -47,7 +52,6 @@ To suggest an integration, please [open an issue][add-tool] for the analytics
tool your organization uses. Or jump to [Writing Integrations][int-howto] to
learn how to contribute the integration yourself!
-[ga]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga/README.md
[ga4]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga4/README.md
[newrelic-browser]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-newrelic-browser/README.md
[qm]: https://github.com/quantummetric/analytics-module-qm/blob/main/README.md
diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md
index 9b8b63fc89..91a3e04839 100644
--- a/docs/plugins/backend-plugin.md
+++ b/docs/plugins/backend-plugin.md
@@ -4,6 +4,12 @@ title: Backend plugins
description: Creating and Developing Backend plugins
---
+:::caution Legacy Documentation
+
+This section is part of the legacy plugins documentation. While this page already describes the new backend system patterns, the canonical documentation for building backend plugins has moved to [Building Backend Plugins and Modules](../backend-system/building-plugins-and-modules/01-index.md).
+
+:::
+
This page describes the process of creating and managing backend plugins in your
Backstage repository.
@@ -38,12 +44,6 @@ cd plugins/carmen-backend
yarn start
```
-:::note Note
-
-This documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md)
-
-:::
-
This will think for a bit, and then say `Listening on :7007`. In a different
terminal window, now run
diff --git a/docs/plugins/call-existing-api.md b/docs/plugins/call-existing-api.md
index 35a6d8d599..5b5a91b708 100644
--- a/docs/plugins/call-existing-api.md
+++ b/docs/plugins/call-existing-api.md
@@ -4,6 +4,12 @@ title: Call Existing API
description: Describes the various options that Backstage frontend plugins have, in communicating with service APIs that already exist
---
+:::caution Legacy Documentation
+
+This section is part of the legacy plugins documentation. The frontend code examples on this page use the old frontend system APIs (`discoveryApiRef`, `fetchApiRef` from `@backstage/core-plugin-api`). The same APIs are available in the new frontend system via `@backstage/frontend-plugin-api`. The general guidance on when to use direct requests vs. the proxy vs. a backend plugin remains valid for both systems.
+
+:::
+
This article describes the various options that Backstage frontend plugins have,
in communicating with service APIs that already exist. Each section below
describes a possible choice, and the circumstances under which it fits.
diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md
index d6c9abc98f..35da80a5b2 100644
--- a/docs/plugins/composability.md
+++ b/docs/plugins/composability.md
@@ -4,6 +4,12 @@ title: Composability System
description: Documentation for the Backstage plugin composability APIs.
---
+:::caution Legacy Documentation
+
+This page describes the composability system for the **old frontend system**, including `createRoutableExtension`, `createComponentExtension`, `RouteRef`, `ExternalRouteRef`, and component data. For the new frontend system, see [Extensions](../frontend-system/architecture/20-extensions.md), [Extension Blueprints](../frontend-system/architecture/23-extension-blueprints.md), and [Routes](../frontend-system/architecture/36-routes.md).
+
+:::
+
## Summary
This page describes the composability system that helps bring together content
diff --git a/docs/plugins/create-a-plugin.md b/docs/plugins/create-a-plugin.md
index ea0b088af7..8c97a3d30d 100644
--- a/docs/plugins/create-a-plugin.md
+++ b/docs/plugins/create-a-plugin.md
@@ -4,6 +4,12 @@ title: Create a Backstage Plugin
description: Documentation on How to Create a Backstage Plugin
---
+:::caution Legacy Documentation
+
+This page describes creating plugins for the **old frontend system**. For creating plugins using the new frontend system, see [Building Frontend Plugins](../frontend-system/building-plugins/01-index.md). For creating backend plugins, see [Building Backend Plugins and Modules](../backend-system/building-plugins-and-modules/01-index.md).
+
+:::
+
A Backstage Plugin adds functionality to Backstage.
## Create a Plugin
diff --git a/docs/plugins/feature-flags.md b/docs/plugins/feature-flags.md
index e9e000df0d..25251e3db5 100644
--- a/docs/plugins/feature-flags.md
+++ b/docs/plugins/feature-flags.md
@@ -4,6 +4,12 @@ title: Feature Flags
description: Details the process of defining setting and reading a feature flag.
---
+:::caution Legacy Documentation
+
+This page describes feature flags using the **old frontend system** APIs (`createPlugin` from `@backstage/core-plugin-api` and `createApp` from `@backstage/app-defaults`). For the new frontend system version, see [Feature Flags](../frontend-system/building-plugins/09-feature-flags.md). The `FeatureFlagged` component and `featureFlagsApiRef` work the same way in both systems.
+
+:::
+
Backstage offers the ability to define feature flags inside a plugin or during application creation. This allows you to restrict parts of your plugin to those individual users who have toggled the feature flag to on.
This page describes the process of defining, setting and reading a feature flag. If you are looking for using feature flags specifically with software templates please see [Writing Templates](https://backstage.io/docs/features/software-templates/writing-templates#remove-sections-or-fields-based-on-feature-flags).
@@ -19,11 +25,18 @@ import { createPlugin } from '@backstage/core-plugin-api';
export const examplePlugin = createPlugin({
// ...
- featureFlags: [{ name: 'show-example-feature' }],
+ featureFlags: [
+ {
+ name: 'show-example-feature',
+ description: 'Enables the new beta dashboard view',
+ },
+ ],
// ...
});
```
+Note that the `description` property is optional. If not provided, the default "Registered in {pluginId} plugin" message is shown.
+
### In the application
Defining a feature flag in the application is done by adding feature flags in `featureFlags` array in the
diff --git a/docs/plugins/index.md b/docs/plugins/index.md
index ef45152f55..388c3b1177 100644
--- a/docs/plugins/index.md
+++ b/docs/plugins/index.md
@@ -1,9 +1,15 @@
---
id: index
-title: Introduction to Plugins
-description: Learn about integrating various infrastructure and software development tools into Backstage through plugins.
+title: Introduction to Plugins (Legacy)
+description: Legacy documentation for integrating various infrastructure and software development tools into Backstage through plugins using the old frontend system.
---
+:::caution Legacy Documentation
+
+This section covers plugin development using the **old frontend system**. For new development, please refer to the [new frontend system](../frontend-system/index.md) and [new backend system](../backend-system/index.md) documentation. The content here is kept for reference and for maintaining existing plugins that have not yet been migrated.
+
+:::
+
Backstage orchestrates a cohesive single-page application by seamlessly integrating various plugins.
Our vision for the plugin ecosystem champions flexibility, empowering you to incorporate a broad spectrum of infrastructure and software development tools into Backstage as plugins. Adherence to stringent [design guidelines](../dls/design.md) guarantees a consistent and intuitive user experience across the entire plugin landscape.
diff --git a/docs/plugins/integrating-plugin-into-software-catalog.md b/docs/plugins/integrating-plugin-into-software-catalog.md
index 911d74b019..1e3ebb2d2b 100644
--- a/docs/plugins/integrating-plugin-into-software-catalog.md
+++ b/docs/plugins/integrating-plugin-into-software-catalog.md
@@ -4,6 +4,12 @@ title: Integrate into the Software Catalog
description: How to integrate a plugin into software catalog
---
+:::caution Legacy Documentation
+
+This page describes integrating plugins into the Software Catalog using the **old frontend system** patterns (`EntitySwitch`, `EntityLayout`, `EntityLayout.Route`). For the new frontend system, entity page integrations are done using `EntityCardBlueprint` and `EntityContentBlueprint` — see [Common Extension Blueprints](../frontend-system/building-plugins/03-common-extension-blueprints.md).
+
+:::
+
> This is an advanced use case and currently is an experimental feature. Expect
> API to change over time
@@ -94,7 +100,7 @@ const systemPage = (
-
+
diff --git a/docs/plugins/integrating-search-into-plugins.md b/docs/plugins/integrating-search-into-plugins.md
index fd80e0a1eb..cbd9c89e14 100644
--- a/docs/plugins/integrating-search-into-plugins.md
+++ b/docs/plugins/integrating-search-into-plugins.md
@@ -4,8 +4,10 @@ title: Integrating Search into a plugin
description: How to integrate Search into a Backstage plugin
---
-:::info
-This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/plugins/integrating-search-into-plugins--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
+:::caution Legacy Documentation
+
+This section is part of the legacy plugins documentation. The backend search collator patterns described here use the new backend system and are still current. The frontend search experience examples use the old frontend system APIs.
+
:::
The Backstage Search Platform was designed to give plugin developers the APIs
diff --git a/docs/plugins/internationalization.md b/docs/plugins/internationalization.md
index 2770f76799..67a6b962a0 100644
--- a/docs/plugins/internationalization.md
+++ b/docs/plugins/internationalization.md
@@ -4,6 +4,12 @@ title: Internationalization
description: Documentation on adding internationalization to plugins and apps
---
+:::caution Legacy Documentation
+
+This section is part of the legacy plugins documentation. For the new frontend system version, see [Internationalization](../frontend-system/building-plugins/07-internationalization.md). The i18n APIs (`createTranslationRef`, `useTranslationRef`) work the same way in both the old and new frontend systems.
+
+:::
+
## Overview
The Backstage core function provides internationalization for plugins and apps. The underlying library is [`i18next`](https://www.i18next.com/) with some additional Backstage typescript magic for type safety with keys.
diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md
index cf983615b5..e7cef270ab 100644
--- a/docs/plugins/new-backend-system.md
+++ b/docs/plugins/new-backend-system.md
@@ -4,6 +4,12 @@ title: New Backend System
description: Details of the new backend system
---
+:::caution Legacy Documentation
+
+This section is part of the legacy plugins documentation. The canonical documentation for the backend system has moved to the [Backend System](../backend-system/index.md) section, which includes more detailed and up-to-date guides for [building plugins and modules](../backend-system/building-plugins-and-modules/01-index.md), [architecture](../backend-system/architecture/01-index.md), and [core services](../backend-system/core-services/01-index.md).
+
+:::
+
## Status
The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system.
diff --git a/docs/plugins/observability.md b/docs/plugins/observability.md
index 7803d90bd5..eab2ed200c 100644
--- a/docs/plugins/observability.md
+++ b/docs/plugins/observability.md
@@ -4,6 +4,12 @@ title: Observability
description: Adding Observability to Your Plugin
---
+:::caution Legacy Documentation
+
+This section is part of the legacy plugins documentation. For new backend system logging, see the [Logger](../backend-system/core-services/logger.md) and [Root Logger](../backend-system/core-services/root-logger.md) core service documentation. For health checks, see [Root Health](../backend-system/core-services/root-health.md).
+
+:::
+
This article briefly describes the observability options that are available to a
Backstage integrator.
diff --git a/docs/plugins/plugin-development.md b/docs/plugins/plugin-development.md
index 5ba1de4a7d..89318e7c5f 100644
--- a/docs/plugins/plugin-development.md
+++ b/docs/plugins/plugin-development.md
@@ -4,6 +4,12 @@ title: Plugin Development
description: Documentation on Plugin Development
---
+:::caution Legacy Documentation
+
+This page covers plugin development patterns for the **old frontend system**, including `createPlugin`, `createRoutableExtension`, and `RouteRef` from `@backstage/core-plugin-api`. For the new frontend system equivalents, see [Building Frontend Plugins](../frontend-system/building-plugins/01-index.md) and [Routes](../frontend-system/architecture/36-routes.md).
+
+:::
+
Backstage plugins provide features to a Backstage App.
Each plugin is treated as a self-contained web app and can include almost any
diff --git a/docs/plugins/plugin-directory-audit.md b/docs/plugins/plugin-directory-audit.md
index 9e82cb6de5..b080463c82 100644
--- a/docs/plugins/plugin-directory-audit.md
+++ b/docs/plugins/plugin-directory-audit.md
@@ -4,6 +4,12 @@ title: Plugin Directory Audit
description: Details about the process for auditing plugins in the directory
---
+:::caution Legacy Documentation
+
+This section is part of the legacy plugins documentation. The audit process described here is still current.
+
+:::
+
## Audit Process
We have a simple process in place to audit the plugins in the Plugin Directory:
diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md
index bd89fc6c36..a764ea64d2 100644
--- a/docs/plugins/proxying.md
+++ b/docs/plugins/proxying.md
@@ -4,6 +4,12 @@ title: Proxying
description: Documentation on Proxying
---
+:::caution Legacy Documentation
+
+This section is part of the legacy plugins documentation. The proxy configuration and usage described here applies to both the old and new backend systems. For creating backend plugins and modules, see [Building Backend Plugins and Modules](../backend-system/building-plugins-and-modules/01-index.md).
+
+:::
+
This page describes how to configure and use the built-in HTTP proxy functionality in your Backstage backend.
## Overview
diff --git a/docs/plugins/publish-private.md b/docs/plugins/publish-private.md
deleted file mode 100644
index d04449d465..0000000000
--- a/docs/plugins/publish-private.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-id: publish-private
-title: Publish private
-description: Documentation on How to Publish private
----
-
-## TODO
diff --git a/docs/plugins/structure-of-a-plugin.md b/docs/plugins/structure-of-a-plugin.md
index 7cfaca4561..3843359857 100644
--- a/docs/plugins/structure-of-a-plugin.md
+++ b/docs/plugins/structure-of-a-plugin.md
@@ -4,6 +4,12 @@ title: Structure of a Plugin
description: Details about structure of a plugin
---
+:::caution Legacy Documentation
+
+This page describes the structure of a plugin for the **old frontend system**. For the new frontend system, see [Building Frontend Plugins](../frontend-system/building-plugins/01-index.md). The general folder structure is similar, but the plugin wiring in `plugin.ts` differs significantly.
+
+:::
+
Nice, you have a new plugin! We'll soon see how we can develop it into doing
great things. But first off, let's look at what we get out of the box.
diff --git a/docs/plugins/testing.md b/docs/plugins/testing.md
index 1297d516f3..94015700fe 100644
--- a/docs/plugins/testing.md
+++ b/docs/plugins/testing.md
@@ -4,6 +4,12 @@ title: Testing with Jest
description: Documentation on How to do unit testing with Jest
---
+:::caution Legacy Documentation
+
+This section is part of the legacy plugins documentation. The general testing principles described here still apply, but for system-specific testing guides, see [Testing Frontend Plugins](../frontend-system/building-plugins/02-testing.md) and [Testing Backend Plugins and Modules](../backend-system/building-plugins-and-modules/02-testing.md).
+
+:::
+
:::note Note
You may want to consider migrating to Jest 30, to do this, you can follow this guide: [Migrating to Jest 30](../tutorials/jest30-migration.md)
diff --git a/docs/publishing.md b/docs/publishing.md
index 15b613f800..9c2ecb0157 100644
--- a/docs/publishing.md
+++ b/docs/publishing.md
@@ -47,12 +47,12 @@ Additional steps for the main line release
- Check for mentions of "major" & "breaking" and if they are expected in the current release
- Verify the version we are shipping is correct
- Create Release Notes
- - There exists a [release notes template](./.release-notes-template.md) for creating the release notes. It can already be created after the last main line release to keep track of major changes during the month
+ - There exists a release notes template (`.release-notes-template.md`) for creating the release notes. It can already be created after the last main line release to keep track of major changes during the month
- The content is picked by relevancy showcasing the work of the community during the month of the release
- Mention newly added packages or features
- Mention any security fixes
- Create Release Notes PR
- - Add the release note file as [`/docs/releases/vx.y.0.md`](./releases)
+ - Add the release note file as [`/docs/releases/vx.y.0.md`](https://backstage.io/docs/releases/)
- Finally copy the content, without the metadata header, into the description of the [`Version Packages` Pull Request](https://github.com/backstage/backstage/pulls?q=is%3Aopen+is%3Apr+in%3Atitle+%22Version+Packages)
Once the release has been published edit the newly created release in the [GitHub repository](https://github.com/backstage/backstage/releases) and replace the text content with the release notes.
diff --git a/docs/references/glossary.md b/docs/references/glossary.md
index ad9e2bbee5..31b7115efa 100644
--- a/docs/references/glossary.md
+++ b/docs/references/glossary.md
@@ -186,7 +186,7 @@ This standard is a key component of [OpenID Connect](#openid-connect-aka-oidc).
Classification of an [entity](#entity) in the Backstage Software Catalog, for example _service_, _database_, or _team_. An element of the [kind|namespace|name triplet](#kind-namespace-name-triplet) that is an important concept for uniqueness.
-## Kind|namespace|name triplet
+## Kind|namespace|name triplet {#kind-namespace-name-triplet}
The primary reference for [Software Catalog](#software-catalog) entities. It is human-readable and should be unique across your Backstage instance.
@@ -379,7 +379,7 @@ Existing search technology that [Backstage Search](#search) can take advantage o
The Software Catalog is a core feature of Backstage. See [Backstage Software Catalog](https://backstage.io/docs/next/features/software-catalog/) for an overview, the life of an entity in the catalog, how to configure the catalog, its architecture and high-level design, how to configure and customize it, and its API. The overview describes how the catalog works, how to add components to it, how to find software in it, and more.
-## Software Templates (aka Scaffolder)
+## Software Templates (aka Scaffolder) {#software-templates}
1. A "skeleton" software project created and managed in the Backstage Software Templates tool.
diff --git a/docs/releases/v1.3.0.md b/docs/releases/v1.3.0.md
index 57004158cf..b87851ca83 100644
--- a/docs/releases/v1.3.0.md
+++ b/docs/releases/v1.3.0.md
@@ -28,7 +28,7 @@ Several new [entity providers](https://backstage.io/docs/features/software-catal
- `AzureDevOpsEntityProvider` as replacement for `AzureDevOpsDiscoveryProcessor`. PR [#11604](https://github.com/backstage/backstage/pull/11604) contributed by [@goenning](https://github.com/goenning)
- `GitlabDiscoveryEntityProvider` as replacement for `GitLabDiscoveryProcessor`. PR [#11886](https://github.com/backstage/backstage/pull/11886) contributed by [@ivangonzalezacuna](https://github.com/ivangonzalezacuna)
-- `BitbucketCloudEntityProvider` as a replacement for `BitbucketDiscoveryProcessor` (for Bitbucket Cloud only). PR [#11345](https://github.com/backstage/backstage/pull/11345) contributed by [@pjungermann](https://github.com/pjungermann)
+- `BitbucketCloudEntityProvider` as a replacement for `BitbucketDiscoveryProcessor` (for Bitbucket Cloud only). PR [#11345](https://github.com/backstage/backstage/pull/11345) contributed by [@pjungermann](https://github.com/pjungermann)
### New plugin: Vault
diff --git a/docs/releases/v1.49.0-changelog.md b/docs/releases/v1.49.0-changelog.md
new file mode 100644
index 0000000000..f3c2bef2e7
--- /dev/null
+++ b/docs/releases/v1.49.0-changelog.md
@@ -0,0 +1,3330 @@
+# Release v1.49.0
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.49.0](https://backstage.github.io/upgrade-helper/?to=1.49.0)
+
+## @backstage/integration@2.0.0
+
+### Major Changes
+
+- 527cf88: **BREAKING** Removed deprecated Azure DevOps, Bitbucket, Gerrit and GitHub code:
+
+ - For Azure DevOps, the long deprecated `token` string and `credential` object have been removed from the `config.d.ts`. Use the `credentials` array object instead.
+ - For Bitbucket, the long deprecated `bitbucket` object has been removed from the `config.d.ts`. Use the `bitbucketCloud` or `bitbucketServer` objects instead.
+ - For Gerrit, the `parseGerritGitilesUrl` function has been removed, use `parseGitilesUrlRef` instead. The `buildGerritGitilesArchiveUrl` function has also been removed, use `buildGerritGitilesArchiveUrlFromLocation` instead.
+ - For GitHub, the `getGitHubRequestOptions` function has been removed.
+
+### Minor Changes
+
+- d933f62: Add configurable throttling and retry mechanism for GitLab integration.
+
+### Patch Changes
+
+- 1513a0b: Fixed a security vulnerability where path traversal sequences in SCM URLs could be used to access unintended API endpoints using server-side integration credentials.
+- 993a598: Fixed Azure integration config schema visibility annotations to use per-field `@visibility secret` instead of `@deepVisibility secret` on parent objects, so that non-secret fields like `clientId`, `tenantId`, `organizations`, and `managedIdentityClientId` are no longer incorrectly marked as secret.
+
+## @backstage/plugin-catalog@2.0.0
+
+### Major Changes
+
+- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible.
+
+ **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`.
+
+ **Migration:**
+
+ Simply delete the obsolete `variant` and `gridSizes` props, e.g:
+
+ ```diff
+ -
+ +
+ ```
+
+ ```diff
+ -
+ +
+ ```
+
+### Minor Changes
+
+- 4d58894: Added support for group alias IDs and configurable content ordering on the entity page. Groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `defaultContentOrder` option (default `title`) controls how content items within each group are sorted, with support for both a page-level default and per-group overrides.
+
+- c548a0f: Migrated entity table cards (`HasComponentsCard`, `HasResourcesCard`, `HasSubcomponentsCard`, `HasSubdomainsCard`, `HasSystemsCard`, `DependsOnComponentsCard`, `DependsOnResourcesCard`, `DependencyOfComponentsCard`) to use BUI when no legacy props are passed. The old `variant`, `columns`, and `tableOptions` props are deprecated but still supported — passing any of them triggers the legacy MUI-based rendering. The new `columnConfig` prop accepts `EntityColumnConfig[]` for BUI-based rendering.
+
+ `RelatedEntitiesCard` is deprecated — use `EntityRelationCard` from `@backstage/plugin-catalog-react/alpha` instead.
+
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+
+### Patch Changes
+
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- 07ba746: Fixed entity page tab groups not respecting the ordering from the `groups` configuration.
+- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern.
+- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/ui@0.13.0
+ - @backstage/core-compat-api@0.5.9
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-search-react@1.11.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/plugin-scaffolder-common@2.0.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/integration-react@1.2.16
+ - @backstage/plugin-techdocs-react@1.3.9
+
+## @backstage/plugin-scaffolder-common@2.0.0
+
+### Major Changes
+
+- 527cf88: **BREAKING** Removed deprecated `bitbucket` integration from being registered in the `ScaffolderClient`. Use the `bitbucketCloud` or `bitbucketServer` integrations instead.
+
+### Minor Changes
+
+- f598909: **BREAKING PRODUCERS**: Made `retry`, `listTasks`, `listTemplatingExtensions`, `dryRun`, and `autocomplete` required methods on the `ScaffolderApi` interface. Implementations of `ScaffolderApi` must now provide these methods.
+- e8736ea: Added an optional `secrets` field to `TemplateEntityV1beta3` for configuring secrets validation. The schema for validating secrets is defined under `secrets.schema` as a JSON Schema object.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/backend-app-api@1.6.0
+
+### Minor Changes
+
+- 545557a: Registration errors should be forwarded as BackendStartupResult
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+
+## @backstage/backend-defaults@0.16.0
+
+### Minor Changes
+
+- 42960f1: The actions registry invoke endpoint now accepts direct user credentials in addition to service principals, enabling CLI and other direct user clients to invoke actions.
+- 0e7d8f9: The scheduler service now uses the metrics service to create metrics, providing plugin-scoped attribution.
+- 527cf88: **BREAKING** Removed deprecated `BitbucketUrlReader`. Use the `BitbucketCloudUrlReader` or the `BitbucketServerUrlReader` instead.
+
+### Patch Changes
+
+- cc8348e: Added permissions integration to the actions registry. Actions registered with a `visibilityPermission` field are now checked against the permissions framework when listing and invoking. Denied actions are filtered from list results, and invoking a denied action returns a `404 Not Found` as if the action does not exist. Permissions are automatically registered with the `PermissionsRegistryService` so they appear in the permission policy system.
+- dee4283: Added `pluginId` field to `ActionsServiceAction` type, populated from the registering plugin's metadata.
+- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available.
+- 638e6c7: chore(deps): bump `yauzl` from 3.2.0 to 3.2.1
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500.
+- d933f62: Add configurable throttling and retry mechanism for GitLab integration.
+- b99158a: Fixed `yarn backstage-cli config:check --strict --config app-config.yaml` config validation error by adding
+ an optional `default` type discriminator to PostgreSQL connection configuration,
+ allowing `config:check` to properly validate `default` connection configurations.
+- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
+- 5fcbef2: Updated dependency `express-rate-limit` to `^8.0.0`.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/cli-node@0.3.0
+ - @backstage/integration@2.0.0
+ - @backstage/config-loader@1.10.9
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/plugin-auth-node@0.6.14
+ - @backstage/backend-app-api@1.6.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/backend-dynamic-feature-service@0.8.0
+
+### Minor Changes
+
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+
+### Patch Changes
+
+- 4074a22: Fixed `resolvePackagePath` resolution for bundled dynamic plugins. When a plugin bundles its own copy of `@backstage/backend-plugin-api` inside `node_modules`, the `CommonJSModuleLoader` fallback now correctly resolves the plugin's `package.json` by name. Previously the fallback only applied when the resolution originated from the host application; it now also applies when originating from a bundled dependency, which is the case for plugins produced by the `backstage-cli package bundle` command.
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-defaults@0.16.0
+ - @backstage/cli-node@0.3.0
+ - @backstage/plugin-catalog-backend@3.5.0
+ - @backstage/cli-common@0.2.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/plugin-events-backend@0.6.0
+ - @backstage/config-loader@1.10.9
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/plugin-auth-node@0.6.14
+ - @backstage/backend-openapi-utils@0.6.7
+ - @backstage/plugin-app-node@0.1.43
+ - @backstage/plugin-events-node@0.4.20
+ - @backstage/plugin-search-backend-node@1.4.2
+
+## @backstage/backend-plugin-api@1.8.0
+
+### Minor Changes
+
+- cc8348e: Added optional `visibilityPermission` field to `ActionsRegistryActionOptions`, allowing actions to declare a `BasicPermission` that controls visibility and access.
+
+ ```typescript
+ import { createPermission } from '@backstage/plugin-permission-common';
+
+ const myPermission = createPermission({
+ name: 'myPlugin.myAction.use',
+ attributes: {},
+ });
+
+ actionsRegistry.register({
+ name: 'my-action',
+ title: 'My Action',
+ description: 'An action that requires permission',
+ visibilityPermission: myPermission,
+ schema: {
+ input: z => z.object({ name: z.string() }),
+ output: z => z.object({ ok: z.boolean() }),
+ },
+ action: async ({ input }) => {
+ return { output: { ok: true } };
+ },
+ });
+ ```
+
+ Actions without a `visibilityPermission` field continue to work as before.
+
+- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available.
+
+### Patch Changes
+
+- dee4283: Added `pluginId` field to `ActionsServiceAction` type, populated from the registering plugin's metadata.
+- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/catalog-client@1.14.0
+
+### Minor Changes
+
+- 972f686: Added support for the `query` field in `getEntitiesByRefs` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+- 5d95e8e: Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error.
+- 56c908e: Added support for the `query` field in `getEntityFacets` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+- 51e23eb: Added predicate-based entity filtering via POST /entities/by-query endpoint.
+
+ Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and (partially) `$contains` operators for expressive entity queries. Integrated into the existing `queryEntities` flow with full cursor-based pagination, permission enforcement, and `totalItems` support.
+
+ The catalog client's `queryEntities()` method automatically routes to the POST endpoint when a `query` predicate is provided.
+
+### Patch Changes
+
+- d2494d6: Minor update to catalog client docs
+- Updated dependencies
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/cli@0.36.0
+
+### Minor Changes
+
+- d0f4cd2: Added new `auth` command group for authenticating the CLI with Backstage instances using OAuth 2.0 with a pre-registered client metadata document. Commands include `login`, `logout`, `list`, `show`, `print-token`, and `select` for managing multiple authenticated instances.
+
+- d806b0c: The CLI now automatically discovers CLI modules from the project root's `dependencies` and `devDependencies`. Any installed package with the `cli-module` Backstage role will be loaded automatically without needing to be hardcoded in the CLI itself.
+
+ If no CLI modules are found in the project dependencies, the CLI falls back to the built-in set of modules and prints a deprecation warning. This fallback will be removed in a future release. To prepare for this, add `@backstage/cli-defaults` as a `devDependency` in your root `package.json`:
+
+ ```json
+ {
+ "devDependencies": {
+ "@backstage/cli-defaults": "backstage:^"
+ }
+ }
+ ```
+
+ If you are not using the Backstage Yarn plugin, run the following instead:
+
+ ```sh
+ yarn workspace root add --dev @backstage/cli-defaults
+ ```
+
+ For fine-grained control you can instead install individual CLI modules:
+
+ ```json
+ {
+ "devDependencies": {
+ "@backstage/cli-module-auth": "backstage:^",
+ "@backstage/cli-module-build": "backstage:^",
+ "@backstage/cli-module-config": "backstage:^",
+ "@backstage/cli-module-github": "backstage:^",
+ "@backstage/cli-module-info": "backstage:^",
+ "@backstage/cli-module-lint": "backstage:^",
+ "@backstage/cli-module-maintenance": "backstage:^",
+ "@backstage/cli-module-migrate": "backstage:^",
+ "@backstage/cli-module-new": "backstage:^",
+ "@backstage/cli-module-test-jest": "backstage:^",
+ "@backstage/cli-module-translations": "backstage:^"
+ }
+ }
+ ```
+
+- 08d9770: **BREAKING**: The CLI templates for frontend plugins have been renamed:
+
+ - `new-frontend-plugin` → `frontend-plugin`
+ - `new-frontend-plugin-module` → `frontend-plugin-module`
+ - `frontend-plugin` (legacy) → `legacy-frontend-plugin`
+
+ To smooth out this breaking change, the CLI now auto-detects which frontend system your app uses based on the dependencies in `packages/app/package.json`. When using the default templates (no explicit `templates` configuration):
+
+ - Apps using `@backstage/frontend-defaults` will see the new frontend system templates (`frontend-plugin`, `frontend-plugin-module`)
+ - Apps using `@backstage/app-defaults` will see the legacy template (displayed as `frontend-plugin`)
+
+ This means existing projects that haven't migrated to the new frontend system will continue to create legacy plugins by default, while new projects will get the new frontend system templates. If you have explicit template configuration in your `package.json`, it will be used as-is without any auto-detection.
+
+- b36a60d: **BREAKING**: The `migrate package-exports` command has been removed. Use `repo fix` instead.
+
+### Patch Changes
+
+- edf2b77: Added a new `cli-module` template for creating CLI module packages.
+
+- 246877a: Updated dependency `bfj` to `^9.0.2`.
+
+- 0d2d0f2: Internal refactor of CLI modularization, moving individual commands to be implemented with cleye.
+
+- a4e5902: Internal refactor of the CLI command registration
+
+- bba2e49: Internal refactor to use new concurrency utilities from `@backstage/cli-node`.
+
+- 2fcba39: Internal refactor to move shared utilities into their consuming modules, reducing cross-module dependencies.
+
+- c85ac86: Internal refactor to split `loadCliConfig` into separate implementations for the build and config CLI modules, removing a cross-module dependency.
+
+- 94a885a: Added support for the new `cli-module` package role in the build system, ESLint configuration, Jest configuration, and maintenance commands.
+
+- fd50cb3: Added `translations export` and `translations import` commands for managing translation files.
+
+ The `translations export` command discovers all `TranslationRef` definitions across frontend plugin dependencies and exports their default messages as JSON files. The `translations import` command generates `TranslationResource` wiring code from translated JSON files, ready to be plugged into the app.
+
+ Both commands support a `--pattern` option for controlling the message file layout, for example `--pattern '{lang}/{id}.json'` for language-based directory grouping.
+
+- 0be3eab: Migrated CLI plugin modules to use `createCliModule` from `@backstage/cli-node`.
+
+- 61cb976: Migrated internal versioning utilities to use `@backstage/cli-node` instead of a local implementation.
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+
+- ff4a45a: Migrated remaining CLI command handlers from `commander` to `cleye` for argument parsing. Several camelCase CLI flags have been deprecated in favor of their kebab-case equivalents (e.g. `--successCache` → `--success-cache`). The old camelCase forms still work but will now log a deprecation warning. Please update any scripts or CI configurations to use the kebab-case versions.
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+
+- 825c81d: Internal refactor of CLI command modules.
+
+- ea90ab0: The built-in `yarn new` templates have been moved to `@backstage/cli-module-new`. Existing references to `@backstage/cli/templates/*` in your root `package.json` will continue to work through a backwards compatibility rewrite in the `new` command.
+
+- ebeb0d4: Updated the new frontend plugin template to use `@backstage/frontend-dev-utils` in its `dev/` entry point instead of wiring `createApp` manually. Generated plugins now get the same dev app helper setup as the built-in examples.
+
+- 971cc94: The `new` command now prompts for the plugin package name when creating plugin modules, in order to properly populate the `package.json` file.
+
+- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages.
+
+- 092b41f: Updated dependency `webpack` to `~5.105.0`.
+
+- 4a75544: Updated dependency `react-refresh` to `^0.18.0`.
+
+- a9d23c4: Properly support `package.json` `workspaces` field
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+ - @backstage/cli-defaults@0.1.0
+ - @backstage/cli-module-build@0.1.0
+ - @backstage/eslint-plugin@0.2.2
+ - @backstage/cli-module-test-jest@0.1.0
+
+## @backstage/cli-common@0.2.0
+
+### Minor Changes
+
+- 56bd494: Added `targetPaths` and `findOwnPaths` as replacements for `findPaths`, with a cleaner separation between target project paths and package-relative paths.
+
+ To migrate existing `findPaths` usage:
+
+ ```ts
+ // Before
+ import { findPaths } from '@backstage/cli-common';
+ const paths = findPaths(__dirname);
+
+ // After — for target project paths (cwd-based):
+ import { targetPaths } from '@backstage/cli-common';
+ // paths.targetDir → targetPaths.dir
+ // paths.targetRoot → targetPaths.rootDir
+ // paths.resolveTarget('src') → targetPaths.resolve('src')
+ // paths.resolveTargetRoot('yarn.lock') → targetPaths.resolveRoot('yarn.lock')
+
+ // After — for package-relative paths:
+ import { findOwnPaths } from '@backstage/cli-common';
+ const own = findOwnPaths(__dirname);
+ // paths.ownDir → own.dir
+ // paths.ownRoot → own.rootDir
+ // paths.resolveOwn('config/jest.js') → own.resolve('config/jest.js')
+ // paths.resolveOwnRoot('tsconfig.json') → own.resolveRoot('tsconfig.json')
+ ```
+
+### Patch Changes
+
+- e44b6a9: The `findOwnRootDir` utility now searches for the monorepo root by traversing up the directory tree looking for a `package.json` with `workspaces`, instead of assuming a fixed `../..` relative path. If no workspaces root is found during this traversal, `findOwnRootDir` now throws to enforce stricter validation of the repository layout.
+- 9361965: Fixed `runCheck` to ignore stdio of the spawned process, preventing unwanted output from leaking to the terminal.
+
+## @backstage/cli-defaults@0.1.0
+
+### Minor Changes
+
+- 7781ae5: Introduced `@backstage/cli-defaults`, a convenience package that bundles all standard Backstage CLI modules. Install this single package as a `devDependency` to get the full default set of CLI commands without listing each module individually.
+
+### Patch Changes
+
+- 42960f1: Added `@backstage/cli-module-actions` to the default set of CLI modules.
+- Updated dependencies
+ - @backstage/cli-module-actions@0.0.1
+ - @backstage/cli-module-new@0.1.0
+ - @backstage/cli-module-maintenance@0.1.0
+ - @backstage/cli-module-build@0.1.0
+ - @backstage/cli-module-auth@0.1.0
+ - @backstage/cli-module-config@0.1.0
+ - @backstage/cli-module-github@0.1.0
+ - @backstage/cli-module-info@0.1.0
+ - @backstage/cli-module-lint@0.1.0
+ - @backstage/cli-module-migrate@0.1.0
+ - @backstage/cli-module-test-jest@0.1.0
+ - @backstage/cli-module-translations@0.1.0
+
+## @backstage/cli-module-auth@0.1.0
+
+### Minor Changes
+
+- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+
+## @backstage/cli-module-build@0.1.0
+
+### Minor Changes
+
+- 62d0849: Added `package bundle` command to create self-contained plugin bundles for dynamic loading, to be used by the `backend-dynamic-feature-service`. Supports backend and frontend plugins, with optional `--pre-packed-dir` for batch bundling from a pre-built workspace.
+- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+ - @backstage/module-federation-common@0.1.2
+ - @backstage/config-loader@1.10.9
+
+## @backstage/cli-module-config@0.1.0
+
+### Minor Changes
+
+- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+ - @backstage/config-loader@1.10.9
+
+## @backstage/cli-module-github@0.1.0
+
+### Minor Changes
+
+- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+
+## @backstage/cli-module-info@0.1.0
+
+### Minor Changes
+
+- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+
+## @backstage/cli-module-lint@0.1.0
+
+### Minor Changes
+
+- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+
+## @backstage/cli-module-maintenance@0.1.0
+
+### Minor Changes
+
+- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
+
+### Patch Changes
+
+- f189d8f: Added auto-fill of `backstage.pluginPackage` metadata for known plugins during `repo fix`.
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+
+## @backstage/cli-module-migrate@0.1.0
+
+### Minor Changes
+
+- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+
+## @backstage/cli-module-new@0.1.0
+
+### Minor Changes
+
+- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
+
+### Patch Changes
+
+- edf2b77: Added support for the `cli-module` template role for scaffolding new CLI module packages.
+- ea90ab0: The built-in `yarn new` templates have been moved to this package from `@backstage/cli`. The default template references have been updated from `@backstage/cli/templates/*` to `@backstage/cli-module-new/templates/*`. Existing references to `@backstage/cli/templates/*` in your root `package.json` will continue to work through a backwards compatibility rewrite.
+- ebeb0d4: Updated the new frontend plugin template to use `@backstage/frontend-dev-utils` in its `dev/` entry point instead of wiring `createApp` manually. Generated plugins now get the same dev app helper setup as the built-in examples.
+- 971cc94: The `new` command now prompts for the plugin package name when creating plugin modules, in order to properly populate the `package.json` file.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+
+## @backstage/cli-module-test-jest@0.1.0
+
+### Minor Changes
+
+- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+
+## @backstage/cli-module-translations@0.1.0
+
+### Minor Changes
+
+- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+
+## @backstage/cli-node@0.3.0
+
+### Minor Changes
+
+- 7d055ef: Added `createCliModule` API and related types for building Backstage CLI plugins.
+
+### Patch Changes
+
+- 94a885a: Added a new `cli-module` package role for packages that provide CLI plugin extensions.
+- 12fa965: Added `CliAuth` class for managing CLI authentication state. This provides a class-based API with a static `create` method that resolves the currently selected (or explicitly named) auth instance, transparently refreshes expired access tokens, and exposes helpers for other CLI modules to authenticate with a Backstage backend.
+- 61cb976: Added `toString()` method to `Lockfile` for serializing lockfiles back to string format.
+- 06c2015: Added `runConcurrentTasks` and `runWorkerQueueThreads` utilities, moved from the `@backstage/cli` internal code.
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- 3c811bf: Added `hasBackstageYarnPlugin` and `SuccessCache` exports, moved from `@backstage/cli`.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- a9d23c4: Properly support `package.json` `workspaces` field
+- Updated dependencies
+ - @backstage/cli-common@0.2.0
+
+## @backstage/create-app@0.8.0
+
+### Minor Changes
+
+- a6735c3: **BREAKING**: The new frontend system is now the default template when creating a new Backstage app. The previous `--next` flag has been replaced with a `--legacy` flag that can be used to create an app using the old frontend system instead.
+
+### Patch Changes
+
+- d806b0c: The create-app templates now include `@backstage/cli-defaults` as a `devDependency`, enabling the CLI's automatic module discovery for newly created projects.
+
+- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`.
+
+ - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows.
+ - `CatalogGraphCard`: Removed `variant` prop.
+ - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`.
+ - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`.
+ - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`.
+
+ **Migration:**
+
+ ```diff
+ -
+ +
+ ```
+
+ ```diff
+ -
+ +
+ ```
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+
+- ea90ab0: Updated the `next-app` template to reference `@backstage/cli-module-new/templates/*` instead of `@backstage/cli/templates/*` for the built-in `yarn new` templates.
+
+- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages.
+
+- a9d23c4: Properly support `package.json` `workspaces` field
+
+- ebd4630: Replace deprecated `workspaces.packages` with `workspaces` in `package.json`
+
+ This change is **not** required, but you can edit your main `package.json`, to fit the more modern & more common pattern:
+
+ ```diff
+ - "workspaces": {
+ - "packages": [
+ "workspaces": [
+ "packages/*",
+ "plugins/*"
+ - ]
+ - },
+ ],
+ ```
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0
+
+## @backstage/frontend-app-api@0.16.0
+
+### Minor Changes
+
+- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createSpecializedApp`. This flag had no effect and was a no-op, so no behavioral changes are expected.
+
+### Patch Changes
+
+- d911b72: Frontend apps now respect an explicit `pluginId` on `ApiRef`s when deciding which plugin owns an API factory.
+- 0452d02: Add optional `description` field to plugin-level feature flags.
+- 5fd78ba: Removed `@backstage/core-plugin-api` leakage from the public API surface. All types such as `ApiHolder` and `ConfigApi` are now imported from `@backstage/frontend-plugin-api`.
+- dab6c46: Added the `ExtensionFactoryMiddleware` type as a public export.
+- 3f36ce1: Clarified the `IconElement` sizing contract for the new frontend system and aligned legacy system icon rendering with the new icon API.
+- 5b160f9: Added `prepareSpecializedApp` for two-phase app wiring so apps can render a bootstrap tree before full app finalization. The bootstrap phase now supports deferred `app/root.elements`, predicate-gated APIs, reusable `sessionState`, and warnings for bootstrap-visible predicates or bootstrap code that accessed APIs that only became available after finalization. Utility APIs that are materialized during bootstrap are also frozen for the lifetime of the app instance, causing deferred overrides of those APIs to be ignored and reported as app errors.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/core-app-api@1.19.6
+ - @backstage/frontend-defaults@0.5.0
+ - @backstage/filter-predicates@0.1.1
+
+## @backstage/frontend-defaults@0.5.0
+
+### Minor Changes
+
+- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createApp`. This flag had no effect and was a no-op, so no behavioral changes are expected.
+- 33de79d: **BREAKING**: Removed the deprecated `createPublicSignInApp` function. Use `createApp` from `@backstage/frontend-defaults` with `appModulePublicSignIn` from `@backstage/plugin-app/alpha` instead.
+
+### Patch Changes
+
+- 5b160f9: Updated `createApp` to use the phased `prepareSpecializedApp` flow, allowing apps to render a bootstrap tree before the full app is finalized.
+- Updated dependencies
+ - @backstage/frontend-app-api@0.16.0
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-app@0.4.1
+
+## @backstage/frontend-dev-utils@0.1.0
+
+### Minor Changes
+
+- c25532a: Added `@backstage/frontend-dev-utils`, a new package that provides a minimal helper for wiring up a development app for frontend plugins using the new frontend system. It exports a `createDevApp` function that handles creating and rendering a development app from a `dev/` entry point. The dev app automatically bypasses the sign-in page and loads the `@backstage/ui` CSS. The options interface accepts `features` together with route bindings through `bindRoutes`.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-app@0.4.1
+ - @backstage/frontend-defaults@0.5.0
+
+## @backstage/frontend-plugin-api@0.15.0
+
+### Minor Changes
+
+- 5fd78ba: Renamed `PluginOptions` to `CreateFrontendPluginOptions` and deprecated the old name. Removed `ResolvedExtensionInputs` from the main entry point; it is still available as an inline type in extension factory signatures.
+
+- 72991a5: Removed the `ResolvedExtensionInput` and `ExtensionDataRefToValue` helper types from the public API surface to reduce top-level API clutter. These types were internal plumbing that are not needed by plugin authors. If you were relying on `ResolvedExtensionInput`, use the `ResolvedExtensionInputs` type instead, which maps a full set of inputs. If you were using `ExtensionDataRefToValue`, replace it with `ExtensionDataValue` combined with inferred types from your `ExtensionDataRef`.
+
+- 9508514: **BREAKING**: Promoted `PluginWrapperApi`, `pluginWrapperApiRef`, `PluginWrapperBlueprint`, and the new `PluginWrapperDefinition` type from `@alpha` to `@public`. These are now available from the main package entry point rather than only through `/alpha`.
+
+ The `PluginWrapperApi` type now has a required `getRootWrapper()` method that returns a root wrapper component. The `pluginWrapperApiRef` ID changed from `core.plugin-wrapper.alpha` to `core.plugin-wrapper`.
+
+ The `PluginWrapperBlueprint` now accepts `PluginWrapperDefinition` as the loader return type, which supports an optional `useWrapperValue` hook that allows sharing state between wrapper instances.
+
+- 6573901: **BREAKING**: Removed the deprecated `AnyExtensionDataRef` type. Use `ExtensionDataRef` without type parameters instead.
+
+- a9440f0: **BREAKING**: Simplified the `ExtensionAttachTo` type to only support a single attachment target. The array form for attaching to multiple extension points has been removed. Also removed the deprecated `ExtensionAttachToSpec` type alias.
+
+### Patch Changes
+
+- e26e3de: The `icon` field on `AuthProviderInfo` now accepts `IconElement` in addition to `IconComponent`, letting you pass ` ` instead of `MyIcon`.
+
+- eea95b8: Deprecated `AlertApi` in favor of the new `ToastApi`.
+
+ `AlertApi` is now deprecated and will be removed in a future release. Please migrate to `ToastApi` which provides richer notification features.
+
+ **Why migrate?**
+
+ `ToastApi` offers enhanced capabilities over `AlertApi`:
+
+ - **Title and Description**: Display a prominent title with optional description text
+ - **Action Links**: Include clickable links within notifications
+ - **Status Variants**: Support for neutral, info, success, warning, and danger statuses
+ - **Per-toast Timeout**: Control auto-dismiss timing for each notification individually
+ - **Programmatic Dismiss**: Close notifications via the `close()` handle returned from `post()`
+
+ **Migration Guide**
+
+ | AlertApi | ToastApi |
+ | -------------------------------------------- | ------------------------------------------ |
+ | `message: string` | `title: ReactNode` |
+ | `severity: 'error'` | `status: 'danger'` |
+ | `severity: 'success' \| 'info' \| 'warning'` | `status: 'success' \| 'info' \| 'warning'` |
+ | `display: 'transient'` | `timeout: 5000` (or custom ms) |
+ | `display: 'permanent'` | omit `timeout` |
+ | `post()` returns `void` | `post()` returns `{ close(): void }` |
+
+ **Example Migration**
+
+ ```typescript
+ // Before (AlertApi)
+ import { alertApiRef, useApi } from '@backstage/core-plugin-api';
+
+ const alertApi = useApi(alertApiRef);
+ alertApi.post({
+ message: 'Entity saved successfully',
+ severity: 'success',
+ display: 'transient',
+ });
+
+ // After (ToastApi)
+ import { toastApiRef, useApi } from '@backstage/frontend-plugin-api';
+
+ const toastApi = useApi(toastApiRef);
+ const toast = toastApi.post({
+ title: 'Entity saved successfully',
+ status: 'success',
+ timeout: 5000,
+ });
+ // Later: toast.close() to dismiss programmatically
+ ```
+
+ **Note**: During the migration period, both APIs work simultaneously. The `ToastDisplay` component subscribes to both `AlertApi` and `ToastApi`, so existing code continues to work while you migrate incrementally.
+
+- 8a3a906: Deprecated `NavItemBlueprint`. Nav items are now automatically inferred from `PageBlueprint` extensions based on their `title` and `icon` params.
+
+- b15a685: Deprecated `withApis`, use the `withApis` export from `@backstage/core-compat-api` instead.
+
+- 0452d02: Add optional `description` field to plugin-level feature flags.
+
+- 1bec049: Fixed inconsistent `JSX.Element` type reference in the `DialogApiDialog.update` method signature.
+
+- 9c81af9: Made the `pluginId` property optional in the `FrontendFeature` type, allowing plugins published against older versions of the framework to be used without type errors.
+
+- 2c383b5: Deprecated `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory` in favor of the exports from `@backstage/plugin-app-react`.
+
+- dab6c46: Deprecated the `ExtensionFactoryMiddleware` type, which has been moved to `@backstage/frontend-app-api`.
+
+- aa29b50: Pages created with `PageBlueprint` now render the plugin header by default in the new frontend system.
+
+- 3f36ce1: Clarified the `IconElement` sizing contract for the new frontend system and aligned legacy system icon rendering with the new icon API.
+
+- cc459f7: Added a builder form for `createApiRef` in the new frontend system and deprecated the direct `createApiRef({ ... })` call in favor of `createApiRef().with({ ... })`. The builder form now also preserves literal API ref IDs in the resulting `ApiRef` type.
+
+ The `createApiRef().with({ ... })` form can also use an explicit `pluginId` to declare API ownership without encoding the plugin ID into the API ref ID, while keeping that metadata internal to runtime handling.
+
+- 5b160f9: Added support for `if` predicates on `createFrontendPlugin` and `createFrontendModule`, applying shared conditions to every extension in the feature. Plugin and extension overrides can now also replace or remove existing `if` predicates.
+
+- d0206c4: Removed the deprecated `defaultPath` migration helper from `PageBlueprint` params.
+
+- edb872c: Renamed the `PageTab` type to `PageLayoutTab`. The old `PageTab` name is now a deprecated type alias.
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+
+- 7e743f4: Introduced a new `ToastApi` for displaying rich toast notifications in the new frontend system.
+
+ The new `ToastApi` provides enhanced notification capabilities compared to the existing `AlertApi`:
+
+ - **Title and Description**: Toasts support both a title and an optional description
+ - **Custom Timeouts**: Each toast can specify its own timeout duration
+ - **Links**: Toasts can include action links
+ - **Status Variants**: Support for neutral, info, success, warning, and danger statuses
+ - **Programmatic Dismiss**: Toasts can be dismissed programmatically using the `close()` handle returned from `post()`
+
+ **Usage:**
+
+ ```typescript
+ import { toastApiRef, useApi } from '@backstage/frontend-plugin-api';
+
+ const toastApi = useApi(toastApiRef);
+
+ // Full-featured toast
+ toastApi.post({
+ title: 'Entity saved',
+ description: 'Your changes have been saved successfully.',
+ status: 'success',
+ timeout: 5000,
+ links: [{ label: 'View entity', href: '/catalog/entity' }],
+ });
+
+ // Programmatic dismiss
+ const { close } = toastApi.post({ title: 'Uploading...', status: 'info' });
+ // Later...
+ close();
+ ```
+
+ The `ToastDisplay` component subscribes to both `ToastApi` and `AlertApi`, providing a migration path where both systems work side by side until `AlertApi` is fully deprecated.
+
+- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context.
+
+- Updated dependencies
+ - @backstage/filter-predicates@0.1.1
+
+## @backstage/repo-tools@0.17.0
+
+### Minor Changes
+
+- 0fbcf23: Added support for OpenAPI 3.1 to all `schema openapi` commands. The commands now auto-detect the OpenAPI version from the spec file and use the appropriate generator, supporting both OpenAPI 3.0.x and 3.1.x specifications.
+
+### Patch Changes
+
+- 426edbe: Fixed `generate-catalog-info` command failing with "too many arguments" when invoked by lint-staged via the pre-commit hook.
+- d5779e5: Updated the CLI report parser to support cleye-style help output, and strip ANSI escape codes from captured output.
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+- 2a51546: Fixed prettier existence checks in OpenAPI commands to use `fs.pathExists` instead of checking the resolved path string, which was always truthy.
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- 18a946c: Updated `@microsoft/api-extractor` to `7.57.3` and added tests for `getTsDocConfig`
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/cli-node@0.3.0
+ - @backstage/cli-common@0.2.0
+ - @backstage/config-loader@1.10.9
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/ui@0.13.0
+
+### Minor Changes
+
+- 768f09d: **BREAKING**: Simplified the neutral background prop API for container components. The explicit `neutral-1`, `neutral-2`, `neutral-3`, and `neutral-auto` values have been removed from `ProviderBg`. They are replaced by a single `'neutral'` value that always auto-increments from the parent context, making it impossible to skip or pin to an explicit neutral level.
+
+ **Migration:**
+
+ Replace any explicit `bg="neutral-1"`, `bg="neutral-2"`, `bg="neutral-3"`, or `bg="neutral-auto"` props with `bg="neutral"`. To achieve a specific neutral level in stories or tests, use nested containers — each additional `bg="neutral"` wrapper increments by one level.
+
+ ```tsx
+ // Before
+ ...
+
+ // After
+
+ ...
+
+ ```
+
+ **Affected components:** Box, Flex, Grid, Card, Accordion, Popover, Tooltip, Dialog, Menu
+
+- b42fcdc: **BREAKING**: Removed `--bui-bg-popover` CSS token. Popover, Tooltip, Menu, and Dialog now use `--bui-bg-app` for their outer shell and `Box bg="neutral-1"` for content areas, providing better theme consistency and eliminating a redundant token.
+
+ **Migration:**
+
+ Replace any usage of `--bui-bg-popover` with `--bui-bg-neutral-1` (for content surfaces) or `--bui-bg-app` (for outer shells):
+
+ ```diff
+ - background: var(--bui-bg-popover);
+ + background: var(--bui-bg-neutral-1);
+ ```
+
+ **Affected components:** Popover, Tooltip, Menu, Dialog
+
+- bd3a76e: **BREAKING**: Data attributes rendered by components are now always lowercase. This affects CSS selectors targeting camelCase data attributes.
+
+ **Migration:**
+
+ Update any custom CSS selectors that target camelCase data attributes to use lowercase instead:
+
+ ```diff
+ - [data-startCollapsed='true'] { ... }
+ + [data-startcollapsed='true'] { ... }
+ ```
+
+ **Affected components:** SearchField
+
+- 95702ab: **BREAKING**: Removed deprecated types `ComponentDefinition`, `ClassNamesMap`, `DataAttributeValues`, and `DataAttributesMap` from the public API. These were internal styling infrastructure types that have been replaced by the `defineComponent` system.
+
+ **Migration:**
+
+ Remove any direct usage of these types. Component definitions now use `defineComponent()` and their shapes are not part of the public API contract.
+
+ ```diff
+ - import type { ComponentDefinition, ClassNamesMap } from '@backstage/ui';
+ ```
+
+ If you were reading `definition.dataAttributes`, use `definition.propDefs` instead — props with `dataAttribute: true` in `propDefs` are the equivalent.
+
+- 42f8c9b: **BREAKING**: Centralized client-side routing in `BUIProvider`. Components like Link, ButtonLink, Tabs, Menu, TagGroup, and Table now require a `BUIProvider` rendered inside a React Router context for client-side navigation to work.
+
+ **Migration:**
+
+ This change requires updating `@backstage/plugin-app` and `@backstage/core-app-api` alongside `@backstage/ui`. If you only upgrade `@backstage/ui`, BUI components will fall back to full-page navigation.
+
+ If you cannot upgrade all packages together, or if you have a custom app shell, add a `BUIProvider` inside your Router:
+
+ ```diff
+ + import { BUIProvider } from '@backstage/ui';
+
+
+ +
+
+ +
+
+ ```
+
+ **Affected components:** Link, ButtonLink, Tabs, Menu, TagGroup, Table
+
+- 17d6398: **BREAKING**: Renamed internal CSS classes to match the `Header` component name.
+
+ **Migration:**: If you are targeting these classes directly in your styles, update the following:
+
+ - `bui-HeaderPage` → `bui-Header`
+ - `bui-HeaderPageContent` → `bui-HeaderContent`
+ - `bui-HeaderPageBreadcrumbs` → `bui-HeaderBreadcrumbs`
+ - `bui-HeaderPageTabsWrapper` → `bui-HeaderTabsWrapper`
+ - `bui-HeaderPageControls` → `bui-HeaderControls`
+
+ **Affected components:**: Header
+
+- 9d5f3ba: Removed redundant `selected` and `indeterminate` props from the `Checkbox` component. Use the `isSelected` and `isIndeterminate` props instead, which are the standard React Aria props and already handle both the checkbox behaviour and the corresponding CSS data attributes.
+
+ **Migration:**
+ Replace any usage of the `selected` and `indeterminate` props on `Checkbox` with the `isSelected` and `isIndeterminate` props. Note that the checked state and related CSS data attributes (such as `data-selected` and `data-indeterminate`) are now driven by React Aria, so any custom logic that previously inspected or set these via the old props should instead rely on the React Aria-managed state and attributes exposed through the new props.
+
+ **Affected components:** Checkbox
+
+### Patch Changes
+
+- 04d9d8d: Added `List` and `ListRow` components. These provide a standalone, accessible list of interactive rows built on top of React Aria's `GridList` and `GridListItem` primitives. Rows support icons, descriptions, actions, menus, and single or multiple selection modes.
+
+ **Affected components:** List, ListRow
+
+- a1f4bee: Made Accordion a `bg` provider so nested components like Button auto-increment their background level. Updated `useDefinition` to resolve `bg` `propDef` defaults for provider components.
+
+- db92751: Added interactive support to the `Card` component. Pass `onPress` to make the entire card surface pressable, or `href` to make it navigate to a URL. A transparent overlay handles the interaction while nested buttons and links remain independently clickable.
+
+- 12d8afe: Added analytics capabilities to the component library. Components with navigation behavior (Link, ButtonLink, Tab, MenuItem, Tag, Row) now fire analytics events on click when a `BUIProvider` is present.
+
+ New exports: `BUIProvider`, `useAnalytics`, `getNodeText`, and associated types (`AnalyticsTracker`, `UseAnalyticsFn`, `BUIProviderProps`, `AnalyticsEventAttributes`).
+
+ Components with analytics support now accept a `noTrack` prop to suppress event firing.
+
+ **Affected components:** Link, ButtonLink, Tab, MenuItem, Tag, Row
+
+- b838cc9: Added a `loading` prop and `data-loading` data attribute to `TableRoot`, allowing consumers to distinguish between stale data and initial loading states. Both `stale` and `loading` set `aria-busy` on the table.
+
+ Affected components: TableRoot
+
+- 690786f: Improved the `Table` component loading state to show a skeleton UI with visible headers instead of plain "Loading..." text. The table now renders its full structure during loading, with animated skeleton rows in place of data. The loading state includes proper accessibility support with `aria-busy` on the table and screen reader announcements.
+
+ Affected components: Table
+
+- 58224d3: Fixed neutral-1 hover & pressed state in light mode.
+
+- 95702ab: Migrated all components from `useStyles` to `useDefinition` hook. Exported `OwnProps` types for each component, enabling better type composition for consumers.
+
+ **Affected components:** Avatar, Checkbox, Container, Dialog, FieldError, FieldLabel, Flex, FullPage, Grid, HeaderPage, Link, Menu, PasswordField, PluginHeader, Popover, RadioGroup, SearchField, Select, Skeleton, Switch, Table, TablePagination, Tabs, TagGroup, Text, TextField, ToggleButton, ToggleButtonGroup, Tooltip, VisuallyHidden
+
+- 430d5ed: Fixed interactive cards so that CardBody can scroll when the card has a constrained height. Previously, the overlay element blocked scroll events.
+
+ **Affected components:** Card
+
+- 4c2c350: Removed the `transition` on `Container` padding to prevent an unwanted animation when the viewport is resized.
+
+ Affected components: Container
+
+- e0b7eb0: Fixed --bui-fg-success token in light mode to be more accessible.
+
+- ad7c883: Deprecated the `HeaderPage` component name in favor of `Header`. The old `HeaderPage`, `HeaderPageProps`, `HeaderPageOwnProps`, `HeaderPageBreadcrumb`, and `HeaderPageDefinition` exports are still available as deprecated aliases.
+
+- 0ebde15: Added documentation for the table cell wrapper requirement to TSDoc comments for `Cell`, `CellText`, `CellProfile`, `ColumnConfig`, and `RowRenderFn`.
+
+- d9d2dd6: Added `SearchAutocomplete` and `SearchAutocompleteItem` components for building accessible search-with-results patterns. Built on React Aria's Autocomplete with keyboard navigation and screen reader support. Designed for async/external search results with a configurable popover width.
+
+ **Affected components:** SearchAutocomplete, SearchAutocompleteItem
+
+- a6b84e1: Made Checkbox `children` optional and added a dev warning when neither a visible label, `aria-label`, nor `aria-labelledby` is provided. The label wrapper div is no longer rendered when there are no children, removing the unnecessary gap.
+
+ **Affected components:** Checkbox
+
+- b99f6d5: Fixed `Dialog` content overflowing when no `height` prop is set. The dialog now grows with its content and scrolls when content exceeds the viewport height.
+
+ **Affected components**: Dialog
+
+- 2f581de: Fixed focus ring styles to use React Aria's `[data-focus-visible]` data attribute instead of the native CSS `:focus-visible` pseudo-class. This ensures keyboard focus rings render reliably when focus is managed programmatically by React Aria (e.g. inside a GridList, Menu, or Select).
+
+ **Affected components:** Accordion, Button, ButtonIcon, ButtonLink, Card, List, Menu, Select, ToggleButtonGroup
+
+- 17d6398: Fixed incorrect bottom spacing caused by `Container` using `padding-bottom` for its default bottom spacing. Changed to `margin-bottom` and prevented it from applying when `Container` is used as the `Header` root element.
+
+ **Affected components:** Container, Header
+
+- 2e5c651: Fixed `PluginHeader` to avoid triggering `ResizeObserver loop completed with undelivered notifications` warnings when used in layouts that react to the header height, such as pages that use `FullPage`.
+
+ **Affected components:** PluginHeader
+
+- d4fa5b4: Fixed tab `matchStrategy` matching to ignore query parameters and hash fragments in tab `href` values. Previously, tabs with query params in their `href` (e.g., `/page?group=foo`) would never show as active since matching compared the full `href` string against `location.pathname` which never includes query params.
+
+ **Affected components:** Tabs, PluginHeader
+
+- bc42b60: Fixed Table component to use current `--bui-bg-neutral-1` tokens instead of the removed `--bui-bg-tint` tokens, restoring row hover, selected, pressed, and disabled background colors.
+
+- 9314ff5: Fixed a bug in the `useTable` hook where the loading skeleton was never shown for `complete` mode when using `getData`. The initial data state was an empty array instead of `undefined`, causing the `Table` component to skip the loading state.
+
+- f42f4cc: Fixed Table column headers overflowing and wrapping when there is not enough space. Headers now truncate with ellipsis instead.
+
+ **Affected components:** Table
+
+- 1f9682b: Fixed Table row hover, selected, pressed, and disabled background states to use the correct neutral token level based on the container background.
+
+ **Affected components:** Table
+
+- fbd5c5a: Fixed Table rows showing a pointer cursor when not interactive. Rows now only show `cursor: pointer` when they have an `href`, are selectable, or are pressable.
+
+ **Affected components:** Table
+
+- 612c217: Fixed `Table` rows with external `href` values to open in a new tab by automatically applying `target="_blank"` and `rel="noopener noreferrer"`.
+
+ **Affected components**: Table
+
+- 545129a: Updated Table selection checkboxes to use `aria-label` instead of empty fragment children, improving accessibility and removing the unnecessary label gap in the selection cells.
+
+ **Affected components:** Table
+
+- 36987db: Fixed handling of the `style` prop on `Button`, `ButtonIcon`, and `ButtonLink` so that it is now correctly forwarded to the underlying element instead of being silently dropped.
+
+ **Affected components:** Button, ButtonIcon, ButtonLink
+
+- 95702ab: Fixed Link variant default from `'body'` to `'body-medium'` to match actual CSS selectors. The previous default did not correspond to a valid variant value.
+
+ **Affected components:** Link
+
+- 9027b10: Fixed scroll overflow in Menu and Select popover content when constrained by viewport height.
+
+ **Affected components:** Menu, Select
+
+- 7960d54: Added support for native HTML div attributes on the `Flex`, `Grid`, and `Grid.Item` components.
+
+ **Affected components:** Flex, Grid, Grid.Item
+
+- 0559408: Added `virtualized` prop to `Table` component for virtualized rendering of large datasets. Accepts `true` for default row height, `{ rowHeight: number }` for fixed height, or `{ estimatedRowHeight: number }` for variable height rows.
+
+- 8909359: Fixed focus-visible outline styles for Menu and Select components.
+
+ **Affected components:** Menu, Select
+
+- 12d8afe: Fixed MenuItem `onAction` prop ordering so user-provided `onAction` handlers are chained rather than silently overwritten.
+
+- aa29b50: Pages created with `PageBlueprint` now render the plugin header by default in the new frontend system.
+
+- bb66b86: The `Select` trigger now automatically adapts its background colour based on the parent background context.
+
+ **Affected components:** Select
+
+- 4105a78: Merged the internal `PluginHeaderToolbar` component into `PluginHeader`, removing the separate component and its associated types (`PluginHeaderToolbarOwnProps`, `PluginHeaderToolbarProps`) and definition (`PluginHeaderToolbarDefinition`). This is an internal refactor with no changes to the public API of `PluginHeader`.
+
+ **Affected components:** PluginHeader
+
+- 9599697: Updated dependency `globals` to `^17.0.0`.
+
+- 0f462f8: Improved type safety in `useDefinition` by centralizing prop resolution and strengthening the `BgPropsConstraint` to require that `bg` provider components declare `children` as a required prop in their OwnProps type.
+
+- 8909359: Added proper cursor styles for RadioGroup items.
+
+ **Affected components:** RadioGroup
+
+- fcaac3b: Fixed `Card` interactive cards not firing the `onPress` handler when clicking the card surface.
+
+ **Affected components**: Card
+
+- b303857: Fixed `isRequired` prop not being passed to the underlying React Aria field components in TextField, SearchField, and PasswordField. Previously, `isRequired` was consumed locally for the secondary label text but never forwarded, which meant the input elements lacked `aria-required="true"` and React Aria's built-in required validation was not activated.
+
+ **Affected components:** TextField, SearchField, PasswordField
+
+- 934ac03: `SearchField` and `TextField` now automatically adapt their background color based on the parent bg context, stepping up one neutral level (e.g. neutral-1 → neutral-2) when placed on a neutral background. `TextField` also gains a focus ring using the `--bui-ring` token.
+
+ **Affected components:** SearchField, TextField
+
+- cd3cb0f: Improved `useBreakpoint` performance by sharing a single set of `matchMedia` listeners across all component instances instead of creating independent listeners per hook call.
+
+- 36987db: Extended `AlertProps`, `ContainerProps`, `DialogBodyProps`, and `FieldLabelProps` with native div element props to allow passing attributes like `aria-*` and `data-*`.
+
+ **Affected components:** Alert, Container, DialogBody, FieldLabel
+
+## @backstage/plugin-catalog-backend@3.5.0
+
+### Minor Changes
+
+- a6b2819: Added `query-catalog-entities` action to the catalog backend actions registry. Supports predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+
+- 972f686: Added support for predicate-based filtering on the `/entities/by-refs` endpoint via the `query` field in the request body. Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+
+- 5d95e8e: Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error.
+
+- 56c908e: Added support for predicate-based filtering on the `/entity-facets` endpoint via a new `POST` method. Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+
+- bf71677: Added opentelemetry metrics for SCM events:
+
+ - `catalog.events.scm.messages` with attribute `eventType`: Counter for the number of SCM events actually received by the catalog backend. The `eventType` is currently either `location` or `repository`.
+
+- 51e23eb: Added predicate-based entity filtering via POST /entities/by-query endpoint.
+
+ Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and (partially) `$contains` operators for expressive entity queries. Integrated into the existing `queryEntities` flow with full cursor-based pagination, permission enforcement, and `totalItems` support.
+
+ The catalog client's `queryEntities()` method automatically routes to the POST endpoint when a `query` predicate is provided.
+
+### Patch Changes
+
+- a91bd1b: Improved catalog entity deletion so parent invalidation and deferred relation restitch scheduling are coordinated more safely.
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+
+- 7416e8b: Moved stitch queue concerns out of `refresh_state` and `final_entities` into a dedicated `stitch_queue` table with `entity_ref` as the primary key. The `stitch_ticket` is used for optimistic concurrency control. When a stitch completes successfully and the ticket hasn't changed, the corresponding row is deleted from the queue. The migration handles existing data and is fully reversible.
+
+- fbf382f: Minor internal optimisation
+
+- 1ee5b28: Migrates existing catalog metrics to use the alpha MetricsService. This release is a 1:1 migration with no breaking changes.
+
+- 72747b4: Deprecated two processors as they have been moved to the Community Plugins repo with their own backend modules:
+
+ - `AnnotateScmSlugEntityProcessor`: Use `@backstage-community/plugin-catalog-backend-module-annotate-scm-slug` instead
+ - `CodeOwnersProcessor`: Use `@backstage-community/plugin-catalog-backend-module-codeowners` instead
+
+- 3644b72: Make the `search` foreign key catalog migration non-blocking on large tables by using batch deletes and PostgreSQL `NOT VALID`/`VALIDATE` to reduce lock duration
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+
+- 3181973: Changed the `search` table foreign key to point to `final_entities` instead of `refresh_state`
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/catalog-model@1.7.7
+ - @backstage/backend-openapi-utils@0.6.7
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-catalog-backend-module-github@0.13.0
+
+### Minor Changes
+
+- b11c2cd: The default user transformer now prefers organization verified domain emails over the user's public GitHub email when populating the user entity profile. It also strips plus-addressed routing tags that GitHub adds to these emails.
+
+ If you want to retain the old behavior, you can do so with a custom user transformer using the `githubOrgEntityProviderTransformsExtensionPoint`:
+
+ ```ts
+ import { createBackendModule } from '@backstage/backend-plugin-api';
+ import { githubOrgEntityProviderTransformsExtensionPoint } from '@backstage/plugin-catalog-backend-module-github-org';
+ import { defaultUserTransformer } from '@backstage/plugin-catalog-backend-module-github';
+
+ export default createBackendModule({
+ pluginId: 'catalog',
+ moduleId: 'github-org-custom-transforms',
+ register(env) {
+ env.registerInit({
+ deps: {
+ transforms: githubOrgEntityProviderTransformsExtensionPoint,
+ },
+ async init({ transforms }) {
+ transforms.setUserTransformer(async (item, ctx) => {
+ const entity = await defaultUserTransformer(item, ctx);
+ if (entity && item.email) {
+ entity.spec.profile!.email = item.email;
+ }
+ return entity;
+ });
+ },
+ });
+ },
+ });
+ ```
+
+### Patch Changes
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+
+- 106d1b2: Added a `defaultUserTransformer.useVerifiedEmails` config option for the `githubOrg` provider. When set to `true`, the default user transformer prefers organization verified domain emails over the user's public GitHub email. Defaults to `false`, which uses only the public GitHub email.
+
+ This option has no effect when a custom user transformer is set via the `githubOrgEntityProviderTransformsExtensionPoint`.
+
+ ```yaml
+ catalog:
+ providers:
+ githubOrg:
+ production:
+ githubUrl: https://github.com
+ orgs:
+ - my-org
+ defaultUserTransformer:
+ useVerifiedEmails: true
+ ```
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-catalog-graph@0.6.0
+
+### Minor Changes
+
+- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`.
+
+ - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows.
+ - `CatalogGraphCard`: Removed `variant` prop.
+ - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`.
+ - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`.
+ - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`.
+
+ **Migration:**
+
+ ```diff
+ -
+ +
+ ```
+
+ ```diff
+ -
+ +
+ ```
+
+### Patch Changes
+
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/ui@0.13.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-catalog-node@2.1.0
+
+### Minor Changes
+
+- bf71677: Added the ability for SCM events subscribers to mark the fact that they have taken actions based on events, which produces output metrics:
+
+ - `catalog.events.scm.actions` with attribute `action`: Counter for the number of actions actually taken by catalog internals or other subscribers, based on SCM events. The `action` is currently either `create`, `delete`, `refresh`, or `move`.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-test-utils@1.11.1
+ - @backstage/catalog-client@1.14.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-catalog-react@2.1.0
+
+### Minor Changes
+
+- c548a0f: Added `EntityDataTable`, `EntityRelationCard`, `entityDataTableColumns`, `entityColumnPresets`, and related types as alpha exports. These replace `EntityTable` and `RelatedEntitiesCard` (from `@backstage/plugin-catalog`) respectively, providing a unified BUI-based pattern for entity table cards.
+- 4d58894: Added `aliases` and `contentOrder` fields to `EntityContentGroupDefinition`, allowing groups to declare alias IDs and control the sort order of their content items.
+- d14b6e0: Exported `useEntityRefLink` hook that returns a function for generating entity page URLs from entity references.
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- c6080eb: Added `EntityInfoCard` component to `@backstage/plugin-catalog-react` as a BUI-based card wrapper for entity page cards.
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/ui@0.13.0
+ - @backstage/core-compat-api@0.5.9
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/frontend-test-utils@0.5.1
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/catalog-model@1.7.7
+ - @backstage/integration-react@1.2.16
+
+## @backstage/plugin-devtools-react@0.2.0
+
+### Minor Changes
+
+- f4a1edd: Removed the deprecated `DevToolsContentBlueprint` from `@backstage/plugin-devtools-react`. DevTools pages in the new frontend system now use `SubPageBlueprint` tabs instead, and the catalog unprocessed entities alpha extension now attaches to DevTools as a subpage.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/frontend-plugin-api@0.15.0
+
+## @backstage/plugin-events-backend@0.6.0
+
+### Minor Changes
+
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-openapi-utils@0.6.7
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-notifications-backend-module-slack@0.4.0
+
+### Minor Changes
+
+- cd62d78: **BREAKING**: Only send direct messages to user entity recipients. Notifications sent to non-user entities no longer send Slack direct messages to resolved users.
+- 749ba60: Add an extension for custom Slack message layouts
+
+### Patch Changes
+
+- e7c6c32: The Slack notification processor now uses the `MetricsService` to create metrics, providing plugin-scoped attribution. `{message}` unit has also been added.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-notifications-node@0.2.24
+
+## @backstage/plugin-org@0.7.0
+
+### Minor Changes
+
+- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`.
+
+ - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows.
+ - `CatalogGraphCard`: Removed `variant` prop.
+ - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`.
+ - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`.
+ - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`.
+
+ **Migration:**
+
+ ```diff
+ -
+ +
+ ```
+
+ ```diff
+ -
+ +
+ ```
+
+- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible.
+
+ **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`.
+
+ **Migration:**
+
+ Simply delete the obsolete `variant` and `gridSizes` props, e.g:
+
+ ```diff
+ -
+ +
+ ```
+
+ ```diff
+ -
+ +
+ ```
+
+### Patch Changes
+
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/ui@0.13.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-scaffolder@1.36.0
+
+### Minor Changes
+
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+
+### Patch Changes
+
+- e27bd4e: Removed check for deprecated `bitbucket` integration from `repoPickerValidation` function used by the `RepoUrlPicker`, it now validates the `bitbucketServer` and `bitbucketCloud` integrations instead.
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- bd5b842: Added a new `ui:autoSelect` option to the EntityPicker field that controls whether an entity is automatically selected when the field loses focus. When set to `false`, the field will remain empty if the user closes it without explicitly selecting an entity, preventing unintentional selections. Defaults to `true` for backward compatibility.
+- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/plugin-scaffolder-react@1.20.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/plugin-scaffolder-common@2.0.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/integration-react@1.2.16
+ - @backstage/plugin-techdocs-react@1.3.9
+
+## @backstage/plugin-scaffolder-backend@3.2.0
+
+### Minor Changes
+
+- c9b11eb: Added a new `list-scaffolder-tasks` action that allows querying scaffolder tasks with optional ownership filtering and pagination support
+- 1b42218: Adds a new `get-scaffolder-task-logs` action to `@backstage/plugin-scaffolder-backend` that retrieves log events for a given scaffolder task, with optional support for retrieving only new events after a given event ID.
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+- 7695dd2: Added a new `list-scaffolder-actions` action that returns all installed scaffolder actions with their schemas and examples
+- e8736ea: Added secrets schema validation for task creation, retry, and dry-run endpoints. When a template defines `spec.secrets.schema`, the API validates provided secrets against the schema and returns a `400` error if validation fails.
+
+### Patch Changes
+
+- e27bd4e: Removed `@backstage/plugin-scaffolder-backend-module-bitbucket` from `package.json` as the package itself has been deprecated and the code deleted.
+- 4f5ed06: Fixed a security vulnerability where server-configured environment secrets were exposed through the scaffolder dry-run endpoint.
+- 30ff981: Fixed a security vulnerability where secrets could bypass log redaction when transformed through Nunjucks filters in scaffolder templates.
+- 4e39e63: Removed unused dependencies
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- ccc20cf: create scaffolder MCP action to dry run a provided scaffolder template
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/plugin-scaffolder-common@2.0.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/catalog-model@1.7.7
+ - @backstage/backend-openapi-utils@0.6.7
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-scaffolder-node@0.13.0
+
+### Minor Changes
+
+- e27bd4e: **BREAKING** Removed deprecated `bitbucket` integration from being used in the `parseRepoUrl` function. It will use the `bitbucketCloud` or `bitbucketServer` integrations instead.
+
+### Patch Changes
+
+- cd0ecc5: Added `removeFiles` helper function for staging file removals in Git.
+- f598909: Added `scaffolderServiceRef` and `ScaffolderService` interface for backend plugins that need to interact with the scaffolder API using `BackstageCredentials` instead of raw tokens.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-test-utils@1.11.1
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-scaffolder-common@2.0.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-scaffolder-react@1.20.0
+
+### Minor Changes
+
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- 470f72d: The `LogViewer` component from `@backstage/core-components` now supports downloading logs if a callback is passed to `onDownloadLogs`
+
+### Patch Changes
+
+- 004b5c1: Added back `formFieldsApiRef` and `ScaffolderFormFieldsApi` as alpha exports.
+- bd31ddd: Updated dependency `flatted` to `3.3.4`.
+- f598909: Added `scaffolderApiMock` test utility, exported from `@backstage/plugin-scaffolder-react/testUtils`.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/frontend-test-utils@0.5.1
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/plugin-scaffolder-common@2.0.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-search@1.7.0
+
+### Minor Changes
+
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+
+### Patch Changes
+
+- d5eb954: Fixes the search component not registering the first search on navigate to the search page.
+- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern.
+- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/ui@0.13.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-search-react@1.11.0
+
+## @backstage/plugin-search-backend@2.1.0
+
+### Minor Changes
+
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/backend-openapi-utils@0.6.7
+ - @backstage/plugin-search-backend-node@1.4.2
+
+## @backstage/plugin-search-react@1.11.0
+
+### Minor Changes
+
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+
+### Patch Changes
+
+- d5eb954: Fixes the search component not registering the first search on navigate to the search page.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+
+## @backstage/app-defaults@1.7.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/core-app-api@1.19.6
+ - @backstage/plugin-permission-react@0.4.41
+
+## @backstage/backend-openapi-utils@0.6.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+
+## @backstage/backend-test-utils@1.11.1
+
+### Patch Changes
+
+- dee4283: Added `pluginId` field to `ActionsServiceAction` type, populated from the registering plugin's metadata.
+- 164711a: Added `cancelTask` to `MockSchedulerService` and mock scheduler service factory.
+- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- 1ee5b28: Adds a new metrics service mock to be leveraged in tests
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-defaults@0.16.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-auth-node@0.6.14
+ - @backstage/backend-app-api@1.6.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/catalog-model@1.7.7
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+
+## @backstage/cli-module-actions@0.0.1
+
+### Patch Changes
+
+- 42960f1: Added `actions` CLI module for listing and executing actions from the distributed actions registry. Includes `actions list`, `actions execute`, and `actions sources` commands for managing plugin sources.
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+
+## @backstage/codemods@0.1.55
+
+### Patch Changes
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0
+
+## @backstage/config-loader@1.10.9
+
+### Patch Changes
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0
+
+## @backstage/core-app-api@1.19.6
+
+### Patch Changes
+
+- 12d8afe: Added `BUIProvider` from `@backstage/ui` to the app shell provider tree, enabling BUI components to fire analytics events through the Backstage analytics system.
+- 59752a2: Deprecated `AlertApiForwarder` in favor of the new `ToastApi`. The `AlertApiForwarder` now emits a console warning on first use, guiding developers to migrate to `ToastApi` from `@backstage/frontend-plugin-api`.
+- 0452d02: Add optional `description` field to plugin-level feature flags.
+- 42f8c9b: Added `BUIProvider` inside the legacy app router to enable client-side routing for all BUI components.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/ui@0.13.0
+ - @backstage/core-plugin-api@1.12.4
+
+## @backstage/core-compat-api@0.5.9
+
+### Patch Changes
+
+- b15a685: Added `withApis`, which is a Higher-Order Component for providing APIs as props to a component via `useApiHolder`.
+- 8e09233: Added a missing dependency on `@backstage/filter-predicates` to `@backstage/core-compat-api`. This fixes package metadata for consumers that use compatibility helpers relying on filter predicate support.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-app-react@0.2.1
+ - @backstage/filter-predicates@0.1.1
+
+## @backstage/core-components@0.18.8
+
+### Patch Changes
+
+- e26e3de: The login request dialog now handles auth provider icons passed as `IconElement` in addition to `IconComponent`.
+- 8e09233: Fixed the shared `Progress` component to provide an accessible name for its loading indicator by default.
+- 8b1a847: Fixed Table component layout when both `filters` and `title` props are used together. The filter controls now use a dedicated CSS class (`filterControls`) instead of incorrectly reusing the root container class.
+- 3f36ce1: Clarified the `IconElement` sizing contract for the new frontend system and aligned legacy system icon rendering with the new icon API.
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- 470f72d: The `LogViewer` component from `@backstage/core-components` now supports downloading logs if a callback is passed to `onDownloadLogs`
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+
+## @backstage/core-plugin-api@1.12.4
+
+### Patch Changes
+
+- d911b72: Updated `createApiRef` to preserve the direct config call without deprecation warnings while staying compatible with the new frontend API ref typing.
+- 59752a2: Deprecated `AlertApi`, `AlertMessage`, and `alertApiRef` in favor of the new `ToastApi` from `@backstage/frontend-plugin-api`.
+- 0452d02: Add optional `description` field to plugin-level feature flags.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0
+
+## @backstage/dev-utils@1.1.21
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/ui@0.13.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/core-app-api@1.19.6
+ - @backstage/catalog-model@1.7.7
+ - @backstage/app-defaults@1.7.6
+ - @backstage/integration-react@1.2.16
+
+## @backstage/eslint-plugin@0.2.2
+
+### Patch Changes
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+
+## @backstage/filter-predicates@0.1.1
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+
+## @backstage/frontend-dynamic-feature-loader@0.1.10
+
+### Patch Changes
+
+- e8ba654: Update the README of the `frontend-dynamic-feature-loader` package to mention the new `backstage-cli package bundle` command.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/module-federation-common@0.1.2
+
+## @backstage/frontend-test-utils@0.5.1
+
+### Patch Changes
+
+- b56f573: Deprecated standalone mock API exports in favor of the `mockApis` namespace. This includes the mock classes (`MockAlertApi`, `MockAnalyticsApi`, `MockConfigApi`, `MockErrorApi`, `MockFetchApi`, `MockFeatureFlagsApi`, `MockPermissionApi`, `MockStorageApi`, `MockTranslationApi`), their option types (`MockErrorApiOptions`, `MockFeatureFlagsApiOptions`), and the `ErrorWithContext` type. `MockFetchApiOptions` is kept as a non-deprecated export. Use the `mockApis` namespace instead, for example `mockApis.alert()` or `mockApis.alert.mock()`.
+- 479282f: Fixed type inference of `TestApiPair` when using tuple syntax by wrapping `MockWithApiFactory` in `NoInfer`.
+- 8e09233: Added a missing dependency on `@backstage/filter-predicates` to `@backstage/frontend-test-utils`. This fixes package metadata for consumers using the frontend test app helpers with predicate-based behavior.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- 909c742: Switched `MockTranslationApi` and related test utility imports from `@backstage/core-plugin-api/alpha` to the stable `@backstage/frontend-plugin-api` export. The `TranslationApi` type in the API report is now sourced from a single package. This has no effect on runtime behavior.
+- Updated dependencies
+ - @backstage/frontend-app-api@0.16.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/core-app-api@1.19.6
+ - @backstage/plugin-app@0.4.1
+ - @backstage/plugin-app-react@0.2.1
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/test-utils@1.7.16
+
+## @backstage/integration-react@1.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/integration@2.0.0
+
+## @backstage/module-federation-common@0.1.2
+
+### Patch Changes
+
+- 0cb5646: Fixed the `@mui/material/styles` shared dependency key by removing a trailing slash that caused module resolution failures with MUI package exports.
+
+## @techdocs/cli@1.10.6
+
+### Patch Changes
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages.
+- Updated dependencies
+ - @backstage/backend-defaults@0.16.0
+ - @backstage/cli-common@0.2.0
+ - @backstage/plugin-techdocs-node@1.14.4
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/test-utils@1.7.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-app-api@1.19.6
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-api-docs@0.13.5
+
+### Patch Changes
+
+- 9c9d425: Fixed invisible text in parameter input fields when using dark mode in OpenAPI definition pages
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- 30e08df: Added default entity content groups for the API docs entity content tabs. The API definition tab defaults to the `documentation` group and the APIs tab defaults to the `development` group.
+- c548a0f: Migrated entity table cards (`ConsumedApisCard`, `ProvidedApisCard`, `HasApisCard`, `ConsumingComponentsCard`, `ProvidingComponentsCard`) to use BUI when no legacy props are passed. The old `variant`, `columns`, and `tableOptions` props are deprecated but still supported — passing any of them triggers the legacy MUI-based rendering. The new `columnConfig` prop accepts `EntityColumnConfig[]` for BUI-based rendering.
+- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern.
+- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces.
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- ca277ef: Updated dependency `graphiql` to `3.9.0` to address security vulnerability in `markdown-it` package.
+ Updated dependency `@graphiql/react` to `0.29.0` to match the version used by `graphiql`.
+ Moved dependency `graphql-config` to `devDependencies` as it is needed only for types.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/ui@0.13.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-catalog@2.0.0
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-app@0.4.1
+
+### Patch Changes
+
+- 5f3f5d2: `NavContentBlueprint` nav item collections now keep previously collected `rest()` results in sync when additional items are taken later in the same render, making it easier to place items across multiple sidebar sections.
+
+- aa29b50: Pages created with `PageBlueprint` now render the plugin header by default in the new frontend system.
+
+- c0ab376: The app nav now falls back to `plugin.icon` for navigation items that don't have an explicit icon set.
+
+- 12d8afe: Added `BUIProvider` from `@backstage/ui` to the app root, enabling BUI components to fire analytics events through the Backstage analytics system.
+
+- 5fec07d: Updated the default app root to better support phased app preparation by allowing the app layout to be absent during bootstrap, routing bootstrap failures through the app root boundary, and avoiding installation of a guest identity in protected apps that do not provide a sign-in page.
+
+- 9508514: Updated the default `PluginWrapperApi` implementation to support the new `useWrapperValue` hook and root wrapper. The root wrapper is now rendered in the app root to manage shared hook state across plugin wrapper instances.
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+
+- 42f8c9b: Moved `BUIProvider` inside the app router to enable automatic client-side routing for all BUI components.
+
+- 909c742: Switched translation API imports (`translationApiRef`, `appLanguageApiRef`) from the alpha `@backstage/core-plugin-api/alpha` path to the stable `@backstage/frontend-plugin-api` export. This has no effect on runtime behavior.
+
+- 7e743f4: Introduced a new `ToastApi` for displaying rich toast notifications in the new frontend system.
+
+ The new `ToastApi` provides enhanced notification capabilities compared to the existing `AlertApi`:
+
+ - **Title and Description**: Toasts support both a title and an optional description
+ - **Custom Timeouts**: Each toast can specify its own timeout duration
+ - **Links**: Toasts can include action links
+ - **Status Variants**: Support for neutral, info, success, warning, and danger statuses
+ - **Programmatic Dismiss**: Toasts can be dismissed programmatically using the `close()` handle returned from `post()`
+
+ **Usage:**
+
+ ```typescript
+ import { toastApiRef, useApi } from '@backstage/frontend-plugin-api';
+
+ const toastApi = useApi(toastApiRef);
+
+ // Full-featured toast
+ toastApi.post({
+ title: 'Entity saved',
+ description: 'Your changes have been saved successfully.',
+ status: 'success',
+ timeout: 5000,
+ links: [{ label: 'View entity', href: '/catalog/entity' }],
+ });
+
+ // Programmatic dismiss
+ const { close } = toastApi.post({ title: 'Uploading...', status: 'info' });
+ // Later...
+ close();
+ ```
+
+ The `ToastDisplay` component subscribes to both `ToastApi` and `AlertApi`, providing a migration path where both systems work side by side until `AlertApi` is fully deprecated.
+
+- Updated dependencies
+ - @backstage/ui@0.13.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-app-react@0.2.1
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/integration-react@1.2.16
+
+## @backstage/plugin-app-backend@0.5.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/config-loader@1.10.9
+ - @backstage/plugin-auth-node@0.6.14
+ - @backstage/plugin-app-node@0.1.43
+
+## @backstage/plugin-app-node@0.1.43
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/config-loader@1.10.9
+
+## @backstage/plugin-app-react@0.2.1
+
+### Patch Changes
+
+- 5f3f5d2: `NavContentBlueprint` nav item collections now keep previously collected `rest()` results in sync when additional items are taken later in the same render, making it easier to place items across multiple sidebar sections.
+- 2c383b5: Added `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory`, migrated from `@backstage/frontend-plugin-api`.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/frontend-plugin-api@0.15.0
+
+## @backstage/plugin-app-visualizer@0.2.1
+
+### Patch Changes
+
+- c25532a: Switched dev entry point to use `createDevApp` from `@backstage/frontend-dev-utils`.
+- Updated dependencies
+ - @backstage/ui@0.13.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+
+## @backstage/plugin-auth@0.1.6
+
+### Patch Changes
+
+- 06294aa: Migrated the ConsentPage UI from Material-UI and `@backstage/core-components` to `@backstage/ui`.
+- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern.
+- Updated dependencies
+ - @backstage/ui@0.13.0
+ - @backstage/frontend-plugin-api@0.15.0
+
+## @backstage/plugin-auth-backend@0.27.2
+
+### Patch Changes
+
+- 1ccad86: Added `who-am-i` action to the auth backend actions registry. Returns the catalog entity and user info for the currently authenticated user.
+- d0f4cd2: Added optional client metadata document endpoint at `/.well-known/oauth-client/cli.json` relative to the auth backend base URL for CLI authentication. Enabled when `auth.experimentalClientIdMetadataDocuments.enabled` is set to `true`.
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+- e9b6e97: Fixed a security vulnerability where the CIMD metadata fetch could follow HTTP redirects to internal hosts, bypassing SSRF protections.
+- 0f9d673: Improved redirect URI validation in the experimental OIDC provider to match against normalized URLs rather than raw strings.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- 634eded: Fixed a foreign key constraint violation when issuing refresh tokens for CIMD clients, and
+ prevented a failed refresh token issuance from failing the entire token exchange.
+ Fixed AWS ALB auth provider incorrectly returning HTTP 500 instead of 401 for JWT validation failures,
+ which caused retry loops and memory pressure under load.
+- 619be54: Update migrations to be reversible
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.13
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-auth0-provider@0.3.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.14
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- 634eded: Fixed a foreign key constraint violation when issuing refresh tokens for CIMD clients, and
+ prevented a failed refresh token issuance from failing the entire token exchange.
+ Fixed AWS ALB auth provider incorrectly returning HTTP 500 instead of 401 for JWT validation failures,
+ which caused retry loops and memory pressure under load.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-backend@0.27.2
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.18
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.13
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.13
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.13
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.13
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-github-provider@0.5.1
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-gitlab-provider@0.4.1
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-google-provider@0.3.13
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-guest-provider@0.2.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.13
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.13
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.18
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-oidc-provider@0.4.14
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-backend@0.27.2
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-okta-provider@0.2.13
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.13
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-openshift-provider@0.1.5
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-auth-node@0.6.14
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-auth-react@0.1.25
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+
+## @backstage/plugin-bitbucket-cloud-common@0.3.8
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-catalog-backend-module-aws@0.4.21
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-defaults@0.16.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-catalog-backend-module-azure@0.3.15
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+
+## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/backend-openapi-utils@0.6.7
+
+## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.9
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-bitbucket-cloud-common@0.3.8
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.9
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-catalog-backend-module-gcp@0.3.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-catalog-backend-module-gerrit@0.3.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+
+## @backstage/plugin-catalog-backend-module-gitea@0.1.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+
+## @backstage/plugin-catalog-backend-module-github-org@0.3.20
+
+### Patch Changes
+
+- 106d1b2: Added a `defaultUserTransformer.useVerifiedEmails` config option for the `githubOrg` provider. When set to `true`, the default user transformer prefers organization verified domain emails over the user's public GitHub email. Defaults to `false`, which uses only the public GitHub email.
+
+ This option has no effect when a custom user transformer is set via the `githubOrgEntityProviderTransformsExtensionPoint`.
+
+ ```yaml
+ catalog:
+ providers:
+ githubOrg:
+ production:
+ githubUrl: https://github.com
+ orgs:
+ - my-org
+ defaultUserTransformer:
+ useVerifiedEmails: true
+ ```
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-backend-module-github@0.13.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.8.1
+
+### Patch Changes
+
+- d933f62: Add configurable throttling and retry mechanism for GitLab integration.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-defaults@0.16.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.19
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-backend-module-gitlab@0.8.1
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.10
+
+### Patch Changes
+
+- 5f1e7b8: Migrated metrics from direct `@opentelemetry/api` usage to the alpha `MetricsService`, providing plugin-scoped metric attribution. The `@opentelemetry/api` dependency has been removed.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-defaults@0.16.0
+ - @backstage/plugin-catalog-backend@3.5.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-catalog-backend-module-ldap@0.12.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-catalog-backend-module-logs@0.1.20
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-backend@3.5.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-catalog-backend-module-msgraph@0.9.1
+
+### Patch Changes
+
+- 97eaecf: Fixed scheduler task remaining stuck in running state after pod termination by propagating AbortSignal into MicrosoftGraphOrgEntityProvider.read()
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-catalog-backend-module-openapi@0.2.20
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-catalog-backend-module-puppetdb@0.2.20
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/plugin-scaffolder-common@2.0.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-catalog-backend-module-unprocessed@0.6.9
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-catalog-import@0.13.11
+
+### Patch Changes
+
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/catalog-model@1.7.7
+ - @backstage/integration-react@1.2.16
+
+## @backstage/plugin-catalog-unprocessed-entities@0.2.27
+
+### Patch Changes
+
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern.
+- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces.
+- f4a1edd: Removed the deprecated `DevToolsContentBlueprint` from `@backstage/plugin-devtools-react`. DevTools pages in the new frontend system now use `SubPageBlueprint` tabs instead, and the catalog unprocessed entities alpha extension now attaches to DevTools as a subpage.
+- Updated dependencies
+ - @backstage/ui@0.13.0
+ - @backstage/core-compat-api@0.5.9
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+
+## @backstage/plugin-config-schema@0.1.78
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+
+## @backstage/plugin-devtools@0.1.37
+
+### Patch Changes
+
+- afabb37: Fixed URL encoding of task IDs for the trigger feature (tasks that contained a "/" in their ID were not triggered)
+- f80195e: Added `cancelScheduledTask` to the DevTools API and a cancel button to the scheduled tasks UI.
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern.
+- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces.
+- f4a1edd: Removed the deprecated `DevToolsContentBlueprint` from `@backstage/plugin-devtools-react`. DevTools pages in the new frontend system now use `SubPageBlueprint` tabs instead, and the catalog unprocessed entities alpha extension now attaches to DevTools as a subpage.
+- Updated dependencies
+ - @backstage/ui@0.13.0
+ - @backstage/core-compat-api@0.5.9
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-devtools-common@0.1.23
+ - @backstage/plugin-permission-react@0.4.41
+
+## @backstage/plugin-devtools-backend@0.5.15
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/cli-common@0.2.0
+ - @backstage/plugin-devtools-common@0.1.23
+ - @backstage/config-loader@1.10.9
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+
+## @backstage/plugin-devtools-common@0.1.23
+
+### Patch Changes
+
+- f80195e: Added `cancelScheduledTask` to the DevTools API and a cancel button to the scheduled tasks UI.
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-events-backend-module-aws-sqs@0.4.20
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-events-backend-module-azure@0.2.29
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.29
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-events-backend-module-bitbucket-server@0.1.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-events-backend-module-gerrit@0.2.29
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-events-backend-module-github@0.4.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-events-backend-module-gitlab@0.3.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-events-backend-module-google-pubsub@0.2.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-events-backend-module-kafka@0.3.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-events-backend-test-utils@0.1.53
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-events-node@0.4.20
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+
+## @backstage/plugin-gateway-backend@1.1.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+
+## @backstage/plugin-home@0.9.3
+
+### Patch Changes
+
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/core-compat-api@0.5.9
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/core-app-api@1.19.6
+ - @backstage/plugin-home-react@0.1.36
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-home-react@0.1.36
+
+### Patch Changes
+
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- Updated dependencies
+ - @backstage/core-compat-api@0.5.9
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+
+## @backstage/plugin-kubernetes@0.12.17
+
+### Patch Changes
+
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/plugin-kubernetes-react@0.5.17
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-kubernetes-backend@0.21.2
+
+### Patch Changes
+
+- 6b6b5de: Added `endpointType` config option to the GKE cluster locator, allowing use of DNS-based control plane endpoints instead of public IP endpoints. Set `endpointType: 'dns'` to use GKE DNS endpoints (e.g. `gke-..gke.goog`) which provide proper TLS certificates and IAM-based access control.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-kubernetes-node@0.4.2
+
+## @backstage/plugin-kubernetes-cluster@0.0.35
+
+### Patch Changes
+
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/plugin-kubernetes-react@0.5.17
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-kubernetes-node@0.4.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-kubernetes-react@0.5.17
+
+### Patch Changes
+
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-mcp-actions-backend@0.1.10
+
+### Patch Changes
+
+- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500.
+
+- dee4283: Added `mcpActions.name` and `mcpActions.description` config options to customize the MCP server identity. Namespaced tool names now use dot separator to align with the MCP spec convention.
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+
+- c74b697: Added support for splitting MCP actions into multiple servers via `mcpActions.servers` configuration. Each server gets its own endpoint at `/api/mcp-actions/v1/{key}` with actions scoped using include/exclude filter rules. Tool names are now namespaced with the plugin ID by default, configurable via `mcpActions.namespacedToolNames`. When `mcpActions.servers` is not configured, the plugin continues to serve a single server at `/api/mcp-actions/v1`.
+
+- dc81af1: Adds two new metrics to track MCP server operations and sessions.
+
+ - `mcp.server.operation.duration`: The duration taken to process an individual MCP operation
+ - `mcp.server.session.duration`: The duration of the MCP session from the perspective of the server
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/plugin-catalog-node@2.1.0
+
+## @backstage/plugin-mui-to-bui@0.2.5
+
+### Patch Changes
+
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- ad7c883: Updated the MUI to BUI theme converter page to use the renamed `Header` component from `@backstage/ui`.
+- Updated dependencies
+ - @backstage/ui@0.13.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/frontend-plugin-api@0.15.0
+
+## @backstage/plugin-notifications@0.5.15
+
+### Patch Changes
+
+- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern.
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- Updated dependencies
+ - @backstage/ui@0.13.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-signals-react@0.0.20
+
+## @backstage/plugin-notifications-backend@0.6.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-notifications-node@0.2.24
+ - @backstage/plugin-signals-node@0.1.29
+
+## @backstage/plugin-notifications-backend-module-email@0.3.19
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-notifications-node@0.2.24
+
+## @backstage/plugin-notifications-node@0.2.24
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-signals-node@0.1.29
+
+## @backstage/plugin-org-react@0.1.48
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-permission-backend@0.7.10
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-permission-common@0.9.7
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+
+## @backstage/plugin-permission-node@0.10.11
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-auth-node@0.6.14
+
+## @backstage/plugin-permission-react@0.4.41
+
+### Patch Changes
+
+- 5fec07d: Permission checks made in the same tick are now batched into a single call to the permission backend.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-proxy-backend@0.6.11
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-proxy-node@0.1.13
+
+## @backstage/plugin-proxy-node@0.1.13
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+
+## @backstage/plugin-scaffolder-backend-module-azure@0.2.19
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.4
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.8
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.19
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.19
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.21
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-defaults@0.16.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-gcp@0.2.19
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.19
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-gitea@0.2.19
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-github@0.9.7
+
+### Patch Changes
+
+- a761a48: Added optional `returnWorkflowRunDetails` input to `github:actions:dispatch` action. When true, exposes `workflowRunId`, `workflowRunUrl`, and `workflowRunHtmlUrl` as outputs using the GitHub API `return_run_details` parameter.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- b2591f6: Fixed environment `waitTime` description incorrectly asking for milliseconds instead of minutes.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.4
+
+### Patch Changes
+
+- 5730c8e: Added `maskedAndHidden` option to `gitlab:projectVariable:create` and `publish:gitlab` action to support creating GitLab project variables that are both masked and hidden. Updated gitbeaker to version 43.8.0 for proper type support.
+
+- 0c1726a: Added new `gitlab:group:access` scaffolder action to add or remove users and groups as members of GitLab groups. The action supports specifying members via `userIds` and/or `groupIds` array parameters, configurable access levels (Guest, Reporter, Developer, Maintainer, Owner), and defaults to the 'add' action when not specified.
+
+- 4b8fcf0: Added two optional inputs to the `publish:gitlab` action:
+
+ - `settings.name`: set a custom human-readable project title that differs from the repository slug.
+ - `ownerUsername`: add a specific GitLab user as project owner (access level 50) of the newly created repository. Requires a privileged token in the integration configuration.
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-notifications@0.1.20
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/plugin-notifications-node@0.2.24
+
+## @backstage/plugin-scaffolder-backend-module-rails@0.5.19
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-sentry@0.3.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.20
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.9
+
+## @backstage/plugin-scaffolder-node-test-utils@0.3.9
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-test-utils@1.11.1
+ - @backstage/plugin-scaffolder-node@0.13.0
+
+## @backstage/plugin-search-backend-module-catalog@0.3.13
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-search-backend-node@1.4.2
+
+## @backstage/plugin-search-backend-module-elasticsearch@1.8.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-search-backend-node@1.4.2
+
+## @backstage/plugin-search-backend-module-explore@0.3.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-search-backend-node@1.4.2
+
+## @backstage/plugin-search-backend-module-pg@0.5.53
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-search-backend-node@1.4.2
+
+## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-search-backend-node@1.4.2
+
+## @backstage/plugin-search-backend-module-techdocs@0.4.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/plugin-techdocs-node@1.14.4
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-search-backend-node@1.4.2
+
+## @backstage/plugin-search-backend-node@1.4.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-signals@0.0.29
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-signals-react@0.0.20
+
+## @backstage/plugin-signals-backend@0.3.13
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-events-node@0.4.20
+ - @backstage/plugin-signals-node@0.1.29
+
+## @backstage/plugin-signals-node@0.1.29
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+ - @backstage/plugin-events-node@0.4.20
+
+## @backstage/plugin-signals-react@0.0.20
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+
+## @backstage/plugin-techdocs@1.17.1
+
+### Patch Changes
+
+- 9795d30: chore(deps): bump `dompurify` from 3.3.1 to 3.3.2
+- 30e08df: Added `documentation` as the default entity content group for the TechDocs entity content tab.
+- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-search-react@1.11.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/integration@2.0.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/integration-react@1.2.16
+ - @backstage/plugin-auth-react@0.1.25
+ - @backstage/plugin-techdocs-react@1.3.9
+
+## @backstage/plugin-techdocs-addons-test-utils@2.0.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/plugin-search-react@1.11.0
+ - @backstage/core-app-api@1.19.6
+ - @backstage/plugin-techdocs@1.17.1
+ - @backstage/plugin-catalog@2.0.0
+ - @backstage/integration-react@1.2.16
+ - @backstage/test-utils@1.7.16
+ - @backstage/plugin-techdocs-react@1.3.9
+
+## @backstage/plugin-techdocs-backend@2.1.6
+
+### Patch Changes
+
+- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/plugin-techdocs-node@1.14.4
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-node@2.1.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-techdocs-module-addons-contrib@1.1.34
+
+### Patch Changes
+
+- 895563a: Avoid enabling the TechDocs LightBox addon for images wrapped in links, so image links keep working.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/integration@2.0.0
+ - @backstage/integration-react@1.2.16
+ - @backstage/plugin-techdocs-react@1.3.9
+
+## @backstage/plugin-techdocs-node@1.14.4
+
+### Patch Changes
+
+- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require.
+- e96f6d9: Removed `INHERIT` from the `ALLOWED_MKDOCS_KEYS` set to address a security concern with MkDocs configuration inheritance.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/integration@2.0.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-techdocs-react@1.3.9
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-user-settings@0.9.1
+
+### Patch Changes
+
+- e26e3de: The `ProviderSettingsItem` `icon` prop now accepts `IconElement` in addition to `IconComponent`.
+- 538c985: Updated installation documentation to use feature discovery as the default.
+- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern.
+- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces.
+- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/ui@0.13.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/core-app-api@1.19.6
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-signals-react@0.0.20
+
+## @backstage/plugin-user-settings-backend@0.4.1
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/plugin-auth-node@0.6.14
+ - @backstage/plugin-signals-node@0.1.29
+
+## example-app@0.0.33
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.36.0
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/ui@0.13.0
+ - @backstage/core-compat-api@0.5.9
+ - @backstage/frontend-app-api@0.16.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/plugin-app-visualizer@0.2.1
+ - @backstage/plugin-auth@0.1.6
+ - @backstage/core-components@0.18.8
+ - @backstage/plugin-user-settings@0.9.1
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-search-react@1.11.0
+ - @backstage/plugin-search@1.7.0
+ - @backstage/plugin-api-docs@0.13.5
+ - @backstage/plugin-devtools@0.1.37
+ - @backstage/core-app-api@1.19.6
+ - @backstage/plugin-scaffolder@1.36.0
+ - @backstage/plugin-techdocs@1.17.1
+ - @backstage/plugin-catalog@2.0.0
+ - @backstage/plugin-catalog-graph@0.6.0
+ - @backstage/plugin-catalog-import@0.13.11
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27
+ - @backstage/plugin-home@0.9.3
+ - @backstage/plugin-kubernetes@0.12.17
+ - @backstage/plugin-org@0.7.0
+ - @backstage/plugin-scaffolder-react@1.20.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34
+ - @backstage/plugin-app@0.4.1
+ - @backstage/plugin-app-react@0.2.1
+ - @backstage/plugin-notifications@0.5.15
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/frontend-defaults@0.5.0
+ - @backstage/plugin-home-react@0.1.36
+ - @backstage/plugin-kubernetes-cluster@0.0.35
+ - @backstage/catalog-model@1.7.7
+ - @backstage/app-defaults@1.7.6
+ - @backstage/integration-react@1.2.16
+ - @backstage/plugin-auth-react@0.1.25
+ - @backstage/plugin-signals@0.0.29
+ - @backstage/plugin-techdocs-react@1.3.9
+
+## app-example-plugin@0.0.33
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+
+## example-app-legacy@0.2.119
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.36.0
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/ui@0.13.0
+ - @backstage/frontend-app-api@0.16.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/plugin-user-settings@0.9.1
+ - @backstage/plugin-search-react@1.11.0
+ - @backstage/plugin-search@1.7.0
+ - @backstage/plugin-api-docs@0.13.5
+ - @backstage/plugin-devtools@0.1.37
+ - @backstage/core-app-api@1.19.6
+ - @backstage/plugin-scaffolder@1.36.0
+ - @backstage/plugin-techdocs@1.17.1
+ - @backstage/plugin-catalog@2.0.0
+ - @backstage/plugin-catalog-graph@0.6.0
+ - @backstage/plugin-catalog-import@0.13.11
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27
+ - @backstage/plugin-home@0.9.3
+ - @backstage/plugin-kubernetes@0.12.17
+ - @backstage/plugin-mui-to-bui@0.2.5
+ - @backstage/plugin-org@0.7.0
+ - @backstage/plugin-scaffolder-react@1.20.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34
+ - @backstage/plugin-notifications@0.5.15
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/plugin-home-react@0.1.36
+ - @backstage/plugin-kubernetes-cluster@0.0.35
+ - @backstage/catalog-model@1.7.7
+ - @backstage/app-defaults@1.7.6
+ - @backstage/integration-react@1.2.16
+ - @backstage/plugin-auth-react@0.1.25
+ - @backstage/plugin-signals@0.0.29
+ - @backstage/plugin-techdocs-react@1.3.9
+
+## example-backend@0.0.48
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-defaults@0.16.0
+ - @backstage/plugin-catalog-backend@3.5.0
+ - @backstage/plugin-auth-backend@0.27.2
+ - @backstage/plugin-techdocs-backend@2.1.6
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.7
+ - @backstage/plugin-scaffolder-backend@3.2.0
+ - @backstage/plugin-mcp-actions-backend@0.1.10
+ - @backstage/plugin-kubernetes-backend@0.21.2
+ - @backstage/plugin-events-backend@0.6.0
+ - @backstage/plugin-search-backend@2.1.0
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.1
+ - @backstage/plugin-permission-backend@0.7.10
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-auth-node@0.6.14
+ - @backstage/plugin-app-backend@0.5.12
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.17
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.20
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9
+ - @backstage/plugin-devtools-backend@0.5.15
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.1
+ - @backstage/plugin-notifications-backend@0.6.3
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17
+ - @backstage/plugin-proxy-backend@0.6.11
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20
+ - @backstage/plugin-search-backend-module-catalog@0.3.13
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.1
+ - @backstage/plugin-search-backend-module-explore@0.3.12
+ - @backstage/plugin-search-backend-module-techdocs@0.4.12
+ - @backstage/plugin-search-backend-node@1.4.2
+ - @backstage/plugin-signals-backend@0.3.13
+
+## @internal/cli@0.0.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
+
+## e2e-test@0.2.38
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0
+ - @backstage/create-app@0.8.0
+
+## @internal/frontend@0.0.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/filter-predicates@0.1.1
+
+## @internal/scaffolder@0.0.19
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-scaffolder-react@1.20.0
+
+## techdocs-cli-embedded-app@0.2.118
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.36.0
+ - @backstage/ui@0.13.0
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/core-app-api@1.19.6
+ - @backstage/plugin-techdocs@1.17.1
+ - @backstage/plugin-catalog@2.0.0
+ - @backstage/plugin-app-react@0.2.1
+ - @backstage/frontend-defaults@0.5.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/integration-react@1.2.16
+ - @backstage/test-utils@1.7.16
+ - @backstage/plugin-techdocs-react@1.3.9
+
+## yarn-plugin-backstage@0.0.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0
+
+## @internal/plugin-todo-list@1.0.49
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+
+## @internal/plugin-todo-list-backend@1.0.48
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
diff --git a/docs/releases/v1.49.0-next.0-changelog.md b/docs/releases/v1.49.0-next.0-changelog.md
new file mode 100644
index 0000000000..fcfc092d5e
--- /dev/null
+++ b/docs/releases/v1.49.0-next.0-changelog.md
@@ -0,0 +1,2362 @@
+# Release v1.49.0-next.0
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.49.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.49.0-next.0)
+
+## @backstage/cli-common@0.2.0-next.0
+
+### Minor Changes
+
+- 56bd494: Added `targetPaths` and `findOwnPaths` as replacements for `findPaths`, with a cleaner separation between target project paths and package-relative paths.
+
+ To migrate existing `findPaths` usage:
+
+ ```ts
+ // Before
+ import { findPaths } from '@backstage/cli-common';
+ const paths = findPaths(__dirname);
+
+ // After — for target project paths (cwd-based):
+ import { targetPaths } from '@backstage/cli-common';
+ // paths.targetDir → targetPaths.dir
+ // paths.targetRoot → targetPaths.rootDir
+ // paths.resolveTarget('src') → targetPaths.resolve('src')
+ // paths.resolveTargetRoot('yarn.lock') → targetPaths.resolveRoot('yarn.lock')
+
+ // After — for package-relative paths:
+ import { findOwnPaths } from '@backstage/cli-common';
+ const own = findOwnPaths(__dirname);
+ // paths.ownDir → own.dir
+ // paths.ownRoot → own.rootDir
+ // paths.resolveOwn('config/jest.js') → own.resolve('config/jest.js')
+ // paths.resolveOwnRoot('tsconfig.json') → own.resolveRoot('tsconfig.json')
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/errors@1.2.7
+
+## @backstage/integration@1.21.0-next.0
+
+### Minor Changes
+
+- d933f62: Add configurable throttling and retry mechanism for GitLab integration.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-catalog-backend@3.5.0-next.0
+
+### Minor Changes
+
+- bf71677: Added opentelemetry metrics for SCM events:
+
+ - `catalog.events.scm.messages` with attribute `eventType`: Counter for the number of SCM events actually received by the catalog backend. The `eventType` is currently either `location` or `repository`.
+
+### Patch Changes
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+- fbf382f: Minor internal optimisation
+- 1ee5b28: Migrates existing catalog metrics to use the alpha MetricsService. This release is a 1:1 migration with no breaking changes.
+- 3181973: Changed the `search` table foreign key to point to `final_entities` instead of `refresh_state`
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/plugin-catalog-node@2.1.0-next.0
+
+### Minor Changes
+
+- bf71677: Added the ability for SCM events subscribers to mark the fact that they have taken actions based on events, which produces output metrics:
+
+ - `catalog.events.scm.actions` with attribute `action`: Counter for the number of actions actually taken by catalog internals or other subscribers, based on SCM events. The `action` is currently either `create`, `delete`, `refresh`, or `move`.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/backend-test-utils@1.11.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/plugin-notifications-backend-module-slack@0.4.0-next.0
+
+### Minor Changes
+
+- 749ba60: Add an extension for custom Slack message layouts
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-notifications-common@0.2.1
+ - @backstage/plugin-notifications-node@0.2.24-next.0
+
+## @backstage/app-defaults@1.7.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/backend-app-api@1.5.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/backend-defaults@0.15.3-next.0
+
+### Patch Changes
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+- d933f62: Add configurable throttling and retry mechanism for GitLab integration.
+- b99158a: Fixed `yarn backstage-cli config:check --strict --config app-config.yaml` config validation error by adding
+ an optional `default` type discriminator to PostgreSQL connection configuration,
+ allowing `config:check` to properly validate `default` connection configurations.
+- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
+- Updated dependencies
+ - @backstage/cli-node@0.2.19-next.0
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/backend-app-api@1.5.1-next.0
+ - @backstage/backend-dev-utils@0.1.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/backend-dynamic-feature-service@0.7.10-next.0
+
+### Patch Changes
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/cli-node@0.2.19-next.0
+ - @backstage/backend-defaults@0.15.3-next.0
+ - @backstage/plugin-catalog-backend@3.5.0-next.0
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-app-node@0.1.43-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-events-backend@0.5.12-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/backend-openapi-utils@0.6.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/backend-plugin-api@1.7.1-next.0
+
+### Patch Changes
+
+- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/backend-test-utils@1.11.1-next.0
+
+### Patch Changes
+
+- 1ee5b28: Adds a new metrics service mock to be leveraged in tests
+- Updated dependencies
+ - @backstage/backend-defaults@0.15.3-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/backend-app-api@1.5.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+
+## @backstage/catalog-client@1.13.1-next.0
+
+### Patch Changes
+
+- d2494d6: Minor update to catalog client docs
+- Updated dependencies
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.0
+
+## @backstage/cli@0.35.5-next.0
+
+### Patch Changes
+
+- 246877a: Updated dependency `bfj` to `^9.0.2`.
+
+- bba2e49: Internal refactor to use new concurrency utilities from `@backstage/cli-node`.
+
+- fd50cb3: Added `translations export` and `translations import` commands for managing translation files.
+
+ The `translations export` command discovers all `TranslationRef` definitions across frontend plugin dependencies and exports their default messages as JSON files. The `translations import` command generates `TranslationResource` wiring code from translated JSON files, ready to be plugged into the app.
+
+ Both commands support a `--pattern` option for controlling the message file layout, for example `--pattern '{lang}/{id}.json'` for language-based directory grouping.
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+
+- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages.
+
+- 092b41f: Updated dependency `webpack` to `~5.105.0`.
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/cli-node@0.2.19-next.0
+ - @backstage/eslint-plugin@0.2.2-next.0
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/module-federation-common@0.1.0
+ - @backstage/release-manifests@0.0.13
+ - @backstage/types@1.2.2
+
+## @backstage/cli-node@0.2.19-next.0
+
+### Patch Changes
+
+- 06c2015: Added `runConcurrentTasks` and `runWorkerQueueThreads` utilities, moved from the `@backstage/cli` internal code.
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/codemods@0.1.55-next.0
+
+### Patch Changes
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+
+## @backstage/config-loader@1.10.9-next.0
+
+### Patch Changes
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/core-app-api@1.19.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/core-compat-api@0.5.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-app-react@0.2.1-next.0
+
+## @backstage/core-components@0.18.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.7.2
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/core-plugin-api@1.12.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/create-app@0.7.10-next.0
+
+### Patch Changes
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+
+## @backstage/dev-utils@1.1.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.12.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/app-defaults@1.7.6-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/theme@0.7.2
+
+## @backstage/eslint-plugin@0.2.2-next.0
+
+### Patch Changes
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+
+## @backstage/frontend-app-api@0.15.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-defaults@0.4.1-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/frontend-defaults@0.4.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-app@0.4.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-app-api@0.15.1-next.0
+
+## @backstage/frontend-dynamic-feature-loader@0.1.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/config@1.3.6
+ - @backstage/module-federation-common@0.1.0
+
+## @backstage/frontend-plugin-api@0.14.2-next.0
+
+### Patch Changes
+
+- 9c81af9: Made the `pluginId` property optional in the `FrontendFeature` type, allowing plugins published against older versions of the framework to be used without type errors.
+- Updated dependencies
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/frontend-test-utils@0.5.1-next.0
+
+### Patch Changes
+
+- 909c742: Switched `MockTranslationApi` and related test utility imports from `@backstage/core-plugin-api/alpha` to the stable `@backstage/frontend-plugin-api` export. The `TranslationApi` type in the API report is now sourced from a single package. This has no effect on runtime behavior.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-app@0.4.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-app-api@0.15.1-next.0
+ - @backstage/test-utils@1.7.16-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-app-react@0.2.1-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/integration-react@1.2.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.4-next.0
+
+## @backstage/repo-tools@0.16.6-next.0
+
+### Patch Changes
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+- 2a51546: Fixed prettier existence checks in OpenAPI commands to use `fs.pathExists` instead of checking the resolved path string, which was always truthy.
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages.
+- 18a946c: Updated `@microsoft/api-extractor` to `7.57.3` and added tests for `getTsDocConfig`
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/cli-node@0.2.19-next.0
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+
+## @techdocs/cli@1.10.6-next.0
+
+### Patch Changes
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/backend-defaults@0.15.3-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-techdocs-node@1.14.3-next.0
+
+## @backstage/test-utils@1.7.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/ui@0.12.1-next.0
+
+### Patch Changes
+
+- a1f4bee: Made Accordion a `bg` provider so nested components like Button auto-increment their background level. Updated `useDefinition` to resolve `bg` `propDef` defaults for provider components.
+
+- 8909359: Fixed focus-visible outline styles for Menu and Select components.
+
+ **Affected components:** Menu, Select
+
+- 0f462f8: Improved type safety in `useDefinition` by centralizing prop resolution and strengthening the `BgPropsConstraint` to require that `bg` provider components declare `children` as a required prop in their OwnProps type.
+
+- 8909359: Added proper cursor styles for RadioGroup items.
+
+ **Affected components:** RadioGroup
+
+- Updated dependencies
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/plugin-api-docs@0.13.5-next.0
+
+### Patch Changes
+
+- 9c9d425: Fixed invisible text in parameter input fields when using dark mode in OpenAPI definition pages
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-catalog@1.33.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-app@0.4.1-next.0
+
+### Patch Changes
+
+- 909c742: Switched translation API imports (`translationApiRef`, `appLanguageApiRef`) from the alpha `@backstage/core-plugin-api/alpha` path to the stable `@backstage/frontend-plugin-api` export. This has no effect on runtime behavior.
+- Updated dependencies
+ - @backstage/ui@0.12.1-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-app-react@0.2.1-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-app-backend@0.5.12-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-app-node@0.1.43-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-app-node@0.1.43-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+
+## @backstage/plugin-app-react@0.2.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+
+## @backstage/plugin-app-visualizer@0.2.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.12.1-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+
+## @backstage/plugin-auth@0.1.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.7.2
+
+## @backstage/plugin-auth-backend@0.27.1-next.0
+
+### Patch Changes
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+- 619be54: Update migrations to be reversible
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-auth0-provider@0.3.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.27.1-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-gitlab-provider@0.4.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-google-provider@0.3.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-oidc-provider@0.4.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.27.1-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-okta-provider@0.2.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.12-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.12-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-auth-node@0.6.14-next.0
+
+## @backstage/plugin-auth-node@0.6.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-auth-react@0.1.25-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-bitbucket-cloud-common@0.3.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+
+## @backstage/plugin-catalog@1.33.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.12.1-next.0
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-compat-api@0.5.9-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-scaffolder-common@1.7.7-next.0
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-techdocs-common@0.1.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+
+## @backstage/plugin-catalog-backend-module-aws@0.4.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.15.3-next.0
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-kubernetes-common@0.9.10
+
+## @backstage/plugin-catalog-backend-module-azure@0.3.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-bitbucket-cloud-common@0.3.8-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-backend-module-gcp@0.3.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-kubernetes-common@0.9.10
+
+## @backstage/plugin-catalog-backend-module-gerrit@0.3.12-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-gitea@0.1.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-github@0.12.3-next.0
+
+### Patch Changes
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-backend-module-github-org@0.3.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend-module-github@0.12.3-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.8.1-next.0
+
+### Patch Changes
+
+- d933f62: Add configurable throttling and retry mechanism for GitLab integration.
+- Updated dependencies
+ - @backstage/backend-defaults@0.15.3-next.0
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend-module-gitlab@0.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.15.3-next.0
+ - @backstage/plugin-catalog-backend@3.5.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+
+## @backstage/plugin-catalog-backend-module-ldap@0.12.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-logs@0.1.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.5.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-backend-module-msgraph@0.9.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-puppetdb@0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-scaffolder-common@1.7.7-next.0
+
+## @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13
+ - @backstage/plugin-permission-common@0.9.6
+
+## @backstage/plugin-catalog-graph@0.5.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-catalog-import@0.13.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-catalog-react@2.0.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.12.1-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/frontend-test-utils@0.5.1-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-compat-api@0.5.9-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.0
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-compat-api@0.5.9-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13
+ - @backstage/plugin-devtools-react@0.1.2-next.0
+
+## @backstage/plugin-config-schema@0.1.78-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-devtools@0.1.37-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-compat-api@0.5.9-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-devtools-common@0.1.22
+ - @backstage/plugin-devtools-react@0.1.2-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-devtools-backend@0.5.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-devtools-common@0.1.22
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/plugin-devtools-react@0.1.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+
+## @backstage/plugin-events-backend@0.5.12-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-events-backend-module-aws-sqs@0.4.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-events-backend-module-azure@0.2.29-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.29-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-events-backend-module-bitbucket-server@0.1.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-events-backend-module-gerrit@0.2.29-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-events-backend-module-github@0.4.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-events-backend-module-gitlab@0.3.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-events-backend-module-kafka@0.3.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-events-backend-test-utils@0.1.53-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-events-node@0.4.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-gateway-backend@1.1.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+
+## @backstage/plugin-home@0.9.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-compat-api@0.5.9-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-home-react@0.1.36-next.0
+
+## @backstage/plugin-home-react@0.1.36-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-compat-api@0.5.9-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+
+## @backstage/plugin-kubernetes@0.12.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/plugin-kubernetes-common@0.9.10
+ - @backstage/plugin-kubernetes-react@0.5.17-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-kubernetes-backend@0.21.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-kubernetes-common@0.9.10
+ - @backstage/plugin-kubernetes-node@0.4.2-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/plugin-kubernetes-cluster@0.0.35-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/plugin-kubernetes-common@0.9.10
+ - @backstage/plugin-kubernetes-react@0.5.17-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-kubernetes-node@0.4.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-kubernetes-common@0.9.10
+
+## @backstage/plugin-kubernetes-react@0.5.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-kubernetes-common@0.9.10
+
+## @backstage/plugin-mcp-actions-backend@0.1.10-next.0
+
+### Patch Changes
+
+- dc81af1: Adds two new metrics to track MCP server operations and sessions.
+
+ - `mcp.server.operation.duration`: The duration taken to process an individual MCP operation
+ - `mcp.server.session.duration`: The duration of the MCP session from the perspective of the server
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-mui-to-bui@0.2.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.12.1-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/theme@0.7.2
+
+## @backstage/plugin-notifications@0.5.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-notifications-common@0.2.1
+ - @backstage/plugin-signals-react@0.0.20-next.0
+
+## @backstage/plugin-notifications-backend@0.6.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-notifications-common@0.2.1
+ - @backstage/plugin-notifications-node@0.2.24-next.0
+ - @backstage/plugin-signals-node@0.1.29-next.0
+
+## @backstage/plugin-notifications-backend-module-email@0.3.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-notifications-common@0.2.1
+ - @backstage/plugin-notifications-node@0.2.24-next.0
+
+## @backstage/plugin-notifications-node@0.2.24-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-notifications-common@0.2.1
+ - @backstage/plugin-signals-node@0.1.29-next.0
+
+## @backstage/plugin-org@0.6.50-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-org-react@0.1.48-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+
+## @backstage/plugin-permission-backend@0.7.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/plugin-permission-node@0.10.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-permission-common@0.9.6
+
+## @backstage/plugin-permission-react@0.4.41-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/plugin-permission-common@0.9.6
+
+## @backstage/plugin-proxy-backend@0.6.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-proxy-node@0.1.13-next.0
+
+## @backstage/plugin-proxy-node@0.1.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+
+## @backstage/plugin-scaffolder@1.35.5-next.0
+
+### Patch Changes
+
+- bd5b842: Added a new `ui:autoSelect` option to the EntityPicker field that controls whether an entity is automatically selected when the field loses focus. When set to `false`, the field will remain empty if the user closes it without explicitly selecting an entity, preventing unintentional selections. Defaults to `true` for backward compatibility.
+- ee87720: Added back the `formFieldsApiRef` and `ScaffolderFormFieldsApi` alpha exports that were unintentionally removed.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-scaffolder-common@1.7.7-next.0
+ - @backstage/plugin-scaffolder-react@1.19.8-next.0
+ - @backstage/plugin-techdocs-common@0.1.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+
+## @backstage/plugin-scaffolder-backend@3.1.4-next.0
+
+### Patch Changes
+
+- 4e39e63: Removed unused dependencies
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+ - @backstage/plugin-scaffolder-common@1.7.7-next.0
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-azure@0.2.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.4-next.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.19-next.0
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-bitbucket-cloud-common@0.3.8-next.0
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.15.3-next.0
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-gcp@0.2.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-gitea@0.2.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.4-next.0
+
+### Patch Changes
+
+- 5730c8e: Added `maskedAndHidden` option to `gitlab:projectVariable:create` and `publish:gitlab` action to support creating GitLab project variables that are both masked and hidden. Updated gitbeaker to version 43.8.0 for proper type support.
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-notifications-common@0.2.1
+ - @backstage/plugin-notifications-node@0.2.24-next.0
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-rails@0.5.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-sentry@0.3.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.0
+
+## @backstage/plugin-scaffolder-common@1.7.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.6
+
+## @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-scaffolder-common@1.7.7-next.0
+
+## @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/backend-test-utils@1.11.1-next.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+
+## @backstage/plugin-scaffolder-react@1.19.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-scaffolder-common@1.7.7-next.0
+
+## @backstage/plugin-search@1.6.2-next.0
+
+### Patch Changes
+
+- d5eb954: Fixes the search component not registering the first search on navigate to the search page.
+- Updated dependencies
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend@2.0.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-catalog@0.3.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-explore@0.3.12-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-pg@0.5.53-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-techdocs@0.4.12-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-techdocs-node@1.14.3-next.0
+
+## @backstage/plugin-search-backend-node@1.4.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-react@1.10.5-next.0
+
+### Patch Changes
+
+- d5eb954: Fixes the search component not registering the first search on navigate to the search page.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-signals@0.0.29-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/plugin-signals-react@0.0.20-next.0
+
+## @backstage/plugin-signals-backend@0.3.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-signals-node@0.1.29-next.0
+
+## @backstage/plugin-signals-node@0.1.29-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-signals-react@0.0.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-techdocs@1.17.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-auth-react@0.1.25-next.0
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-techdocs-common@0.1.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+
+## @backstage/plugin-techdocs-addons-test-utils@2.0.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-catalog@1.33.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/plugin-techdocs@1.17.1-next.0
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/test-utils@1.7.16-next.0
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+
+## @backstage/plugin-techdocs-backend@2.1.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/plugin-catalog-node@2.1.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-client@1.13.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-techdocs-node@1.14.3-next.0
+
+## @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+
+## @backstage/plugin-techdocs-node@1.14.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-techdocs-common@0.1.1
+
+## @backstage/plugin-techdocs-react@1.3.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-techdocs-common@0.1.1
+
+## @backstage/plugin-user-settings@0.9.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/plugin-signals-react@0.0.20-next.0
+ - @backstage/plugin-user-settings-common@0.1.0
+
+## @backstage/plugin-user-settings-backend@0.4.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-signals-node@0.1.29-next.0
+ - @backstage/plugin-user-settings-common@0.1.0
+
+## example-app@0.0.33-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.12.1-next.0
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-search@1.6.2-next.0
+ - @backstage/plugin-api-docs@0.13.5-next.0
+ - @backstage/cli@0.35.5-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-scaffolder@1.35.5-next.0
+ - @backstage/plugin-app@0.4.1-next.0
+ - @backstage/plugin-app-visualizer@0.2.1-next.0
+ - @backstage/plugin-catalog@1.33.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/plugin-techdocs@1.17.1-next.0
+ - @backstage/app-defaults@1.7.6-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-compat-api@0.5.9-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-app-api@0.15.1-next.0
+ - @backstage/frontend-defaults@0.4.1-next.0
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-app-react@0.2.1-next.0
+ - @backstage/plugin-auth@0.1.6-next.0
+ - @backstage/plugin-auth-react@0.1.25-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-catalog-graph@0.5.8-next.0
+ - @backstage/plugin-catalog-import@0.13.11-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0
+ - @backstage/plugin-devtools@0.1.37-next.0
+ - @backstage/plugin-home@0.9.3-next.0
+ - @backstage/plugin-home-react@0.1.36-next.0
+ - @backstage/plugin-kubernetes@0.12.17-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.35-next.0
+ - @backstage/plugin-notifications@0.5.15-next.0
+ - @backstage/plugin-org@0.6.50-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-scaffolder-react@1.19.8-next.0
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-signals@0.0.29-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.0
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+ - @backstage/plugin-user-settings@0.9.1-next.0
+
+## app-example-plugin@0.0.33-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-components@0.18.8-next.0
+
+## example-app-legacy@0.2.119-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.12.1-next.0
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-search@1.6.2-next.0
+ - @backstage/plugin-api-docs@0.13.5-next.0
+ - @backstage/cli@0.35.5-next.0
+ - @backstage/plugin-scaffolder@1.35.5-next.0
+ - @backstage/plugin-catalog@1.33.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/plugin-mui-to-bui@0.2.5-next.0
+ - @backstage/plugin-techdocs@1.17.1-next.0
+ - @backstage/app-defaults@1.7.6-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-app-api@0.15.1-next.0
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-auth-react@0.1.25-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-catalog-graph@0.5.8-next.0
+ - @backstage/plugin-catalog-import@0.13.11-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0
+ - @backstage/plugin-devtools@0.1.37-next.0
+ - @backstage/plugin-home@0.9.3-next.0
+ - @backstage/plugin-home-react@0.1.36-next.0
+ - @backstage/plugin-kubernetes@0.12.17-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.35-next.0
+ - @backstage/plugin-notifications@0.5.15-next.0
+ - @backstage/plugin-org@0.6.50-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-scaffolder-react@1.19.8-next.0
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-signals@0.0.29-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.0
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+ - @backstage/plugin-user-settings@0.9.1-next.0
+
+## example-backend@0.0.48-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.15.3-next.0
+ - @backstage/plugin-auth-backend@0.27.1-next.0
+ - @backstage/plugin-catalog-backend@3.5.0-next.0
+ - @backstage/plugin-scaffolder-backend@3.1.4-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-mcp-actions-backend@0.1.10-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-app-backend@0.5.12-next.0
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.0
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.0
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.0
+ - @backstage/plugin-devtools-backend@0.5.15-next.0
+ - @backstage/plugin-events-backend@0.5.12-next.0
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.0
+ - @backstage/plugin-kubernetes-backend@0.21.2-next.0
+ - @backstage/plugin-notifications-backend@0.6.3-next.0
+ - @backstage/plugin-permission-backend@0.7.10-next.0
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+ - @backstage/plugin-proxy-backend@0.6.11-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.0
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.0
+ - @backstage/plugin-search-backend@2.0.13-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.3.13-next.0
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.0
+ - @backstage/plugin-search-backend-module-explore@0.3.12-next.0
+ - @backstage/plugin-search-backend-module-techdocs@0.4.12-next.0
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-signals-backend@0.3.13-next.0
+ - @backstage/plugin-techdocs-backend@2.1.6-next.0
+
+## e2e-test@0.2.38-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/create-app@0.7.10-next.0
+ - @backstage/errors@1.2.7
+
+## @internal/frontend@0.0.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+
+## @internal/scaffolder@0.0.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-scaffolder-react@1.19.8-next.0
+
+## techdocs-cli-embedded-app@0.2.118-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.12.1-next.0
+ - @backstage/cli@0.35.5-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-catalog@1.33.1-next.0
+ - @backstage/plugin-techdocs@1.17.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/frontend-defaults@0.4.1-next.0
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/test-utils@1.7.16-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-app-react@0.2.1-next.0
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+
+## yarn-plugin-backstage@0.0.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/release-manifests@0.0.13
+
+## @internal/plugin-todo-list@1.0.49-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+
+## @internal/plugin-todo-list-backend@1.0.48-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/errors@1.2.7
diff --git a/docs/releases/v1.49.0-next.1-changelog.md b/docs/releases/v1.49.0-next.1-changelog.md
new file mode 100644
index 0000000000..7110b3d0d5
--- /dev/null
+++ b/docs/releases/v1.49.0-next.1-changelog.md
@@ -0,0 +1,1567 @@
+# Release v1.49.0-next.1
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.49.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.49.0-next.1)
+
+## @backstage/integration@2.0.0-next.1
+
+### Major Changes
+
+- 527cf88: **BREAKING** Removed deprecated Azure DevOps, Bitbucket, Gerrit and GitHub code:
+
+ - For Azure DevOps, the long deprecated `token` string and `credential` object have been removed from the `config.d.ts`. Use the `credentials` array object instead.
+ - For Bitbucket, the long deprecated `bitbucket` object has been removed from the `config.d.ts`. Use the `bitbucketCloud` or `bitbucketServer` objects instead.
+ - For Gerrit, the `parseGerritGitilesUrl` function has been removed, use `parseGitilesUrlRef` instead. The `buildGerritGitilesArchiveUrl` function has also been removed, use `buildGerritGitilesArchiveUrlFromLocation` instead.
+ - For GitHub, the `getGitHubRequestOptions` function has been removed.
+
+### Patch Changes
+
+- 993a598: Fixed Azure integration config schema visibility annotations to use per-field `@visibility secret` instead of `@deepVisibility secret` on parent objects, so that non-secret fields like `clientId`, `tenantId`, `organizations`, and `managedIdentityClientId` are no longer incorrectly marked as secret.
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-common@2.0.0-next.1
+
+### Major Changes
+
+- 527cf88: **BREAKING** Removed deprecated `bitbucket` integration from being registered in the `ScaffolderClient`. Use the `bitbucketCloud` or `bitbucketServer` integrations instead.
+
+### Minor Changes
+
+- f598909: **BREAKING PRODUCERS**: Made `retry`, `listTasks`, `listTemplatingExtensions`, `dryRun`, and `autocomplete` required methods on the `ScaffolderApi` interface. Implementations of `ScaffolderApi` must now provide these methods.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.6
+
+## @backstage/backend-defaults@0.16.0-next.1
+
+### Minor Changes
+
+- 0e7d8f9: The scheduler service now uses the metrics service to create metrics, providing plugin-scoped attribution.
+- 527cf88: **BREAKING** Removed deprecated `BitbucketUrlReader`. Use the `BitbucketCloudUrlReader` or the `BitbucketServerUrlReader` instead.
+
+### Patch Changes
+
+- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500.
+- Updated dependencies
+ - @backstage/cli-node@0.2.19-next.1
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.1
+ - @backstage/backend-app-api@1.5.1-next.0
+ - @backstage/backend-dev-utils@0.1.7
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/backend-dynamic-feature-service@0.8.0-next.1
+
+### Minor Changes
+
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.5.0-next.1
+ - @backstage/cli-common@0.2.0-next.1
+ - @backstage/cli-node@0.2.19-next.1
+ - @backstage/backend-defaults@0.16.0-next.1
+ - @backstage/plugin-events-backend@0.6.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.1
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-app-node@0.1.43-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/catalog-client@1.14.0-next.1
+
+### Minor Changes
+
+- 972f686: Added support for the `query` field in `getEntitiesByRefs` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+- 56c908e: Added support for the `query` field in `getEntityFacets` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+- 51e23eb: Added predicate-based entity filtering via POST /entities/by-query endpoint.
+
+ Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and (partially) `$contains` operators for expressive entity queries. Integrated into the existing `queryEntities` flow with full cursor-based pagination, permission enforcement, and `totalItems` support.
+
+ The catalog client's `queryEntities()` method automatically routes to the POST endpoint when a `query` predicate is provided.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.0
+
+## @backstage/cli@0.36.0-next.1
+
+### Minor Changes
+
+- b36a60d: **BREAKING**: The `migrate package-exports` command has been removed. Use `repo fix` instead.
+
+### Patch Changes
+
+- 0d2d0f2: Internal refactor of CLI modularization, moving individual commands to be implemented with cleye.
+- 2fcba39: Internal refactor to move shared utilities into their consuming modules, reducing cross-module dependencies.
+- c85ac86: Internal refactor to split `loadCliConfig` into separate implementations for the build and config CLI modules, removing a cross-module dependency.
+- 61cb976: Migrated internal versioning utilities to use `@backstage/cli-node` instead of a local implementation.
+- 825c81d: Internal refactor of CLI command modules.
+- a9d23c4: Properly support `package.json` `workspaces` field
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.1
+ - @backstage/cli-node@0.2.19-next.1
+ - @backstage/module-federation-common@0.1.2-next.0
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.2.2-next.0
+ - @backstage/release-manifests@0.0.13
+ - @backstage/types@1.2.2
+
+## @backstage/repo-tools@0.17.0-next.1
+
+### Minor Changes
+
+- 0fbcf23: Added support for OpenAPI 3.1 to all `schema openapi` commands. The commands now auto-detect the OpenAPI version from the spec file and use the appropriate generator, supporting both OpenAPI 3.0.x and 3.1.x specifications.
+
+### Patch Changes
+
+- 426edbe: Fixed `generate-catalog-info` command failing with "too many arguments" when invoked by lint-staged via the pre-commit hook.
+- d5779e5: Updated the CLI report parser to support cleye-style help output, and strip ANSI escape codes from captured output.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.1
+ - @backstage/cli-node@0.2.19-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/errors@1.2.7
+
+## @backstage/ui@0.13.0-next.1
+
+### Minor Changes
+
+- 768f09d: **BREAKING**: Simplified the neutral background prop API for container components. The explicit `neutral-1`, `neutral-2`, `neutral-3`, and `neutral-auto` values have been removed from `ProviderBg`. They are replaced by a single `'neutral'` value that always auto-increments from the parent context, making it impossible to skip or pin to an explicit neutral level.
+
+ **Migration:**
+
+ Replace any explicit `bg="neutral-1"`, `bg="neutral-2"`, `bg="neutral-3"`, or `bg="neutral-auto"` props with `bg="neutral"`. To achieve a specific neutral level in stories or tests, use nested containers — each additional `bg="neutral"` wrapper increments by one level.
+
+ ```tsx
+ // Before
+ ...
+
+ // After
+
+ ...
+
+ ```
+
+ **Affected components:** Box, Flex, Grid, Card, Accordion, Popover, Tooltip, Dialog, Menu
+
+- b42fcdc: **BREAKING**: Removed `--bui-bg-popover` CSS token. Popover, Tooltip, Menu, and Dialog now use `--bui-bg-app` for their outer shell and `Box bg="neutral-1"` for content areas, providing better theme consistency and eliminating a redundant token.
+
+ **Migration:**
+
+ Replace any usage of `--bui-bg-popover` with `--bui-bg-neutral-1` (for content surfaces) or `--bui-bg-app` (for outer shells):
+
+ ```diff
+ - background: var(--bui-bg-popover);
+ + background: var(--bui-bg-neutral-1);
+ ```
+
+ **Affected components:** Popover, Tooltip, Menu, Dialog
+
+- bd3a76e: **BREAKING**: Data attributes rendered by components are now always lowercase. This affects CSS selectors targeting camelCase data attributes.
+
+ **Migration:**
+
+ Update any custom CSS selectors that target camelCase data attributes to use lowercase instead:
+
+ ```diff
+ - [data-startCollapsed='true'] { ... }
+ + [data-startcollapsed='true'] { ... }
+ ```
+
+ **Affected components:** SearchField
+
+- 95702ab: **BREAKING**: Removed deprecated types `ComponentDefinition`, `ClassNamesMap`, `DataAttributeValues`, and `DataAttributesMap` from the public API. These were internal styling infrastructure types that have been replaced by the `defineComponent` system.
+
+ **Migration:**
+
+ Remove any direct usage of these types. Component definitions now use `defineComponent()` and their shapes are not part of the public API contract.
+
+ ```diff
+ - import type { ComponentDefinition, ClassNamesMap } from '@backstage/ui';
+ ```
+
+ If you were reading `definition.dataAttributes`, use `definition.propDefs` instead — props with `dataAttribute: true` in `propDefs` are the equivalent.
+
+### Patch Changes
+
+- 58224d3: Fixed neutral-1 hover & pressed state in light mode.
+
+- 95702ab: Migrated all components from `useStyles` to `useDefinition` hook. Exported `OwnProps` types for each component, enabling better type composition for consumers.
+
+ **Affected components:** Avatar, Checkbox, Container, Dialog, FieldError, FieldLabel, Flex, FullPage, Grid, HeaderPage, Link, Menu, PasswordField, PluginHeader, Popover, RadioGroup, SearchField, Select, Skeleton, Switch, Table, TablePagination, Tabs, TagGroup, Text, TextField, ToggleButton, ToggleButtonGroup, Tooltip, VisuallyHidden
+
+- 4c2c350: Removed the `transition` on `Container` padding to prevent an unwanted animation when the viewport is resized.
+
+ Affected components: Container
+
+- d4fa5b4: Fixed tab `matchStrategy` matching to ignore query parameters and hash fragments in tab `href` values. Previously, tabs with query params in their `href` (e.g., `/page?group=foo`) would never show as active since matching compared the full `href` string against `location.pathname` which never includes query params.
+
+ **Affected components:** Tabs, PluginHeader
+
+- 36987db: Fixed handling of the `style` prop on `Button`, `ButtonIcon`, and `ButtonLink` so that it is now correctly forwarded to the underlying element instead of being silently dropped.
+
+ **Affected components:** Button, ButtonIcon, ButtonLink
+
+- 95702ab: Fixed Link variant default from `'body'` to `'body-medium'` to match actual CSS selectors. The previous default did not correspond to a valid variant value.
+
+ **Affected components:** Link
+
+- 9027b10: Fixed scroll overflow in Menu and Select popover content when constrained by viewport height.
+
+ **Affected components:** Menu, Select
+
+- 4105a78: Merged the internal `PluginHeaderToolbar` component into `PluginHeader`, removing the separate component and its associated types (`PluginHeaderToolbarOwnProps`, `PluginHeaderToolbarProps`) and definition (`PluginHeaderToolbarDefinition`). This is an internal refactor with no changes to the public API of `PluginHeader`.
+
+ **Affected components:** PluginHeader
+
+- b303857: Fixed `isRequired` prop not being passed to the underlying React Aria field components in TextField, SearchField, and PasswordField. Previously, `isRequired` was consumed locally for the secondary label text but never forwarded, which meant the input elements lacked `aria-required="true"` and React Aria's built-in required validation was not activated.
+
+ **Affected components:** TextField, SearchField, PasswordField
+
+- cd3cb0f: Improved `useBreakpoint` performance by sharing a single set of `matchMedia` listeners across all component instances instead of creating independent listeners per hook call.
+
+- 36987db: Extended `AlertProps`, `ContainerProps`, `DialogBodyProps`, and `FieldLabelProps` with native div element props to allow passing attributes like `aria-*` and `data-*`.
+
+ **Affected components:** Alert, Container, DialogBody, FieldLabel
+
+- Updated dependencies
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/plugin-catalog@1.34.0-next.1
+
+### Minor Changes
+
+- 4d58894: Added support for group alias IDs and configurable content ordering on the entity page. Groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `defaultContentOrder` option (default `title`) controls how content items within each group are sorted, with support for both a page-level default and per-group overrides.
+
+### Patch Changes
+
+- 07ba746: Fixed entity page tab groups not respecting the ordering from the `groups` configuration.
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.1
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/plugin-scaffolder-common@2.0.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-compat-api@0.5.9-next.1
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-techdocs-common@0.1.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+
+## @backstage/plugin-catalog-backend@3.5.0-next.1
+
+### Minor Changes
+
+- a6b2819: Added `query-catalog-entities` action to the catalog backend actions registry. Supports predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+- 972f686: Added support for predicate-based filtering on the `/entities/by-refs` endpoint via the `query` field in the request body. Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+- 56c908e: Added support for predicate-based filtering on the `/entity-facets` endpoint via a new `POST` method. Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+- 51e23eb: Added predicate-based entity filtering via POST /entities/by-query endpoint.
+
+ Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and (partially) `$contains` operators for expressive entity queries. Integrated into the existing `queryEntities` flow with full cursor-based pagination, permission enforcement, and `totalItems` support.
+
+ The catalog client's `queryEntities()` method automatically routes to the POST endpoint when a `query` predicate is provided.
+
+### Patch Changes
+
+- 72747b4: Deprecated two processors as they have been moved to the Community Plugins repo with their own backend modules:
+
+ - `AnnotateScmSlugEntityProcessor`: Use `@backstage-community/plugin-catalog-backend-module-annotate-scm-slug` instead
+ - `CodeOwnersProcessor`: Use `@backstage-community/plugin-catalog-backend-module-codeowners` instead
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/plugin-catalog-backend-module-github@0.13.0-next.1
+
+### Minor Changes
+
+- b11c2cd: The default user transformer now prefers organization verified domain emails over the user's public GitHub email when populating the user entity profile. It also strips plus-addressed routing tags that GitHub adds to these emails.
+
+ If you want to retain the old behavior, you can do so with a custom user transformer using the `githubOrgEntityProviderTransformsExtensionPoint`:
+
+ ```ts
+ import { createBackendModule } from '@backstage/backend-plugin-api';
+ import { githubOrgEntityProviderTransformsExtensionPoint } from '@backstage/plugin-catalog-backend-module-github-org';
+ import { defaultUserTransformer } from '@backstage/plugin-catalog-backend-module-github';
+
+ export default createBackendModule({
+ pluginId: 'catalog',
+ moduleId: 'github-org-custom-transforms',
+ register(env) {
+ env.registerInit({
+ deps: {
+ transforms: githubOrgEntityProviderTransformsExtensionPoint,
+ },
+ async init({ transforms }) {
+ transforms.setUserTransformer(async (item, ctx) => {
+ const entity = await defaultUserTransformer(item, ctx);
+ if (entity && item.email) {
+ entity.spec.profile!.email = item.email;
+ }
+ return entity;
+ });
+ },
+ });
+ },
+ });
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-react@2.1.0-next.1
+
+### Minor Changes
+
+- 4d58894: Added `aliases` and `contentOrder` fields to `EntityContentGroupDefinition`, allowing groups to declare alias IDs and control the sort order of their content items.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.1
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/frontend-test-utils@0.5.1-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-compat-api@0.5.9-next.1
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-events-backend@0.6.0-next.1
+
+### Minor Changes
+
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-scaffolder-backend@3.2.0-next.1
+
+### Minor Changes
+
+- c9b11eb: Added a new `list-scaffolder-tasks` action that allows querying scaffolder tasks with optional ownership filtering and pagination support
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+- 7695dd2: Added a new `list-scaffolder-actions` action that returns all installed scaffolder actions with their schemas and examples
+
+### Patch Changes
+
+- e27bd4e: Removed `@backstage/plugin-scaffolder-backend-module-bitbucket` from `package.json` as the package itself has been deprecated and the code deleted.
+- ccc20cf: create scaffolder MCP action to dry run a provided scaffolder template
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-common@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/plugin-scaffolder-node@0.13.0-next.1
+
+### Minor Changes
+
+- e27bd4e: **BREAKING** Removed deprecated `bitbucket` integration from being used in the `parseRepoUrl` function. It will use the `bitbucketCloud` or `bitbucketServer` integrations instead.
+
+### Patch Changes
+
+- f598909: Added `scaffolderServiceRef` and `ScaffolderService` interface for backend plugins that need to interact with the scaffolder API using `BackstageCredentials` instead of raw tokens.
+- Updated dependencies
+ - @backstage/backend-test-utils@1.11.1-next.1
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-common@2.0.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.6
+
+## @backstage/plugin-search-backend@2.1.0-next.1
+
+### Minor Changes
+
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/backend-test-utils@1.11.1-next.1
+
+### Patch Changes
+
+- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500.
+- Updated dependencies
+ - @backstage/backend-defaults@0.16.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.1
+ - @backstage/backend-app-api@1.5.1-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+
+## @backstage/cli-common@0.2.0-next.1
+
+### Patch Changes
+
+- e44b6a9: The `findOwnRootDir` utility now searches for the monorepo root by traversing up the directory tree looking for a `package.json` with `workspaces`, instead of assuming a fixed `../..` relative path. If no workspaces root is found during this traversal, `findOwnRootDir` now throws to enforce stricter validation of the repository layout.
+- Updated dependencies
+ - @backstage/errors@1.2.7
+
+## @backstage/cli-node@0.2.19-next.1
+
+### Patch Changes
+
+- 61cb976: Added `toString()` method to `Lockfile` for serializing lockfiles back to string format.
+- 3c811bf: Added `hasBackstageYarnPlugin` and `SuccessCache` exports, moved from `@backstage/cli`.
+- a9d23c4: Properly support `package.json` `workspaces` field
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/core-compat-api@0.5.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-app-react@0.2.1-next.0
+
+## @backstage/create-app@0.7.10-next.1
+
+### Patch Changes
+
+- a9d23c4: Properly support `package.json` `workspaces` field
+
+- ebd4630: Replace deprecated `workspaces.packages` with `workspaces` in `package.json`
+
+ This change is **not** required, but you can edit your main `package.json`, to fit the more modern & more common pattern:
+
+ ```diff
+ - "workspaces": {
+ - "packages": [
+ "workspaces": [
+ "packages/*",
+ "plugins/*"
+ - ]
+ - },
+ ],
+ ```
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.1
+
+## @backstage/dev-utils@1.1.21-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/app-defaults@1.7.6-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/theme@0.7.2
+
+## @backstage/frontend-dynamic-feature-loader@0.1.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/module-federation-common@0.1.2-next.0
+ - @backstage/config@1.3.6
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+
+## @backstage/frontend-test-utils@0.5.1-next.1
+
+### Patch Changes
+
+- 479282f: Fixed type inference of `TestApiPair` when using tuple syntax by wrapping `MockWithApiFactory` in `NoInfer`.
+- Updated dependencies
+ - @backstage/plugin-app@0.4.1-next.1
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-app-api@0.15.1-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/test-utils@1.7.16-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-app-react@0.2.1-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/integration-react@1.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.4-next.0
+
+## @backstage/module-federation-common@0.1.2-next.0
+
+### Patch Changes
+
+- 0cb5646: Fixed the `@mui/material/styles` shared dependency key by removing a trailing slash that caused module resolution failures with MUI package exports.
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @techdocs/cli@1.10.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.1
+ - @backstage/plugin-techdocs-node@1.14.3-next.1
+ - @backstage/backend-defaults@0.16.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+
+## @backstage/plugin-api-docs@0.13.5-next.1
+
+### Patch Changes
+
+- 30e08df: Added default entity content groups for the API docs entity content tabs. The API definition tab defaults to the `documentation` group and the APIs tab defaults to the `development` group.
+- Updated dependencies
+ - @backstage/plugin-catalog@1.34.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-app@0.4.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.1
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-app-react@0.2.1-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-app-visualizer@0.2.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.1
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+
+## @backstage/plugin-auth-backend@0.27.1-next.1
+
+### Patch Changes
+
+- 1ccad86: Added `who-am-i` action to the auth backend actions registry. Returns the catalog entity and user info for the currently authenticated user.
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.14-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-auth-node@0.6.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-bitbucket-cloud-common@0.3.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+
+## @backstage/plugin-catalog-backend-module-aws@0.4.21-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.16.0-next.1
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-kubernetes-common@0.9.10
+
+## @backstage/plugin-catalog-backend-module-azure@0.3.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-bitbucket-cloud-common@0.3.8-next.1
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-backend-module-gerrit@0.3.12-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-gitea@0.1.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-github-org@0.3.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend-module-github@0.13.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.8.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.16.0-next.1
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.5.0-next.1
+ - @backstage/backend-defaults@0.16.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+
+## @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-common@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-graph@0.5.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-catalog-import@0.13.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-catalog-node@2.1.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/backend-test-utils@1.11.1-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/plugin-devtools@0.1.37-next.1
+
+### Patch Changes
+
+- afabb37: Fixed URL encoding of task IDs for the trigger feature (tasks that contained a "/" in their ID were not triggered)
+- Updated dependencies
+ - @backstage/core-compat-api@0.5.9-next.1
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-devtools-common@0.1.22
+ - @backstage/plugin-devtools-react@0.1.2-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-events-backend-module-github@0.4.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+
+## @backstage/plugin-home@0.9.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-compat-api@0.5.9-next.1
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-home-react@0.1.36-next.0
+
+## @backstage/plugin-kubernetes@0.12.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-kubernetes-common@0.9.10
+ - @backstage/plugin-kubernetes-react@0.5.17-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-kubernetes-backend@0.21.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-kubernetes-common@0.9.10
+ - @backstage/plugin-kubernetes-node@0.4.2-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## @backstage/plugin-kubernetes-cluster@0.0.35-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/plugin-kubernetes-common@0.9.10
+ - @backstage/plugin-kubernetes-react@0.5.17-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-mcp-actions-backend@0.1.10-next.1
+
+### Patch Changes
+
+- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500.
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-mui-to-bui@0.2.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/theme@0.7.2
+
+## @backstage/plugin-notifications-backend-module-email@0.3.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/plugin-notifications-node@0.2.24-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-notifications-common@0.2.1
+
+## @backstage/plugin-notifications-node@0.2.24-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-notifications-common@0.2.1
+ - @backstage/plugin-signals-node@0.1.29-next.0
+
+## @backstage/plugin-org@0.6.50-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-org-react@0.1.48-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+
+## @backstage/plugin-scaffolder@1.35.5-next.1
+
+### Patch Changes
+
+- e27bd4e: Removed check for deprecated `bitbucket` integration from `repoPickerValidation` function used by the `RepoUrlPicker`, it now validates the `bitbucketServer` and `bitbucketCloud` integrations instead.
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/plugin-scaffolder-react@1.19.8-next.1
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-common@2.0.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-techdocs-common@0.1.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-azure@0.2.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.4-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-bitbucket-cloud-common@0.3.8-next.1
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.21-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.16.0-next.1
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-scaffolder-backend-module-gcp@0.2.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-gitea@0.2.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.4-next.1
+
+### Patch Changes
+
+- 0c1726a: Added new `gitlab:group:access` scaffolder action to add or remove users and groups as members of GitLab groups. The action supports specifying members via `userIds` and/or `groupIds` array parameters, configurable access levels (Guest, Reporter, Developer, Maintainer, Owner), and defaults to the 'add' action when not specified.
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/plugin-notifications-node@0.2.24-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-notifications-common@0.2.1
+
+## @backstage/plugin-scaffolder-backend-module-rails@0.5.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-scaffolder-backend-module-sentry@0.3.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.1
+
+## @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-test-utils@1.11.1-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-scaffolder-react@1.19.8-next.1
+
+### Patch Changes
+
+- 004b5c1: Added back `formFieldsApiRef` and `ScaffolderFormFieldsApi` as alpha exports.
+- f598909: Added `scaffolderApiMock` test utility, exported from `@backstage/plugin-scaffolder-react/testUtils`.
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/frontend-test-utils@0.5.1-next.1
+ - @backstage/plugin-scaffolder-common@2.0.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
+## @backstage/plugin-search@1.6.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-search-react@1.10.5-next.0
+
+## @backstage/plugin-search-backend-module-catalog@0.3.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-techdocs@0.4.12-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-techdocs-node@1.14.3-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-techdocs@1.17.1-next.1
+
+### Patch Changes
+
+- 30e08df: Added `documentation` as the default entity content group for the TechDocs entity content tab.
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-auth-react@0.1.25-next.0
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-techdocs-common@0.1.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+
+## @backstage/plugin-techdocs-addons-test-utils@2.0.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-techdocs@1.17.1-next.1
+ - @backstage/plugin-catalog@1.34.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/test-utils@1.7.16-next.0
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+
+## @backstage/plugin-techdocs-backend@2.1.6-next.1
+
+### Patch Changes
+
+- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require.
+- Updated dependencies
+ - @backstage/catalog-client@1.14.0-next.1
+ - @backstage/plugin-techdocs-node@1.14.3-next.1
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+
+## @backstage/plugin-techdocs-node@1.14.3-next.1
+
+### Patch Changes
+
+- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require.
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-techdocs-common@0.1.1
+
+## @backstage/plugin-user-settings@0.9.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/plugin-signals-react@0.0.20-next.0
+ - @backstage/plugin-user-settings-common@0.1.0
+
+## example-app@0.0.33-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.1
+ - @backstage/cli@0.36.0-next.1
+ - @backstage/plugin-devtools@0.1.37-next.1
+ - @backstage/plugin-scaffolder@1.35.5-next.1
+ - @backstage/plugin-api-docs@0.13.5-next.1
+ - @backstage/plugin-techdocs@1.17.1-next.1
+ - @backstage/plugin-catalog@1.34.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/plugin-scaffolder-react@1.19.8-next.1
+ - @backstage/plugin-app@0.4.1-next.1
+ - @backstage/plugin-app-visualizer@0.2.1-next.1
+ - @backstage/plugin-catalog-graph@0.5.8-next.1
+ - @backstage/plugin-catalog-import@0.13.11-next.1
+ - @backstage/plugin-home@0.9.3-next.1
+ - @backstage/plugin-org@0.6.50-next.1
+ - @backstage/app-defaults@1.7.6-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-compat-api@0.5.9-next.1
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-app-api@0.15.1-next.0
+ - @backstage/frontend-defaults@0.4.1-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-app-react@0.2.1-next.0
+ - @backstage/plugin-auth@0.1.6-next.0
+ - @backstage/plugin-auth-react@0.1.25-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0
+ - @backstage/plugin-home-react@0.1.36-next.0
+ - @backstage/plugin-kubernetes@0.12.17-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.35-next.1
+ - @backstage/plugin-notifications@0.5.15-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-search@1.6.2-next.1
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-signals@0.0.29-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+ - @backstage/plugin-user-settings@0.9.1-next.1
+
+## example-app-legacy@0.2.119-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.1
+ - @backstage/cli@0.36.0-next.1
+ - @backstage/plugin-devtools@0.1.37-next.1
+ - @backstage/plugin-scaffolder@1.35.5-next.1
+ - @backstage/plugin-api-docs@0.13.5-next.1
+ - @backstage/plugin-techdocs@1.17.1-next.1
+ - @backstage/plugin-catalog@1.34.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/plugin-scaffolder-react@1.19.8-next.1
+ - @backstage/plugin-mui-to-bui@0.2.5-next.1
+ - @backstage/plugin-catalog-graph@0.5.8-next.1
+ - @backstage/plugin-catalog-import@0.13.11-next.1
+ - @backstage/plugin-home@0.9.3-next.1
+ - @backstage/plugin-org@0.6.50-next.1
+ - @backstage/app-defaults@1.7.6-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-app-api@0.15.1-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-auth-react@0.1.25-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0
+ - @backstage/plugin-home-react@0.1.36-next.0
+ - @backstage/plugin-kubernetes@0.12.17-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.35-next.1
+ - @backstage/plugin-notifications@0.5.15-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-search@1.6.2-next.1
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-signals@0.0.29-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+ - @backstage/plugin-user-settings@0.9.1-next.1
+
+## example-backend@0.0.48-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.5.0-next.1
+ - @backstage/plugin-auth-backend@0.27.1-next.1
+ - @backstage/plugin-techdocs-backend@2.1.6-next.1
+ - @backstage/backend-defaults@0.16.0-next.1
+ - @backstage/plugin-scaffolder-backend@3.2.0-next.1
+ - @backstage/plugin-mcp-actions-backend@0.1.10-next.1
+ - @backstage/plugin-events-backend@0.6.0-next.1
+ - @backstage/plugin-search-backend@2.1.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.1
+ - @backstage/plugin-kubernetes-backend@0.21.2-next.1
+ - @backstage/plugin-search-backend-module-catalog@0.3.13-next.1
+ - @backstage/plugin-search-backend-module-techdocs@0.4.12-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-app-backend@0.5.12-next.0
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.0
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.0
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.1
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.1
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.0
+ - @backstage/plugin-devtools-backend@0.5.15-next.0
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.0
+ - @backstage/plugin-notifications-backend@0.6.3-next.0
+ - @backstage/plugin-permission-backend@0.7.10-next.0
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+ - @backstage/plugin-proxy-backend@0.6.11-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.1
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.1
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.0
+ - @backstage/plugin-search-backend-module-explore@0.3.12-next.0
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-signals-backend@0.3.13-next.0
+
+## techdocs-cli-embedded-app@0.2.118-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.1
+ - @backstage/cli@0.36.0-next.1
+ - @backstage/plugin-techdocs@1.17.1-next.1
+ - @backstage/plugin-catalog@1.34.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/frontend-defaults@0.4.1-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/test-utils@1.7.16-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-app-react@0.2.1-next.0
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
diff --git a/docs/releases/v1.49.0-next.2-changelog.md b/docs/releases/v1.49.0-next.2-changelog.md
new file mode 100644
index 0000000000..b6442fa4b0
--- /dev/null
+++ b/docs/releases/v1.49.0-next.2-changelog.md
@@ -0,0 +1,1826 @@
+# Release v1.49.0-next.2
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.49.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.49.0-next.2)
+
+## @backstage/plugin-catalog@2.0.0-next.2
+
+### Major Changes
+
+- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible.
+
+ **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`.
+
+ **Migration:**
+
+ Simply delete the obsolete `variant` and `gridSizes` props, e.g:
+
+ ```diff
+ -
+ +
+ ```
+
+ ```diff
+ -
+ +
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.5.9-next.2
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-scaffolder-common@2.0.0-next.2
+ - @backstage/plugin-search-react@1.10.5-next.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.1
+
+## @backstage/backend-app-api@1.6.0-next.1
+
+### Minor Changes
+
+- 545557a: Registration errors should be forwarded as BackendStartupResult
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+
+## @backstage/backend-plugin-api@1.8.0-next.1
+
+### Minor Changes
+
+- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.2
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-permission-node@0.10.11-next.1
+
+## @backstage/catalog-client@1.14.0-next.2
+
+### Minor Changes
+
+- 5d95e8e: Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error.
+
+## @backstage/cli@0.36.0-next.2
+
+### Minor Changes
+
+- d0f4cd2: Added new `auth` command group for authenticating the CLI with Backstage instances using OAuth 2.0 with a pre-registered client metadata document. Commands include `login`, `logout`, `list`, `show`, `print-token`, and `select` for managing multiple authenticated instances.
+
+### Patch Changes
+
+- a4e5902: Internal refactor of the CLI command registration
+- ff4a45a: Migrated remaining CLI command handlers from `commander` to `cleye` for argument parsing. Several camelCase CLI flags have been deprecated in favor of their kebab-case equivalents (e.g. `--successCache` → `--success-cache`). The old camelCase forms still work but will now log a deprecation warning. Please update any scripts or CI configurations to use the kebab-case versions.
+- 4a75544: Updated dependency `react-refresh` to `^0.18.0`.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.2
+ - @backstage/integration@2.0.0-next.2
+
+## @backstage/frontend-app-api@0.16.0-next.1
+
+### Minor Changes
+
+- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createSpecializedApp`. This flag had no effect and was a no-op, so no behavioral changes are expected.
+
+### Patch Changes
+
+- 0452d02: Add optional `description` field to plugin-level feature flags.
+- dab6c46: Added the `ExtensionFactoryMiddleware` type as a public export.
+- Updated dependencies
+ - @backstage/core-app-api@1.19.6-next.1
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/frontend-defaults@0.5.0-next.1
+
+## @backstage/frontend-defaults@0.5.0-next.1
+
+### Minor Changes
+
+- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createApp`. This flag had no effect and was a no-op, so no behavioral changes are expected.
+- 33de79d: **BREAKING**: Removed the deprecated `createPublicSignInApp` function. Use `createApp` from `@backstage/frontend-defaults` with `appModulePublicSignIn` from `@backstage/plugin-app/alpha` instead.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/frontend-app-api@0.16.0-next.1
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-app@0.4.1-next.2
+
+## @backstage/frontend-plugin-api@0.15.0-next.1
+
+### Minor Changes
+
+- 6573901: **BREAKING**: Removed the deprecated `AnyExtensionDataRef` type. Use `ExtensionDataRef` without type parameters instead.
+- a9440f0: **BREAKING**: Simplified the `ExtensionAttachTo` type to only support a single attachment target. The array form for attaching to multiple extension points has been removed. Also removed the deprecated `ExtensionAttachToSpec` type alias.
+
+### Patch Changes
+
+- 8a3a906: Deprecated `NavItemBlueprint`. Nav items are now automatically inferred from `PageBlueprint` extensions based on their `title` and `icon` params.
+- b15a685: Deprecated `withApis`, use the `withApis` export from `@backstage/core-compat-api` instead.
+- 0452d02: Add optional `description` field to plugin-level feature flags.
+- 1bec049: Fixed inconsistent `JSX.Element` type reference in the `DialogApiDialog.update` method signature.
+- 2c383b5: Deprecated `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory` in favor of the exports from `@backstage/plugin-app-react`.
+- dab6c46: Deprecated the `ExtensionFactoryMiddleware` type, which has been moved to `@backstage/frontend-app-api`.
+- d0206c4: Removed the deprecated `defaultPath` migration helper from `PageBlueprint` params.
+- edb872c: Renamed the `PageTab` type to `PageLayoutTab`. The old `PageTab` name is now a deprecated type alias.
+- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context.
+
+## @backstage/plugin-catalog-backend@3.5.0-next.2
+
+### Minor Changes
+
+- 5d95e8e: Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error.
+
+### Patch Changes
+
+- 7416e8b: Moved stitch queue concerns out of `refresh_state` and `final_entities` into a dedicated `stitch_queue` table with `entity_ref` as the primary key. The `stitch_ticket` is used for optimistic concurrency control. When a stitch completes successfully and the ticket hasn't changed, the corresponding row is deleted from the queue. The migration handles existing data and is fully reversible.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/backend-openapi-utils@0.6.7-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+ - @backstage/plugin-permission-node@0.10.11-next.1
+
+## @backstage/plugin-catalog-graph@0.6.0-next.2
+
+### Minor Changes
+
+- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`.
+
+ - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows.
+ - `CatalogGraphCard`: Removed `variant` prop.
+ - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`.
+ - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`.
+ - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`.
+
+ **Migration:**
+
+ ```diff
+ -
+ +
+ ```
+
+ ```diff
+ -
+ +
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-catalog-react@2.1.0-next.2
+
+### Minor Changes
+
+- d14b6e0: Exported `useEntityRefLink` hook that returns a function for generating entity page URLs from entity references.
+- c6080eb: Added `EntityInfoCard` component to `@backstage/plugin-catalog-react` as a BUI-based card wrapper for entity page cards.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.5.9-next.2
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/frontend-test-utils@0.5.1-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-notifications-backend-module-slack@0.4.0-next.1
+
+### Minor Changes
+
+- cd62d78: **BREAKING**: Only send direct messages to user entity recipients. Notifications sent to non-user entities no longer send Slack direct messages to resolved users.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-notifications-node@0.2.24-next.2
+
+## @backstage/plugin-org@0.7.0-next.2
+
+### Minor Changes
+
+- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`.
+
+ - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows.
+ - `CatalogGraphCard`: Removed `variant` prop.
+ - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`.
+ - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`.
+ - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`.
+
+ **Migration:**
+
+ ```diff
+ -
+ +
+ ```
+
+ ```diff
+ -
+ +
+ ```
+
+- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible.
+
+ **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`.
+
+ **Migration:**
+
+ Simply delete the obsolete `variant` and `gridSizes` props, e.g:
+
+ ```diff
+ -
+ +
+ ```
+
+ ```diff
+ -
+ +
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-scaffolder-backend@3.2.0-next.2
+
+### Minor Changes
+
+- e8736ea: Added secrets schema validation for task creation, retry, and dry-run endpoints. When a template defines `spec.secrets.schema`, the API validates provided secrets against the schema and returns a `400` error if validation fails.
+
+### Patch Changes
+
+- 30ff981: Fixed a security vulnerability where secrets could bypass log redaction when transformed through Nunjucks filters in scaffolder templates.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-common@2.0.0-next.2
+ - @backstage/backend-openapi-utils@0.6.7-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+ - @backstage/plugin-permission-node@0.10.11-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-common@2.0.0-next.2
+
+### Minor Changes
+
+- e8736ea: Added an optional `secrets` field to `TemplateEntityV1beta3` for configuring secrets validation. The schema for validating secrets is defined under `secrets.schema` as a JSON Schema object.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@2.0.0-next.2
+
+## @backstage/plugin-scaffolder-react@1.20.0-next.2
+
+### Minor Changes
+
+- 470f72d: The `LogViewer` component from `@backstage/core-components` now supports downloading logs if a callback is passed to `onDownloadLogs`
+
+### Patch Changes
+
+- bd31ddd: Updated dependency `flatted` to `3.3.4`.
+- Updated dependencies
+ - @backstage/frontend-test-utils@0.5.1-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-scaffolder-common@2.0.0-next.2
+
+## @backstage/backend-defaults@0.16.0-next.2
+
+### Patch Changes
+
+- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available.
+- 5fcbef2: Updated dependency `express-rate-limit` to `^8.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/backend-app-api@1.6.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+ - @backstage/plugin-permission-node@0.10.11-next.1
+
+## @backstage/backend-dynamic-feature-service@0.8.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-defaults@0.16.0-next.2
+ - @backstage/cli-common@0.2.0-next.2
+ - @backstage/plugin-catalog-backend@3.5.0-next.2
+ - @backstage/backend-openapi-utils@0.6.7-next.1
+ - @backstage/plugin-app-node@0.1.43-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-events-backend@0.6.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+ - @backstage/plugin-permission-node@0.10.11-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+ - @backstage/plugin-search-backend-node@1.4.2-next.1
+
+## @backstage/backend-openapi-utils@0.6.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+
+## @backstage/backend-test-utils@1.11.1-next.2
+
+### Patch Changes
+
+- 164711a: Added `cancelTask` to `MockSchedulerService` and mock scheduler service factory.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-defaults@0.16.0-next.2
+ - @backstage/backend-app-api@1.6.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/cli-common@0.2.0-next.2
+
+### Patch Changes
+
+- 9361965: Fixed `runCheck` to ignore stdio of the spawned process, preventing unwanted output from leaking to the terminal.
+
+## @backstage/core-app-api@1.19.6-next.1
+
+### Patch Changes
+
+- 12d8afe: Added `BUIProvider` from `@backstage/ui` to the app shell provider tree, enabling BUI components to fire analytics events through the Backstage analytics system.
+- 0452d02: Add optional `description` field to plugin-level feature flags.
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/core-plugin-api@1.12.4-next.1
+
+## @backstage/core-compat-api@0.5.9-next.2
+
+### Patch Changes
+
+- b15a685: Added `withApis`, which is a Higher-Order Component for providing APIs as props to a component via `useApiHolder`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/plugin-app-react@0.2.1-next.1
+
+## @backstage/core-components@0.18.8-next.1
+
+### Patch Changes
+
+- 8b1a847: Fixed Table component layout when both `filters` and `title` props are used together. The filter controls now use a dedicated CSS class (`filterControls`) instead of incorrectly reusing the root container class.
+- 470f72d: The `LogViewer` component from `@backstage/core-components` now supports downloading logs if a callback is passed to `onDownloadLogs`
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4-next.1
+
+## @backstage/core-plugin-api@1.12.4-next.1
+
+### Patch Changes
+
+- 0452d02: Add optional `description` field to plugin-level feature flags.
+- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+
+## @backstage/create-app@0.7.10-next.2
+
+### Patch Changes
+
+- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`.
+
+ - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows.
+ - `CatalogGraphCard`: Removed `variant` prop.
+ - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`.
+ - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`.
+ - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`.
+
+ **Migration:**
+
+ ```diff
+ -
+ +
+ ```
+
+ ```diff
+ -
+ +
+ ```
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.2
+
+## @backstage/frontend-dynamic-feature-loader@0.1.10-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+
+## @backstage/frontend-test-utils@0.5.1-next.2
+
+### Patch Changes
+
+- b56f573: Deprecated standalone mock API exports in favor of the `mockApis` namespace. This includes the mock classes (`MockAlertApi`, `MockAnalyticsApi`, `MockConfigApi`, `MockErrorApi`, `MockFetchApi`, `MockFeatureFlagsApi`, `MockPermissionApi`, `MockStorageApi`, `MockTranslationApi`), their option types (`MockErrorApiOptions`, `MockFeatureFlagsApiOptions`), and the `ErrorWithContext` type. `MockFetchApiOptions` is kept as a non-deprecated export. Use the `mockApis` namespace instead, for example `mockApis.alert()` or `mockApis.alert.mock()`.
+- Updated dependencies
+ - @backstage/core-app-api@1.19.6-next.1
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/frontend-app-api@0.16.0-next.1
+ - @backstage/plugin-app-react@0.2.1-next.1
+ - @backstage/plugin-app@0.4.1-next.2
+
+## @backstage/integration@2.0.0-next.2
+
+### Patch Changes
+
+- 1513a0b: Fixed a security vulnerability where path traversal sequences in SCM URLs could be used to access unintended API endpoints using server-side integration credentials.
+
+## @backstage/repo-tools@0.17.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/cli-common@0.2.0-next.2
+
+## @backstage/ui@0.13.0-next.2
+
+### Patch Changes
+
+- db92751: Added interactive support to the `Card` component. Pass `onPress` to make the entire card surface pressable, or `href` to make it navigate to a URL. A transparent overlay handles the interaction while nested buttons and links remain independently clickable.
+
+- 12d8afe: Added analytics capabilities to the component library. Components with navigation behavior (Link, ButtonLink, Tab, MenuItem, Tag, Row) now fire analytics events on click when a `BUIProvider` is present.
+
+ New exports: `BUIProvider`, `useAnalytics`, `getNodeText`, and associated types (`AnalyticsTracker`, `UseAnalyticsFn`, `BUIProviderProps`, `AnalyticsEventAttributes`).
+
+ Components with analytics support now accept a `noTrack` prop to suppress event firing.
+
+ **Affected components:** Link, ButtonLink, Tab, MenuItem, Tag, Row
+
+- 430d5ed: Fixed interactive cards so that CardBody can scroll when the card has a constrained height. Previously, the overlay element blocked scroll events.
+
+ **Affected components:** Card
+
+- ad7c883: Deprecated the `HeaderPage` component name in favor of `Header`. The old `HeaderPage`, `HeaderPageProps`, `HeaderPageOwnProps`, `HeaderPageBreadcrumb`, and `HeaderPageDefinition` exports are still available as deprecated aliases.
+
+- f42f4cc: Fixed Table column headers overflowing and wrapping when there is not enough space. Headers now truncate with ellipsis instead.
+
+ **Affected components:** Table
+
+- fbd5c5a: Fixed Table rows showing a pointer cursor when not interactive. Rows now only show `cursor: pointer` when they have an `href`, are selectable, or are pressable.
+
+ **Affected components:** Table
+
+- 7960d54: Added support for native HTML div attributes on the `Flex`, `Grid`, and `Grid.Item` components.
+
+ **Affected components:** Flex, Grid, Grid.Item
+
+- 12d8afe: Fixed MenuItem `onAction` prop ordering so user-provided `onAction` handlers are chained rather than silently overwritten.
+
+- bb66b86: The `Select` trigger now automatically adapts its background colour based on the parent background context.
+
+ **Affected components:** Select
+
+- 934ac03: `SearchField` and `TextField` now automatically adapt their background color based on the parent bg context, stepping up one neutral level (e.g. neutral-1 → neutral-2) when placed on a neutral background. `TextField` also gains a focus ring using the `--bui-ring` token.
+
+ **Affected components:** SearchField, TextField
+
+## @backstage/plugin-api-docs@0.13.5-next.2
+
+### Patch Changes
+
+- ca277ef: Updated dependency `graphiql` to `3.9.0` to address security vulnerability in `markdown-it` package.
+ Updated dependency `@graphiql/react` to `0.29.0` to match the version used by `graphiql`.
+ Moved dependency `graphql-config` to `devDependencies` as it is needed only for types.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-catalog@2.0.0-next.2
+
+## @backstage/plugin-app@0.4.1-next.2
+
+### Patch Changes
+
+- 12d8afe: Added `BUIProvider` from `@backstage/ui` to the app root, enabling BUI components to fire analytics events through the Backstage analytics system.
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-app-react@0.2.1-next.1
+
+## @backstage/plugin-app-backend@0.5.12-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-app-node@0.1.43-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-app-node@0.1.43-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+
+## @backstage/plugin-app-react@0.2.1-next.1
+
+### Patch Changes
+
+- 2c383b5: Added `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory`, migrated from `@backstage/frontend-plugin-api`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+
+## @backstage/plugin-app-visualizer@0.2.1-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-auth@0.1.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-auth-backend@0.27.1-next.2
+
+### Patch Changes
+
+- d0f4cd2: Added optional client metadata document endpoint at `/.well-known/oauth-client/cli.json` relative to the auth backend base URL for CLI authentication. Enabled when `auth.experimentalClientIdMetadataDocuments.enabled` is set to `true`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-auth0-provider@0.3.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-backend@0.27.1-next.2
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-gitlab-provider@0.4.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-google-provider@0.3.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-oidc-provider@0.4.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-backend@0.27.1-next.2
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-okta-provider@0.2.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.12-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.12-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-auth-node@0.6.14-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+
+## @backstage/plugin-catalog-backend-module-aws@0.4.21-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-defaults@0.16.0-next.2
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-backend-module-azure@0.3.15-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-openapi-utils@0.6.7-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.9-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.9-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-catalog-backend-module-gcp@0.3.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-backend-module-gerrit@0.3.12-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-backend-module-gitea@0.1.10-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-backend-module-github@0.13.0-next.2
+
+### Patch Changes
+
+- 106d1b2: Added a `defaultUserTransformer.useVerifiedEmails` config option for the `githubOrg` provider. When set to `true`, the default user transformer prefers organization verified domain emails over the user's public GitHub email. Defaults to `false`, which uses only the public GitHub email.
+
+ This option has no effect when a custom user transformer is set via the `githubOrgEntityProviderTransformsExtensionPoint`.
+
+ ```yaml
+ catalog:
+ providers:
+ githubOrg:
+ production:
+ githubUrl: https://github.com
+ orgs:
+ - my-org
+ defaultUserTransformer:
+ useVerifiedEmails: true
+ ```
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-catalog-backend-module-github-org@0.3.20-next.2
+
+### Patch Changes
+
+- 106d1b2: Added a `defaultUserTransformer.useVerifiedEmails` config option for the `githubOrg` provider. When set to `true`, the default user transformer prefers organization verified domain emails over the user's public GitHub email. Defaults to `false`, which uses only the public GitHub email.
+
+ This option has no effect when a custom user transformer is set via the `githubOrgEntityProviderTransformsExtensionPoint`.
+
+ ```yaml
+ catalog:
+ providers:
+ githubOrg:
+ production:
+ githubUrl: https://github.com
+ orgs:
+ - my-org
+ defaultUserTransformer:
+ useVerifiedEmails: true
+ ```
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-catalog-backend-module-github@0.13.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.8.1-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-defaults@0.16.0-next.2
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-catalog-backend-module-gitlab@0.8.1-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.10-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-defaults@0.16.0-next.2
+ - @backstage/plugin-catalog-backend@3.5.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-catalog-backend-module-ldap@0.12.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-backend-module-logs@0.1.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-catalog-backend@3.5.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-catalog-backend-module-msgraph@0.9.1-next.1
+
+### Patch Changes
+
+- 97eaecf: Fixed scheduler task remaining stuck in running state after pod termination by propagating AbortSignal into MicrosoftGraphOrgEntityProvider.read()
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-backend-module-puppetdb@0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-scaffolder-common@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-catalog-import@0.13.11-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-catalog-node@2.1.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-test-utils@1.11.1-next.2
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-permission-node@0.10.11-next.1
+
+## @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.5.9-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-devtools-react@0.1.2-next.1
+
+## @backstage/plugin-devtools@0.1.37-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.5.9-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-devtools-react@0.1.2-next.1
+
+## @backstage/plugin-devtools-backend@0.5.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/cli-common@0.2.0-next.2
+ - @backstage/plugin-permission-node@0.10.11-next.1
+
+## @backstage/plugin-devtools-react@0.1.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+
+## @backstage/plugin-events-backend@0.6.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-openapi-utils@0.6.7-next.1
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-events-backend-module-aws-sqs@0.4.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-events-backend-module-azure@0.2.29-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.29-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-events-backend-module-bitbucket-server@0.1.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-events-backend-module-gerrit@0.2.29-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-events-backend-module-github@0.4.10-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-events-backend-module-gitlab@0.3.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-events-backend-module-kafka@0.3.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-events-node@0.4.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+
+## @backstage/plugin-gateway-backend@1.1.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+
+## @backstage/plugin-home@0.9.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.5.9-next.2
+ - @backstage/core-app-api@1.19.6-next.1
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-home-react@0.1.36-next.1
+
+## @backstage/plugin-home-react@0.1.36-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.5.9-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-kubernetes@0.12.17-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-kubernetes-backend@0.21.2-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-kubernetes-node@0.4.2-next.1
+ - @backstage/plugin-permission-node@0.10.11-next.1
+
+## @backstage/plugin-kubernetes-node@0.4.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+
+## @backstage/plugin-mcp-actions-backend@0.1.10-next.2
+
+### Patch Changes
+
+- c74b697: Added support for splitting MCP actions into multiple servers via `mcpActions.servers` configuration. Each server gets its own endpoint at `/api/mcp-actions/v1/{key}` with actions scoped using include/exclude filter rules. Tool names are now namespaced with the plugin ID by default, configurable via `mcpActions.namespacedToolNames`. When `mcpActions.servers` is not configured, the plugin continues to serve a single server at `/api/mcp-actions/v1`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-mui-to-bui@0.2.5-next.2
+
+### Patch Changes
+
+- ad7c883: Updated the MUI to BUI theme converter page to use the renamed `Header` component from `@backstage/ui`.
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+
+## @backstage/plugin-notifications@0.5.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-notifications-backend@0.6.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-notifications-node@0.2.24-next.2
+ - @backstage/plugin-signals-node@0.1.29-next.1
+
+## @backstage/plugin-notifications-backend-module-email@0.3.19-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-notifications-node@0.2.24-next.2
+
+## @backstage/plugin-notifications-node@0.2.24-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-signals-node@0.1.29-next.1
+
+## @backstage/plugin-permission-backend@0.7.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-permission-node@0.10.11-next.1
+
+## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-permission-node@0.10.11-next.1
+
+## @backstage/plugin-permission-node@0.10.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+
+## @backstage/plugin-proxy-backend@0.6.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-proxy-node@0.1.13-next.1
+
+## @backstage/plugin-proxy-node@0.1.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+
+## @backstage/plugin-scaffolder@1.35.5-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-scaffolder-react@1.20.0-next.2
+ - @backstage/plugin-scaffolder-common@2.0.0-next.2
+ - @backstage/plugin-techdocs-react@1.3.9-next.1
+
+## @backstage/plugin-scaffolder-backend-module-azure@0.2.19-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.4-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.19-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.19-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.21-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-defaults@0.16.0-next.2
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-gcp@0.2.19-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.19-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-gitea@0.2.19-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.2
+
+### Patch Changes
+
+- b2591f6: Fixed environment `waitTime` description incorrectly asking for milliseconds instead of minutes.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.4-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-notifications-node@0.2.24-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-rails@0.5.19-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-sentry@0.3.2-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.20-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.2
+
+## @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-test-utils@1.11.1-next.2
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-scaffolder-common@2.0.0-next.2
+
+## @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-test-utils@1.11.1-next.2
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+
+## @backstage/plugin-search@1.6.2-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-search-react@1.10.5-next.1
+
+## @backstage/plugin-search-backend@2.1.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-openapi-utils@0.6.7-next.1
+ - @backstage/plugin-permission-node@0.10.11-next.1
+ - @backstage/plugin-search-backend-node@1.4.2-next.1
+
+## @backstage/plugin-search-backend-module-catalog@0.3.13-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-search-backend-node@1.4.2-next.1
+
+## @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-search-backend-node@1.4.2-next.1
+
+## @backstage/plugin-search-backend-module-explore@0.3.12-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-search-backend-node@1.4.2-next.1
+
+## @backstage/plugin-search-backend-module-pg@0.5.53-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-search-backend-node@1.4.2-next.1
+
+## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-search-backend-node@1.4.2-next.1
+
+## @backstage/plugin-search-backend-module-techdocs@0.4.12-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-techdocs-node@1.14.4-next.2
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+ - @backstage/plugin-search-backend-node@1.4.2-next.1
+
+## @backstage/plugin-search-backend-node@1.4.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+
+## @backstage/plugin-search-react@1.10.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-signals@0.0.29-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-signals-backend@0.3.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-events-node@0.4.20-next.1
+ - @backstage/plugin-signals-node@0.1.29-next.1
+
+## @backstage/plugin-signals-node@0.1.29-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## @backstage/plugin-techdocs@1.17.1-next.2
+
+### Patch Changes
+
+- 9795d30: chore(deps): bump `dompurify` from 3.3.1 to 3.3.2
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-search-react@1.10.5-next.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.1
+
+## @backstage/plugin-techdocs-addons-test-utils@2.0.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.6-next.1
+ - @backstage/plugin-techdocs@1.17.1-next.2
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/plugin-catalog@2.0.0-next.2
+ - @backstage/plugin-search-react@1.10.5-next.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.1
+
+## @backstage/plugin-techdocs-backend@2.1.6-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-techdocs-node@1.14.4-next.2
+ - @backstage/catalog-client@1.14.0-next.2
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/plugin-catalog-node@2.1.0-next.2
+
+## @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.1
+
+## @backstage/plugin-techdocs-node@1.14.4-next.2
+
+### Patch Changes
+
+- e96f6d9: Removed `INHERIT` from the `ALLOWED_MKDOCS_KEYS` set to address a security concern with MkDocs configuration inheritance.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+
+## @backstage/plugin-techdocs-react@1.3.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-user-settings@0.9.1-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.6-next.1
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+
+## @backstage/plugin-user-settings-backend@0.4.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-signals-node@0.1.29-next.1
+
+## example-app@0.0.33-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.5.9-next.2
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/cli@0.36.0-next.2
+ - @backstage/core-app-api@1.19.6-next.1
+ - @backstage/plugin-techdocs@1.17.1-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/frontend-app-api@0.16.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-org@0.7.0-next.2
+ - @backstage/plugin-catalog-graph@0.6.0-next.2
+ - @backstage/plugin-app-react@0.2.1-next.1
+ - @backstage/plugin-app@0.4.1-next.2
+ - @backstage/frontend-defaults@0.5.0-next.1
+ - @backstage/plugin-scaffolder-react@1.20.0-next.2
+ - @backstage/plugin-api-docs@0.13.5-next.2
+ - @backstage/plugin-catalog@2.0.0-next.2
+ - @backstage/plugin-app-visualizer@0.2.1-next.2
+ - @backstage/plugin-auth@0.1.6-next.1
+ - @backstage/plugin-catalog-import@0.13.11-next.2
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.1
+ - @backstage/plugin-devtools@0.1.37-next.2
+ - @backstage/plugin-home@0.9.3-next.2
+ - @backstage/plugin-home-react@0.1.36-next.1
+ - @backstage/plugin-kubernetes@0.12.17-next.2
+ - @backstage/plugin-notifications@0.5.15-next.1
+ - @backstage/plugin-scaffolder@1.35.5-next.2
+ - @backstage/plugin-search@1.6.2-next.2
+ - @backstage/plugin-search-react@1.10.5-next.1
+ - @backstage/plugin-signals@0.0.29-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.2
+ - @backstage/plugin-techdocs-react@1.3.9-next.1
+ - @backstage/plugin-user-settings@0.9.1-next.2
+
+## app-example-plugin@0.0.33-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-components@0.18.8-next.1
+
+## example-app-legacy@0.2.119-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/cli@0.36.0-next.2
+ - @backstage/core-app-api@1.19.6-next.1
+ - @backstage/plugin-techdocs@1.17.1-next.2
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/frontend-app-api@0.16.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-org@0.7.0-next.2
+ - @backstage/plugin-catalog-graph@0.6.0-next.2
+ - @backstage/plugin-mui-to-bui@0.2.5-next.2
+ - @backstage/plugin-scaffolder-react@1.20.0-next.2
+ - @backstage/plugin-api-docs@0.13.5-next.2
+ - @backstage/plugin-catalog@2.0.0-next.2
+ - @backstage/plugin-catalog-import@0.13.11-next.2
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.1
+ - @backstage/plugin-devtools@0.1.37-next.2
+ - @backstage/plugin-home@0.9.3-next.2
+ - @backstage/plugin-home-react@0.1.36-next.1
+ - @backstage/plugin-kubernetes@0.12.17-next.2
+ - @backstage/plugin-notifications@0.5.15-next.1
+ - @backstage/plugin-scaffolder@1.35.5-next.2
+ - @backstage/plugin-search@1.6.2-next.2
+ - @backstage/plugin-search-react@1.10.5-next.1
+ - @backstage/plugin-signals@0.0.29-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.2
+ - @backstage/plugin-techdocs-react@1.3.9-next.1
+ - @backstage/plugin-user-settings@0.9.1-next.2
+
+## example-backend@0.0.48-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-defaults@0.16.0-next.2
+ - @backstage/plugin-auth-backend@0.27.1-next.2
+ - @backstage/plugin-catalog-backend@3.5.0-next.2
+ - @backstage/plugin-scaffolder-backend@3.2.0-next.2
+ - @backstage/plugin-mcp-actions-backend@0.1.10-next.2
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.2
+ - @backstage/plugin-app-backend@0.5.12-next.1
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.1
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.1
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.1
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.2
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.2
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.1
+ - @backstage/plugin-devtools-backend@0.5.15-next.1
+ - @backstage/plugin-events-backend@0.6.0-next.2
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.1
+ - @backstage/plugin-kubernetes-backend@0.21.2-next.2
+ - @backstage/plugin-notifications-backend@0.6.3-next.1
+ - @backstage/plugin-permission-backend@0.7.10-next.1
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.1
+ - @backstage/plugin-permission-node@0.10.11-next.1
+ - @backstage/plugin-proxy-backend@0.6.11-next.1
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.2
+ - @backstage/plugin-search-backend@2.1.0-next.2
+ - @backstage/plugin-search-backend-module-catalog@0.3.13-next.2
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.1
+ - @backstage/plugin-search-backend-module-explore@0.3.12-next.1
+ - @backstage/plugin-search-backend-module-techdocs@0.4.12-next.2
+ - @backstage/plugin-search-backend-node@1.4.2-next.1
+ - @backstage/plugin-signals-backend@0.3.13-next.1
+ - @backstage/plugin-techdocs-backend@2.1.6-next.2
+
+## @internal/frontend@0.0.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+
+## @internal/scaffolder@0.0.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/plugin-scaffolder-react@1.20.0-next.2
+
+## techdocs-cli-embedded-app@0.2.118-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/cli@0.36.0-next.2
+ - @backstage/core-app-api@1.19.6-next.1
+ - @backstage/plugin-techdocs@1.17.1-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-app-react@0.2.1-next.1
+ - @backstage/frontend-defaults@0.5.0-next.1
+ - @backstage/plugin-catalog@2.0.0-next.2
+ - @backstage/plugin-techdocs-react@1.3.9-next.1
+
+## @internal/plugin-todo-list-backend@1.0.48-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
diff --git a/docs/releases/v1.49.0.md b/docs/releases/v1.49.0.md
new file mode 100644
index 0000000000..83c2e69f3d
--- /dev/null
+++ b/docs/releases/v1.49.0.md
@@ -0,0 +1,233 @@
+---
+id: v1.49.0
+title: v1.49.0
+description: Backstage Release v1.49.0
+---
+
+These are the release notes for the v1.49.0 release of [Backstage](https://backstage.io/).
+
+A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done.
+
+## Highlights
+
+### New Frontend System: 1.0 Release Candidate
+
+This release marks the release candidate for version 1.0 of the New Frontend System. Newly created Backstage apps now use the New Frontend System by default, the `--next` flag for `create-app` has been replaced with a `--legacy` flag for apps that still want to use the old system. The `yarn new` templates have been updated accordingly, with auto-detection that presents the correct plugin templates based on which frontend system your app uses.
+
+On the framework side, this release includes significant stabilization of the API surface. Several deprecated types and options have been removed, `PluginWrapperApi` has graduated from alpha to stable, and a new builder pattern for `createApiRef` has been introduced. A full list of API changes can be found in the [`@backstage/frontend-plugin-api` changelog](https://github.com/backstage/backstage/blob/master/packages/frontend-plugin-api/CHANGELOG.md).
+
+Plugin pages across the supported plugins have been migrated to the Backstage UI `Header` component, aligning the plugin header and page-level navigation patterns across the New Frontend System. A new `@backstage/frontend-dev-utils` package with a `createDevApp` helper simplifies the setup of plugin development apps.
+
+Documentation has been overhauled across the board to cover and prioritize the New Frontend System, aligning installation guidance across plugin READMEs, closing gaps with the existing "Plugins" section that is now marked as legacy, and many other changes.
+
+### **BREAKING**: Backstage UI updates
+
+There are several new additions in Backstage UI, for example new `SearchAutocomplete` and `SearchAutocompleteItem` components for building accessible search-with-results patterns. New `List` and `ListRow` components built on React Aria's GridList for interactive lists with icons, descriptions, and selection. A `virtualized` prop added to the `Table` for rendering large datasets. And much more.
+
+There are also several breaking changes to note:
+
+- **Checkbox**: Removed the redundant `selected` and `indeterminate` props. Use `isSelected` and `isIndeterminate` instead, which are the standard React Aria props.
+- **Header CSS classes**: Renamed internal CSS classes to match the `Header` component name (e.g. `bui-HeaderPage` → `bui-Header`).
+- **BUIProvider routing**: Centralized client-side routing in `BUIProvider`. Components like Link, ButtonLink, Tabs, Menu, and Table now require a `BUIProvider` inside a React Router context for client-side navigation to work.
+- **Data attributes**: Data attributes rendered by components are now always lowercase, affecting CSS selectors targeting camelCase attributes.
+- **Removed deprecated types**: `ComponentDefinition`, `ClassNamesMap`, `DataAttributeValues`, and `DataAttributesMap` have been removed from the public API.
+- **Popover token**: Removed `--bui-bg-popover` CSS token. Use `--bui-bg-neutral-1` or `--bui-bg-app` instead.
+- **Neutral background**: Simplified the neutral background prop API. Explicit `neutral-1`, `neutral-2`, `neutral-3`, and `neutral-auto` values have been replaced by a single `neutral` value that auto-increments from the parent context.
+
+Check the [BUI Changelog](https://ui.backstage.io/changelog) for more details.
+
+### **BREAKING**: Entity cards migrated to Backstage UI
+
+The `@backstage/plugin-catalog` package has received a major version bump as `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` have been migrated from MUI to BUI. The `variant` prop has been removed from these cards, and the `gridSizes` prop has been removed from `AboutField`.
+
+Additionally, entity table cards like `HasComponentsCard`, `HasResourcesCard`, `HasSubcomponentsCard`, and others now render with BUI when no legacy props are passed. The old `variant`, `columns`, and `tableOptions` props are deprecated but still supported — passing them triggers the legacy MUI rendering. `RelatedEntitiesCard` is deprecated in favor of the new `EntityRelationCard` from `@backstage/plugin-catalog-react/alpha`.
+
+`MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` from `@backstage/plugin-org` and `@backstage/plugin-catalog-graph` have also been migrated to BUI card primitives via `EntityInfoCard`. The `variant` prop has been removed from these cards.
+
+```diff
+-
++
+```
+
+### **BREAKING**: Deprecated Bitbucket and legacy integrations removed
+
+Several long-deprecated integrations have been cleaned up across the project.
+
+In `@backstage/integration`, the deprecated `bitbucket` config object, Azure DevOps `token`/`credential` fields, `parseGerritGitilesUrl`, `buildGerritGitilesArchiveUrl`, and `getGitHubRequestOptions` have all been removed. Use `bitbucketCloud`/`bitbucketServer`, the `credentials` array, `parseGitilesUrlRef`/`buildGerritGitilesArchiveUrlFromLocation`, respectively.
+
+In `@backstage/backend-defaults`, the deprecated `BitbucketUrlReader` has been removed. Use `BitbucketCloudUrlReader` or `BitbucketServerUrlReader` instead.
+
+In `@backstage/plugin-scaffolder-node`, the deprecated `bitbucket` integration has been removed from `parseRepoUrl`. It now uses `bitbucketCloud` or `bitbucketServer`.
+
+In `@backstage/plugin-scaffolder-common`, the deprecated `bitbucket` integration has been removed from `ScaffolderClient`.
+
+### **BREAKING PRODUCERS**: `ScaffolderApi` methods now required
+
+The `retry`, `listTasks`, `listTemplatingExtensions`, `dryRun`, and `autocomplete` methods are now required on the `ScaffolderApi` interface. Implementations must provide all of these methods.
+
+### **BREAKING**: Slack notifications direct messages
+
+The `@backstage/plugin-notifications-backend-module-slack` module now only sends direct messages to user entity recipients. Notifications sent to non-user entities no longer trigger Slack direct messages.
+
+Contributed by [@kaidubauskas-dd](https://github.com/kaidubauskas-dd) in [#32619](https://github.com/backstage/backstage/pull/32619).
+
+### AI and MCP improvements
+
+This release significantly expands the set of built-in actions available for AI agents and MCP consumers. New actions include `who-am-i` for identifying the authenticated user, `query-catalog-entities` for predicate-based catalog queries, `list-scaffolder-actions` for discovering available scaffolder actions with their schemas, `list-scaffolder-tasks` for querying scaffolder tasks with ownership filtering, and `get-scaffolder-task-logs` for retrieving log events from running tasks.
+
+The MCP actions backend now supports splitting actions into multiple servers via `mcpActions.servers` configuration, with include/exclude filter rules per server. Added `mcpActions.name` and `mcpActions.description` config options for customizing server identity, and tool names now use dot separators to align with the MCP spec convention. Error forwarding has also been improved so that known errors preserve their original status codes.
+
+A new `actions` CLI module provides `actions list`, `actions execute`, and `actions sources` commands for interacting with the actions registry directly from the terminal.
+
+See the [AI documentation](https://backstage.io/docs/ai/generated-index) for more information on using Backstage with AI agents.
+
+### CLI Module System
+
+The Backstage CLI has been refactored into an extensible module system. CLI commands are now provided by individual `cli-module` packages that are discovered automatically from your project's dependencies. This release includes the initial set of CLI modules:
+
+- `@backstage/cli-module-auth`
+- `@backstage/cli-module-actions`
+- `@backstage/cli-module-build`
+- `@backstage/cli-module-config`
+- `@backstage/cli-module-github`
+- `@backstage/cli-module-info`
+- `@backstage/cli-module-lint`
+- `@backstage/cli-module-maintenance`
+- `@backstage/cli-module-migrate`
+- `@backstage/cli-module-new`
+- `@backstage/cli-module-test-jest`
+- `@backstage/cli-module-translations`
+
+If no CLI modules are found in project dependencies, the CLI falls back to the built-in set with a deprecation warning. To prepare for the removal of this fallback, add `@backstage/cli-defaults` as a `devDependency`:
+
+```json
+{
+ "devDependencies": {
+ "@backstage/cli-defaults": "backstage:^"
+ }
+}
+```
+
+A new `createCliModule` API in `@backstage/cli-node` enables third-party CLI modules, and a `cli-module` template is available via `yarn new`.
+
+### CLI Auth Commands
+
+A new `auth` command group has been added for authenticating the CLI with Backstage instances using OAuth 2.0. Commands include `login`, `logout`, `list`, `show`, `print-token`, and `select` for managing multiple authenticated instances. An API for interacting with the selected and specific instance and the authentication sessions towards them has been added to `@backstage/cli-node` grouped under the `CliAuth` export.
+
+### `package bundle` Command
+
+A new `package bundle` command has been added to `@backstage/cli-module-build` for creating self-contained plugin bundles for dynamic loading, to be used by the `backend-dynamic-feature-service`.
+
+Contributed by [@davidfestal](https://github.com/davidfestal) in [#33125](https://github.com/backstage/backstage/pull/33125).
+
+### Predicate-based catalog entity filtering
+
+A powerful new predicate-based filtering system has been added to the catalog. The catalog client's `queryEntities()`, `getEntitiesByRefs()`, and `getEntityFacets()` methods now support a `query` field with operators like `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and `$contains` for expressive entity queries.
+
+These are exposed through new POST endpoints with full pagination and permission enforcement support. A new `query-catalog-entities` action has been added for predicate-based catalog queries via the actions system.
+
+### Actions: Permissions and user invocation
+
+Backend actions now support permission-based visibility. An optional `visibilityPermission` field has been added to `ActionsRegistryActionOptions`, allowing actions to declare a permission that controls access.
+
+The actions registry invoke endpoint now also accepts direct user credentials in addition to service principals, enabling CLI and other direct user clients to invoke actions.
+
+### New `ToastApi` for rich notifications
+
+A new `ToastApi` has been introduced in the New Frontend System for displaying rich toast notifications. Toasts support titles, descriptions, custom timeouts, action links, and status variants (neutral, info, success, warning, danger). They can also be dismissed programmatically.
+
+```typescript
+const toastApi = useApi(toastApiRef);
+
+toastApi.post({
+ title: 'Entity saved',
+ description: 'Your changes have been saved successfully.',
+ status: 'success',
+ timeout: 5000,
+ links: [{ label: 'View entity', href: '/catalog/entity' }],
+});
+```
+
+The `ToastDisplay` component subscribes to both `ToastApi` and the existing `AlertApi`, providing a migration path. The `AlertApi`, `AlertMessage`, `alertApiRef`, and `AlertApiForwarder` have been deprecated in favor of `ToastApi`.
+
+### Template secrets schema validation
+
+Templates can now define a `secrets.schema` field in `TemplateEntityV1beta3` for validating secrets provided during task creation. The scaffolder backend validates secrets against the schema and returns a `400` error if validation fails.
+
+### Scheduler `cancelTask`
+
+A new `cancelTask` method has been added to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. A corresponding cancel button has been added to the DevTools scheduled tasks UI.
+
+### Entity page group aliases and content ordering
+
+Entity page groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `defaultContentOrder` option controls how content items within each group are sorted, with support for both page-level and per-group overrides.
+
+### `LogViewer` download support
+
+The `LogViewer` component now supports downloading logs via an `onDownloadLogs` callback.
+
+Contributed by [@0xts](https://github.com/0xts) in [#31989](https://github.com/backstage/backstage/pull/31989).
+
+### Translation refs promoted to stable
+
+Translation refs across 18 packages have been promoted from alpha-only exports to the stable entry points. If you are importing translation refs from `/alpha` entry points, for example `import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha'`, you can now import them directly from the main package entry point instead. The alpha re-exports are still available but deprecated.
+
+### Permission batching
+
+Permission checks made in the same tick are now batched into a single call to the permission backend, improving performance for pages with many permission-gated elements.
+
+### GitHub org verified email preference
+
+The default user transformer for `githubOrg` now prefers organization verified domain emails over the user's public GitHub email. A new `defaultUserTransformer.useVerifiedEmails` config option controls this behavior for the config-based provider.
+
+### GitLab integration improvements
+
+Added configurable throttling and retry mechanism for GitLab integration.
+
+Contributed by [@JohannesWill](https://github.com/JohannesWill) in [#30804](https://github.com/backstage/backstage/pull/30804).
+
+Added a new `gitlab:group:access` scaffolder action for managing GitLab group memberships.
+
+Contributed by [@Jellyfrog](https://github.com/Jellyfrog) in [#32528](https://github.com/backstage/backstage/pull/32528).
+
+Added `maskedAndHidden` option to `gitlab:projectVariable:create` and `publish:gitlab`.
+
+Contributed by [@hyfc](https://github.com/hyfc) in [#32818](https://github.com/backstage/backstage/pull/32818).
+
+### Custom Slack message layouts
+
+Added an extension for custom Slack message layouts in the notifications backend Slack module.
+
+Contributed by [@kaidubauskas-dd](https://github.com/kaidubauskas-dd) in [#32733](https://github.com/backstage/backstage/pull/32733).
+
+### OpenAPI 3.1 support
+
+OpenAPI schemas across several packages have been migrated to version 3.1. The `schema openapi` commands in `@backstage/repo-tools` now auto-detect the OpenAPI version and support both 3.0.x and 3.1.x specifications.
+
+### Deprecated Catalog Processors & New Community Plugin Backend Modules
+
+Two Catalog processors have been deprecated as they have been moved to the Community Plugins repo with their own backend modules:
+
+- `AnnotateScmSlugEntityProcessor`: Use `@backstage-community/plugin-catalog-backend-module-annotate-scm-slug` instead
+- `CodeOwnersProcessor`: Use `@backstage-community/plugin-catalog-backend-module-codeowners` instead
+
+## Security Fixes
+
+This release does not contain any security fixes.
+
+## Upgrade path
+
+We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated).
+
+## Links and References
+
+Below you can find a list of links and references to help you learn about and start using this new release.
+
+- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/)
+- [GitHub repository](https://github.com/backstage/backstage)
+- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy)
+- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support
+- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.49.0-changelog.md)
+- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins)
+
+Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage.
diff --git a/docs/releases/v1.50.0-next.0-changelog.md b/docs/releases/v1.50.0-next.0-changelog.md
new file mode 100644
index 0000000000..b99b6bb195
--- /dev/null
+++ b/docs/releases/v1.50.0-next.0-changelog.md
@@ -0,0 +1,2439 @@
+# Release v1.50.0-next.0
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.50.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.50.0-next.0)
+
+## @backstage/ui@0.14.0-next.0
+
+### Minor Changes
+
+- 8659f33: **BREAKING**: The `Header` component's `tabs` prop now uses `HeaderNavTabItem[]` instead of `HeaderTab[]`. Tabs render as a `` element with links and optional dropdown menus instead of `role="tablist"`. A new `activeTabId` prop controls which tab is highlighted.
+
+ **Migration:**
+
+ ```diff
+ - import { Header, type HeaderTab } from '@backstage/ui';
+ + import { Header, type HeaderNavTabItem } from '@backstage/ui';
+
+ // Tabs no longer support matchStrategy — active state is controlled via activeTabId
+ - const tabs: HeaderTab[] = [
+ - { id: 'overview', label: 'Overview', href: '/overview', matchStrategy: 'prefix' },
+ + const tabs: HeaderNavTabItem[] = [
+ + { id: 'overview', label: 'Overview', href: '/overview' },
+ ];
+
+ -
+ +
+ ```
+
+ **Affected components:** Header
+
+- bed3307: **BREAKING**: Dropped support for React 17. The minimum supported React version is now 18.
+
+- 49ffe8a: **BREAKING**: Removed the `toolbarWrapper` element from `PluginHeader` and dropped `toolbarWrapper` from `PluginHeaderDefinition.classNames`. Toolbar layout styles now live on `toolbar` (`.bui-PluginHeaderToolbar`).
+
+ **Migration:** Update custom CSS that targeted `.bui-PluginHeaderToolbarWrapper` to use `.bui-PluginHeaderToolbar` instead.
+
+ **Affected components:** PluginHeader
+
+### Patch Changes
+
+- bcbb6eb: Made `SearchAutocomplete` background-aware. The input now adapts its background color based on its parent container's background level.
+
+ **Affected components:** SearchAutocomplete
+
+- 8c2e24e: Added `aria-hidden` to the `PluginHeader` icon to prevent screen readers from announcing decorative plugin icons.
+
+ **Affected components:** PluginHeader
+
+- 3d67aeb: Added `prefers-reduced-motion` support to Tab indicator animations. Users with reduced motion preferences will no longer see sliding transitions on the active and hover indicators.
+
+ **Affected components:** Tabs
+
+- cc4a682: Fixed the ButtonIcon's loading spinner animation
+
+ **Affected components:** ButtonIcon
+
+- adcdd2f: Simplified the `Menu` component's item structure by removing the inner wrapper element and applying styles directly to the menu item, improving DOM clarity.
+
+ **Affected components:** Menu
+
+- 0257ada: Added `react-aria`, `react-stately`, `@react-aria/interactions`, `@react-stately/layout`, and `@react-stately/overlays` as dependencies.
+
+- Updated dependencies
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/plugin-auth-backend@0.28.0-next.0
+
+### Minor Changes
+
+- d7c67cd: **BREAKING**: The setting `auth.omitIdentityTokenOwnershipClaim` has had its default value switched to `true`.
+
+ With this setting Backstage user tokens issued by the `auth` backend will no longer contain an `ent` claim - the one with the user's ownership entity refs. This means that tokens issued in large orgs no longer risk hitting HTTP header size limits.
+
+ To get ownership info for the current user, code should use the `userInfo` core service. In practice code will typically already conform to this since the `ent` claim has not been readily exposed in any other way for quite some time. But code which explicitly decodes Backstage tokens - which is strongly discouraged - may be affected by this change.
+
+ The setting will remain for some time to allow it to be set back to `false` if need be, but it will be removed entirely in a future release.
+
+### Patch Changes
+
+- dc87ac1: Fixed CIMD redirect URI matching to allow any port for localhost addresses per RFC 8252 Section 7.3. Native CLI clients use ephemeral ports for OAuth callbacks, which are now accepted when the registered redirect URI uses a localhost address.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/app-defaults@1.7.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-permission-react@0.4.42-next.0
+
+## @backstage/backend-app-api@1.6.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/backend-defaults@0.16.1-next.0
+
+### Patch Changes
+
+- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/config-loader@1.10.10-next.0
+ - @backstage/backend-app-api@1.6.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/backend-dev-utils@0.1.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/backend-dynamic-feature-service@0.8.1-next.0
+
+### Patch Changes
+
+- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`.
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/plugin-catalog-backend@3.5.1-next.0
+ - @backstage/backend-defaults@0.16.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/config-loader@1.10.10-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-events-backend@0.6.1-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/plugin-search-backend-node@1.4.3-next.0
+ - @backstage/backend-openapi-utils@0.6.8-next.0
+ - @backstage/plugin-app-node@0.1.44-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/backend-openapi-utils@0.6.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/backend-plugin-api@1.8.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/backend-test-utils@1.11.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.16.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/backend-app-api@1.6.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/cli@0.36.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/cli-module-build@0.1.1-next.0
+ - @backstage/cli-module-test-jest@0.1.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/cli-defaults@0.1.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.2.2
+
+## @backstage/cli-common@0.2.1-next.0
+
+### Patch Changes
+
+- e928e73: chore(deps): bump `undici` from 7.22.0 to 7.24.0
+- Updated dependencies
+ - @backstage/errors@1.2.7
+
+## @backstage/cli-defaults@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-module-build@0.1.1-next.0
+ - @backstage/cli-module-config@0.1.1-next.0
+ - @backstage/cli-module-github@0.1.1-next.0
+ - @backstage/cli-module-info@0.1.1-next.0
+ - @backstage/cli-module-lint@0.1.1-next.0
+ - @backstage/cli-module-maintenance@0.1.1-next.0
+ - @backstage/cli-module-migrate@0.1.1-next.0
+ - @backstage/cli-module-new@0.1.1-next.0
+ - @backstage/cli-module-test-jest@0.1.1-next.0
+ - @backstage/cli-module-translations@0.1.1-next.0
+ - @backstage/cli-module-actions@0.0.2-next.0
+ - @backstage/cli-module-auth@0.1.1-next.0
+
+## @backstage/cli-module-actions@0.0.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/errors@1.2.7
+
+## @backstage/cli-module-auth@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/errors@1.2.7
+
+## @backstage/cli-module-build@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/config-loader@1.10.10-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/module-federation-common@0.1.2
+
+## @backstage/cli-module-config@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/config-loader@1.10.10-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/cli-module-github@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+
+## @backstage/cli-module-info@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+
+## @backstage/cli-module-lint@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+
+## @backstage/cli-module-maintenance@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+
+## @backstage/cli-module-migrate@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/release-manifests@0.0.13
+
+## @backstage/cli-module-new@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/errors@1.2.7
+
+## @backstage/cli-module-test-jest@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+
+## @backstage/cli-module-translations@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+
+## @backstage/cli-node@0.3.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/codemods@0.1.56-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+
+## @backstage/config-loader@1.10.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/core-app-api@1.19.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/core-compat-api@0.5.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/plugin-app-react@0.2.2-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/core-components@0.18.9-next.0
+
+### Patch Changes
+
+- 320eed3: Resolved DOM nesting warning in OAuthRequestDialog by rendering secondary text as block-level spans.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.7.2
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/core-plugin-api@1.12.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/create-app@0.8.2-next.0
+
+### Patch Changes
+
+- e8ffac1: Various fixes for the `create-app` template: reorganizing sidebar items, removing redundant config and code, and adding a documentation example.
+- 4cf9f3a: Removed the unnecessary `@backstage/cli-module-new` dependency from the `next-app` template, since it is already included through `@backstage/cli-defaults`.
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+
+## @backstage/dev-utils@1.1.22-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/app-defaults@1.7.7-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/theme@0.7.2
+
+## @backstage/frontend-app-api@0.16.2-next.0
+
+### Patch Changes
+
+- 364d4fe: Added `apis` to `BootstrapSpecializedApp` and `FinalizedSpecializedApp` types.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/frontend-defaults@0.5.1-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/frontend-defaults@0.5.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-app@0.4.3-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/frontend-app-api@0.16.2-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/frontend-dev-utils@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-app@0.4.3-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/frontend-defaults@0.5.1-next.0
+
+## @backstage/frontend-dynamic-feature-loader@0.1.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/config@1.3.6
+ - @backstage/module-federation-common@0.1.2
+
+## @backstage/frontend-plugin-api@0.15.2-next.0
+
+### Patch Changes
+
+- d66a3ec: Added `titleLink` prop to `PageLayoutProps` so the plugin header title can link back to the plugin root.
+- e220589: Removed the unnecessary need to use `defineParams` callback from `PluginHeaderActionBlueprint`. It still works, but is no longer required.
+- Updated dependencies
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+
+## @backstage/frontend-test-utils@0.5.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-app@0.4.3-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/frontend-app-api@0.16.2-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/plugin-app-react@0.2.2-next.0
+ - @backstage/test-utils@1.7.17-next.0
+ - @backstage/config@1.3.6
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-react@0.4.42-next.0
+
+## @backstage/integration-react@1.2.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/config@1.3.6
+ - @backstage/integration@2.0.0
+
+## @backstage/repo-tools@0.17.1-next.0
+
+### Patch Changes
+
+- 270efef: Added support for packages that only support React 18+ in the `peer-deps` command.
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/config-loader@1.10.10-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/errors@1.2.7
+
+## @techdocs/cli@1.10.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/backend-defaults@0.16.1-next.0
+ - @backstage/plugin-techdocs-node@1.14.5-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+
+## @backstage/test-utils@1.7.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/config@1.3.6
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-react@0.4.42-next.0
+
+## @backstage/plugin-api-docs@0.13.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog@2.0.2-next.0
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.42-next.0
+
+## @backstage/plugin-app@0.4.3-next.0
+
+### Patch Changes
+
+- d66a3ec: Updated the `PageLayout` swap to pass a clickable `titleLink` on the `PluginHeader`, resolved from the plugin's root route ref.
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/plugin-app-react@0.2.2-next.0
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-permission-react@0.4.42-next.0
+
+## @backstage/plugin-app-backend@0.5.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/config-loader@1.10.10-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-app-node@0.1.44-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-app-node@0.1.44-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/config-loader@1.10.10-next.0
+
+## @backstage/plugin-app-react@0.2.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+
+## @backstage/plugin-app-visualizer@0.2.2-next.0
+
+### Patch Changes
+
+- e220589: Updated `PluginHeaderActionBlueprint` usage to pass params as a plain object.
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+
+## @backstage/plugin-auth@0.1.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.7.2
+
+## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+
+## @backstage/plugin-auth-backend-module-auth0-provider@0.3.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.28.0-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+
+## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+
+## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-auth-backend-module-github-provider@0.5.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+
+## @backstage/plugin-auth-backend-module-gitlab-provider@0.4.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+
+## @backstage/plugin-auth-backend-module-google-provider@0.3.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+
+## @backstage/plugin-auth-backend-module-guest-provider@0.2.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+
+## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+
+## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-oidc-provider@0.4.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.28.0-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-auth-backend-module-okta-provider@0.2.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+
+## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+
+## @backstage/plugin-auth-backend-module-openshift-provider@0.1.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-auth-node@0.6.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-auth-react@0.1.26-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-catalog@2.0.2-next.0
+
+### Patch Changes
+
+- d7b6077: Disabled the default page layout header for the catalog entity page in the new frontend system. The entity page already renders its own header through the `EntityHeader` extension, so the page layout header was redundant.
+- 744f904: Fixed the catalog table briefly showing an empty loading state when changing filters. The table now keeps displaying stale results until new data arrives.
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-compat-api@0.5.10-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/plugin-search-react@1.11.1-next.0
+ - @backstage/plugin-techdocs-react@1.3.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.42-next.0
+ - @backstage/plugin-scaffolder-common@2.0.0
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-techdocs-common@0.1.1
+
+## @backstage/plugin-catalog-backend@3.5.1-next.0
+
+### Patch Changes
+
+- 375b546: Fixed a deadlock in the catalog processing loop that occurred when running multiple replicas. The `getProcessableEntities` method used `SELECT ... FOR UPDATE SKIP LOCKED` to prevent concurrent processors from claiming the same rows, but the call was not wrapped in a transaction, so the row locks were released before the subsequent `UPDATE` executed. This allowed multiple replicas to select and update overlapping rows, causing PostgreSQL deadlock errors (code 40P01).
+- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/backend-openapi-utils@0.6.8-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/integration@2.0.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-catalog-backend-module-aws@0.4.22-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.16.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-kubernetes-common@0.9.10
+
+## @backstage/plugin-catalog-backend-module-azure@0.3.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/backend-openapi-utils@0.6.8-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.8
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-gcp@0.3.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/plugin-kubernetes-common@0.9.10
+
+## @backstage/plugin-catalog-backend-module-gerrit@0.3.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-gitea@0.1.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-github@0.13.1-next.0
+
+### Patch Changes
+
+- edf465f: Removed the `type-fest` dev dependency, replacing its `PartialDeep` import with a local helper type in tests.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-github-org@0.3.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend-module-github@0.13.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/config@1.3.6
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.8.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.16.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-backend-module-gitlab@0.8.2-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.5.1-next.0
+ - @backstage/backend-defaults@0.16.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-catalog-backend-module-ldap@0.12.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-logs@0.1.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.5.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+
+## @backstage/plugin-catalog-backend-module-msgraph@0.9.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-openapi@0.2.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/integration@2.0.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-catalog-backend-module-puppetdb@0.2.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-scaffolder-common@2.0.0
+
+## @backstage/plugin-catalog-backend-module-unprocessed@0.6.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-catalog-graph@0.6.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-catalog-import@0.13.12-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.42-next.0
+
+## @backstage/plugin-catalog-node@2.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/backend-test-utils@1.11.2-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-catalog-react@2.1.1-next.0
+
+### Patch Changes
+
+- 51aacae: Fixed a UI flicker in the catalog entity list where changing a filter would briefly flash stale data before showing the new results.
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-compat-api@0.5.10-next.0
+ - @backstage/frontend-test-utils@0.5.2-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-react@0.4.42-next.0
+
+## @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.0
+
+### Patch Changes
+
+- b6f1fae: The unprocessed entities view is now primarily intended for use as a tab within the DevTools plugin. The standalone page is still available but disabled by default. To re-enable it, add the following to your `app-config.yaml`:
+
+ ```yaml
+ app:
+ extensions:
+ - page:catalog-unprocessed-entities
+ ```
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-compat-api@0.5.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13
+
+## @backstage/plugin-config-schema@0.1.79-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-devtools@0.1.38-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-compat-api@0.5.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-devtools-common@0.1.23
+ - @backstage/plugin-permission-react@0.4.42-next.0
+
+## @backstage/plugin-devtools-backend@0.5.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/config-loader@1.10.10-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-devtools-common@0.1.23
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-devtools-react@0.2.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+
+## @backstage/plugin-events-backend@0.6.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/backend-openapi-utils@0.6.8-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-events-backend-module-aws-sqs@0.4.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-events-backend-module-azure@0.2.30-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+
+## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.30-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+
+## @backstage/plugin-events-backend-module-bitbucket-server@0.1.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+
+## @backstage/plugin-events-backend-module-gerrit@0.2.30-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+
+## @backstage/plugin-events-backend-module-github@0.4.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/config@1.3.6
+ - @backstage/integration@2.0.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-events-backend-module-gitlab@0.3.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/config@1.3.6
+
+## @backstage/plugin-events-backend-module-google-pubsub@0.2.2-next.0
+
+### Patch Changes
+
+- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-events-backend-module-kafka@0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-events-backend-test-utils@0.1.54-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.21-next.0
+
+## @backstage/plugin-events-node@0.4.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-gateway-backend@1.1.4-next.0
+
+### Patch Changes
+
+- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+
+## @backstage/plugin-home@0.9.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-compat-api@0.5.10-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/plugin-home-react@0.1.37-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/theme@0.7.2
+
+## @backstage/plugin-home-react@0.1.37-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-compat-api@0.5.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+
+## @backstage/plugin-kubernetes@0.12.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/plugin-kubernetes-react@0.5.18-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-kubernetes-common@0.9.10
+ - @backstage/plugin-permission-react@0.4.42-next.0
+
+## @backstage/plugin-kubernetes-backend@0.21.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-kubernetes-node@0.4.3-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-kubernetes-common@0.9.10
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-kubernetes-cluster@0.0.36-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/plugin-kubernetes-react@0.5.18-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-kubernetes-common@0.9.10
+ - @backstage/plugin-permission-react@0.4.42-next.0
+
+## @backstage/plugin-kubernetes-node@0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-kubernetes-common@0.9.10
+
+## @backstage/plugin-kubernetes-react@0.5.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-kubernetes-common@0.9.10
+
+## @backstage/plugin-mcp-actions-backend@0.1.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-mui-to-bui@0.2.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/theme@0.7.2
+
+## @backstage/plugin-notifications@0.5.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-notifications-common@0.2.1
+ - @backstage/plugin-signals-react@0.0.21-next.0
+
+## @backstage/plugin-notifications-backend@0.6.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-notifications-node@0.2.25-next.0
+ - @backstage/plugin-signals-node@0.1.30-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-notifications-common@0.2.1
+
+## @backstage/plugin-notifications-backend-module-email@0.3.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-notifications-node@0.2.25-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-notifications-common@0.2.1
+
+## @backstage/plugin-notifications-backend-module-slack@0.4.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-notifications-node@0.2.25-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-notifications-common@0.2.1
+
+## @backstage/plugin-notifications-node@0.2.25-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-signals-node@0.1.30-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-notifications-common@0.2.1
+
+## @backstage/plugin-org@0.7.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-catalog-common@1.1.8
+
+## @backstage/plugin-org-react@0.1.49-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+
+## @backstage/plugin-permission-backend@0.7.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-permission-node@0.10.12-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-permission-react@0.4.42-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-permission-common@0.9.7
+
+## @backstage/plugin-proxy-backend@0.6.12-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-proxy-node@0.1.14-next.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-proxy-node@0.1.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+
+## @backstage/plugin-scaffolder@1.36.2-next.0
+
+### Patch Changes
+
+- 4cc9af2: Fixed the layout of the scaffolder plugin in the new frontend system to use the new page layout.
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-scaffolder-react@1.20.1-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/plugin-techdocs-react@1.3.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.42-next.0
+ - @backstage/plugin-scaffolder-common@2.0.0
+ - @backstage/plugin-techdocs-common@0.1.1
+
+## @backstage/plugin-scaffolder-backend@3.2.1-next.0
+
+### Patch Changes
+
+- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/backend-openapi-utils@0.6.8-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-scaffolder-common@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-azure@0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.8
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.22-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.16.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-scaffolder-backend-module-gcp@0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-gitea@0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-github@0.9.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-scaffolder-backend-module-notifications@0.1.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-notifications-node@0.2.25-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/plugin-notifications-common@0.2.1
+
+## @backstage/plugin-scaffolder-backend-module-rails@0.5.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-scaffolder-backend-module-sentry@0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.10-next.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-scaffolder-node@0.13.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/backend-test-utils@1.11.2-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-scaffolder-common@2.0.0
+
+## @backstage/plugin-scaffolder-node-test-utils@0.3.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/backend-test-utils@1.11.2-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-scaffolder-react@1.20.1-next.0
+
+### Patch Changes
+
+- bdbbf00: build(deps): bump `flatted` from 3.4.1 to 3.4.2
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/frontend-test-utils@0.5.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-permission-react@0.4.42-next.0
+ - @backstage/plugin-scaffolder-common@2.0.0
+
+## @backstage/plugin-search@1.7.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/plugin-search-react@1.11.1-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend@2.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/plugin-search-backend-node@1.4.3-next.0
+ - @backstage/backend-openapi-utils@0.6.8-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-catalog@0.3.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-search-backend-node@1.4.3-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-elasticsearch@1.8.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-search-backend-node@1.4.3-next.0
+ - @backstage/config@1.3.6
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-explore@0.3.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-search-backend-node@1.4.3-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-pg@0.5.54-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-search-backend-node@1.4.3-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-search-backend-node@1.4.3-next.0
+ - @backstage/config@1.3.6
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-module-techdocs@0.4.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-search-backend-node@1.4.3-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-techdocs-node@1.14.5-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-backend-node@1.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-search-react@1.11.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-search-common@1.2.22
+
+## @backstage/plugin-signals@0.0.30-next.0
+
+### Patch Changes
+
+- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`.
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/plugin-signals-react@0.0.21-next.0
+
+## @backstage/plugin-signals-backend@0.3.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/plugin-signals-node@0.1.30-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-signals-node@0.1.30-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-signals-react@0.0.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-techdocs@1.17.3-next.0
+
+### Patch Changes
+
+- dc3cc87: Migrated the TechDocs alpha plugin pages to use BUI components. The index page and reader page now use BUI `Header` and `Container` instead of legacy `Page`/`Content`/`ContentHeader` wrappers. Added a `SupportButton` as a plugin header action. Changed plugin title to "Documentation" and icon to `RiArticleLine`.
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/plugin-auth-react@0.1.26-next.0
+ - @backstage/plugin-search-react@1.11.1-next.0
+ - @backstage/plugin-techdocs-react@1.3.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-techdocs-common@0.1.1
+
+## @backstage/plugin-techdocs-addons-test-utils@2.0.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog@2.0.2-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/plugin-techdocs@1.17.3-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/plugin-search-react@1.11.1-next.0
+ - @backstage/plugin-techdocs-react@1.3.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/test-utils@1.7.17-next.0
+
+## @backstage/plugin-techdocs-backend@2.1.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-catalog-node@2.1.1-next.0
+ - @backstage/plugin-techdocs-node@1.14.5-next.0
+ - @backstage/catalog-client@1.14.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/plugin-techdocs-react@1.3.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/integration@2.0.0
+
+## @backstage/plugin-techdocs-node@1.14.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-techdocs-common@0.1.1
+
+## @backstage/plugin-techdocs-react@1.3.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/version-bridge@1.0.12
+ - @backstage/plugin-techdocs-common@0.1.1
+
+## @backstage/plugin-user-settings@0.9.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.7.2
+ - @backstage/types@1.2.2
+ - @backstage/plugin-signals-react@0.0.21-next.0
+ - @backstage/plugin-user-settings-common@0.1.0
+
+## @backstage/plugin-user-settings-backend@0.4.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-signals-node@0.1.30-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-user-settings-common@0.1.0
+
+## example-app@0.0.34-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog@2.0.2-next.0
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-scaffolder-react@1.20.1-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/plugin-techdocs@1.17.3-next.0
+ - @backstage/plugin-app@0.4.3-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/plugin-signals@0.0.30-next.0
+ - @backstage/plugin-scaffolder@1.36.2-next.0
+ - @backstage/frontend-app-api@0.16.2-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.0
+ - @backstage/plugin-app-visualizer@0.2.2-next.0
+ - @backstage/core-compat-api@0.5.10-next.0
+ - @backstage/plugin-api-docs@0.13.6-next.0
+ - @backstage/plugin-catalog-graph@0.6.1-next.0
+ - @backstage/plugin-catalog-import@0.13.12-next.0
+ - @backstage/plugin-home@0.9.4-next.0
+ - @backstage/plugin-org@0.7.1-next.0
+ - @backstage/plugin-user-settings@0.9.2-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/plugin-auth@0.1.7-next.0
+ - @backstage/plugin-devtools@0.1.38-next.0
+ - @backstage/plugin-notifications@0.5.16-next.0
+ - @backstage/plugin-search@1.7.1-next.0
+ - @backstage/cli@0.36.1-next.0
+ - @backstage/plugin-kubernetes@0.12.18-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.36-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.0
+ - @backstage/frontend-defaults@0.5.1-next.0
+ - @backstage/app-defaults@1.7.7-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/plugin-auth-react@0.1.26-next.0
+ - @backstage/plugin-home-react@0.1.37-next.0
+ - @backstage/plugin-search-react@1.11.1-next.0
+ - @backstage/plugin-techdocs-react@1.3.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/plugin-app-react@0.2.2-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.42-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## app-example-plugin@0.0.34-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+
+## example-app-legacy@0.2.120-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog@2.0.2-next.0
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-scaffolder-react@1.20.1-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/plugin-techdocs@1.17.3-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/plugin-signals@0.0.30-next.0
+ - @backstage/plugin-scaffolder@1.36.2-next.0
+ - @backstage/frontend-app-api@0.16.2-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.0
+ - @backstage/plugin-api-docs@0.13.6-next.0
+ - @backstage/plugin-catalog-graph@0.6.1-next.0
+ - @backstage/plugin-catalog-import@0.13.12-next.0
+ - @backstage/plugin-home@0.9.4-next.0
+ - @backstage/plugin-org@0.7.1-next.0
+ - @backstage/plugin-user-settings@0.9.2-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/plugin-devtools@0.1.38-next.0
+ - @backstage/plugin-mui-to-bui@0.2.6-next.0
+ - @backstage/plugin-notifications@0.5.16-next.0
+ - @backstage/plugin-search@1.7.1-next.0
+ - @backstage/cli@0.36.1-next.0
+ - @backstage/plugin-kubernetes@0.12.18-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.36-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.0
+ - @backstage/app-defaults@1.7.7-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/plugin-auth-react@0.1.26-next.0
+ - @backstage/plugin-home-react@0.1.37-next.0
+ - @backstage/plugin-search-react@1.11.1-next.0
+ - @backstage/plugin-techdocs-react@1.3.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.42-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## example-backend@0.0.49-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.5.1-next.0
+ - @backstage/plugin-auth-backend@0.28.0-next.0
+ - @backstage/backend-defaults@0.16.1-next.0
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.2-next.0
+ - @backstage/plugin-scaffolder-backend@3.2.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-devtools-backend@0.5.16-next.0
+ - @backstage/plugin-catalog-backend-module-logs@0.1.21-next.0
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.2-next.0
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.6-next.0
+ - @backstage/plugin-notifications-backend@0.6.4-next.0
+ - @backstage/plugin-signals-backend@0.3.14-next.0
+ - @backstage/plugin-app-backend@0.5.13-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-events-backend@0.6.1-next.0
+ - @backstage/plugin-kubernetes-backend@0.21.3-next.0
+ - @backstage/plugin-mcp-actions-backend@0.1.11-next.0
+ - @backstage/plugin-permission-backend@0.7.11-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/plugin-proxy-backend@0.6.12-next.0
+ - @backstage/plugin-search-backend@2.1.1-next.0
+ - @backstage/plugin-search-backend-node@1.4.3-next.0
+ - @backstage/plugin-techdocs-backend@2.1.7-next.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.18-next.0
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.13-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.21-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.19-next.0
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.10-next.0
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.18-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.8-next.0
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.21-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.3.14-next.0
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.2-next.0
+ - @backstage/plugin-search-backend-module-explore@0.3.13-next.0
+ - @backstage/plugin-search-backend-module-techdocs@0.4.13-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-permission-common@0.9.7
+
+## @internal/cli@0.0.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.1-next.0
+
+## e2e-test@0.2.39-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/create-app@0.8.2-next.0
+ - @backstage/errors@1.2.7
+
+## @internal/frontend@0.0.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.12
+
+## @internal/scaffolder@0.0.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-react@1.20.1-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+
+## techdocs-cli-embedded-app@0.2.119-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog@2.0.2-next.0
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-techdocs@1.17.3-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/cli@0.36.1-next.0
+ - @backstage/frontend-defaults@0.5.1-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/plugin-techdocs-react@1.3.10-next.0
+ - @backstage/plugin-app-react@0.2.2-next.0
+ - @backstage/test-utils@1.7.17-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/theme@0.7.2
+
+## yarn-plugin-backstage@0.0.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/release-manifests@0.0.13
+
+## @internal/plugin-todo-list@1.0.50-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+
+## @internal/plugin-todo-list-backend@1.0.49-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/errors@1.2.7
diff --git a/docs/releases/v1.50.0-next.1-changelog.md b/docs/releases/v1.50.0-next.1-changelog.md
new file mode 100644
index 0000000000..269162faf7
--- /dev/null
+++ b/docs/releases/v1.50.0-next.1-changelog.md
@@ -0,0 +1,1679 @@
+# Release v1.50.0-next.1
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.50.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.50.0-next.1)
+
+## @backstage/backend-plugin-api@1.9.0-next.1
+
+### Minor Changes
+
+- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+
+## @backstage/core-app-api@1.20.0-next.1
+
+### Minor Changes
+
+- 400aa23: Added `FetchMiddlewares.clarifyFailures()` which replaces the uninformative "TypeError: Failed to fetch" with a message that includes the request method and URL.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/frontend-plugin-api@0.16.0-next.1
+
+### Minor Changes
+
+- 49397c1: Simplified the type signature of `createRouteRef` by replacing the dual `TParams`/`TParamKeys` type parameters with a single `TParamKey` parameter. This is a breaking change for callers that explicitly provided type arguments, but most usage that relies on inference is unaffected.
+
+### Patch Changes
+
+- ddc5247: Fixed `FlattenedMessages` type to avoid excessive type instantiation depth in TypeScript 6 when using `createTranslationRef` with the `translations` option.
+- fa55078: Refactored the internal `createSchemaFromZod` helper to use a schema-first generic pattern, replacing the `ZodSchema` constraint with `TSchema extends ZodType`. This avoids "excessively deep" type inference errors when multiple Zod copies are resolved.
+
+## @backstage/plugin-auth-node@0.7.0-next.1
+
+### Minor Changes
+
+- fa55078: **BREAKING**: Refactored `SignInResolverFactoryOptions` to use a schema-first generic pattern, following Zod's [recommended approach](https://zod.dev/library-authors?id=how-to-accept-user-defined-schemas#how-to-accept-user-defined-schemas) for writing generic functions. The type parameters changed from `` to ``.
+
+ This fixes "Type instantiation is excessively deep and possibly infinite" errors that occurred when the Zod version in a user's project did not align with the one in Backstage core.
+
+ If you use `createSignInResolverFactory` without explicit type parameters (the typical usage), no changes are needed:
+
+ ```ts
+ // This usage is unchanged
+ createSignInResolverFactory({
+ optionsSchema: z.object({ domain: z.string() }).optional(),
+ create(options = {}) {
+ /* ... */
+ },
+ });
+ ```
+
+ If you reference `SignInResolverFactoryOptions` with explicit type parameters, update as follows:
+
+ ```diff
+ - SignInResolverFactoryOptions
+ + SignInResolverFactoryOptions
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## @backstage/plugin-scaffolder-backend@3.3.0-next.1
+
+### Minor Changes
+
+- 309b712: Added a new `execute-template` actions registry action that executes a scaffolder template with provided input values and returns a task ID for tracking progress.
+
+### Patch Changes
+
+- 4559806: Removed unnecessary empty `examples` array from actions bridged via the actions registry.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-openapi-utils@0.6.8-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/app-defaults@1.7.7-next.1
+
+### Patch Changes
+
+- 400aa23: Added `FetchMiddlewares.clarifyFailures()` to the default fetch API middleware stack.
+- Updated dependencies
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/backend-app-api@1.6.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## @backstage/backend-defaults@0.16.1-next.1
+
+### Patch Changes
+
+- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions.
+- 5cd814f: Refactored auditor severity log level mappings to use `zod/v4` with schema-driven defaults and type inference.
+- 6e2aaab: Fixed `AwsS3UrlReader` failing to read files from S3 buckets configured with custom endpoint hosts. When an integration was configured with a specific endpoint like `https://bucket-1.s3.eu-central-1.amazonaws.com`, the URL parser incorrectly fell through to the non-AWS code path, always defaulting the region to `us-east-1` instead of extracting it from the hostname.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/backend-app-api@1.6.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+
+## @backstage/backend-dynamic-feature-service@0.8.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-defaults@0.16.1-next.1
+ - @backstage/plugin-catalog-backend@3.5.1-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/backend-openapi-utils@0.6.8-next.1
+ - @backstage/plugin-app-node@0.1.44-next.1
+ - @backstage/plugin-events-backend@0.6.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+ - @backstage/plugin-search-backend-node@1.4.3-next.1
+
+## @backstage/backend-openapi-utils@0.6.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## @backstage/backend-test-utils@1.11.2-next.1
+
+### Patch Changes
+
+- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-defaults@0.16.1-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/backend-app-api@1.6.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/cli@0.36.1-next.1
+
+### Patch Changes
+
+- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc).
+- a7a14b7: Added `DOM.AsyncIterable` to the default `lib` in the shared TypeScript configuration, enabling standard async iteration support for DOM APIs such as `FileSystemDirectoryHandle`. This aligns behavior with [TypeScript 6.0](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/#the-dom-lib-now-contains-domiterable-and-domasynciterable), where this lib is included in `DOM` by default.
+- Updated dependencies
+ - @backstage/cli-module-build@0.1.1-next.1
+ - @backstage/cli-module-test-jest@0.1.1-next.1
+ - @backstage/eslint-plugin@0.2.3-next.0
+
+## @backstage/cli-module-auth@0.1.1-next.1
+
+### Patch Changes
+
+- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc).
+
+## @backstage/cli-module-build@0.1.1-next.1
+
+### Patch Changes
+
+- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc).
+
+## @backstage/cli-module-new@0.1.1-next.1
+
+### Patch Changes
+
+- 64a91d0: Rename the legacy `frontend-plugin` to `frontend-plugin-legacy`
+
+## @backstage/cli-module-test-jest@0.1.1-next.1
+
+### Patch Changes
+
+- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc).
+- 6cc4811: Minor error message update
+
+## @backstage/core-compat-api@0.5.10-next.1
+
+### Patch Changes
+
+- 77ab7d5: Hide the default page header for pages created through the compatibility wrappers, since legacy plugins already render their own headers.
+- 49397c1: Removed unnecessary type argument from internal `createRouteRef` call.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-app-react@0.2.2-next.1
+
+## @backstage/core-plugin-api@1.12.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+
+## @backstage/create-app@0.8.2-next.1
+
+### Patch Changes
+
+- Bumped create-app version.
+
+## @backstage/dev-utils@1.1.22-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/app-defaults@1.7.7-next.1
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/eslint-plugin@0.2.3-next.0
+
+### Patch Changes
+
+- df43b0e: Fixed `no-mixed-plugin-imports` rule to return `null` from non-fixable suggestion handlers and added an explicit `SuggestionReportDescriptor[]` type annotation, matching the stricter type checking in TypeScript 6.0.
+
+## @backstage/frontend-app-api@0.16.2-next.1
+
+### Patch Changes
+
+- 400aa23: Wrapped extension permission authorization in a try/catch to surface errors as `ForwardedError` with a clear message.
+- Updated dependencies
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/frontend-defaults@0.5.1-next.1
+
+## @backstage/frontend-defaults@0.5.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-app@0.4.3-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/frontend-app-api@0.16.2-next.1
+ - @backstage/core-components@0.18.9-next.0
+
+## @backstage/frontend-dev-utils@0.1.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-app@0.4.3-next.1
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/frontend-defaults@0.5.1-next.1
+
+## @backstage/frontend-dynamic-feature-loader@0.1.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+
+## @backstage/frontend-test-utils@0.5.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-app@0.4.3-next.1
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/frontend-app-api@0.16.2-next.1
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/test-utils@1.7.17-next.1
+ - @backstage/plugin-app-react@0.2.2-next.1
+
+## @backstage/repo-tools@0.17.1-next.1
+
+### Patch Changes
+
+- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc).
+- 8e9679b: Parallelized CLI report generation, reducing wall-clock time by ~4x.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## @backstage/test-utils@1.7.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/ui@0.14.0-next.1
+
+### Patch Changes
+
+- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc).
+
+- 8d79835: Added RangeSlider component for selecting numeric ranges.
+
+ **Affected components:** RangeSlider
+
+- 5081bcc: Fixed `Avatar` becoming elliptical in flex layouts by preventing it from shrinking.
+
+ **Affected components:** Avatar
+
+- d840ba9: Fixed relative `href` resolution for BUI link components. Relative paths like `../other` are now correctly turned into absolute paths before reaching the React Aria layer, ensuring client-side navigation goes to the right place.
+
+ **Affected components:** ButtonLink, Card, CellProfile, CellText, Link, ListRow, MenuItem, MenuListBoxItem, Row, SearchAutocompleteItem, Tab, Tag
+
+- 3bc23a5: Added support for disabling pagination in `useTable` complete mode by setting `paginationOptions: { type: 'none' }`. This skips data slicing and produces `pagination: { type: 'none' }` in `tableProps`, removing the need for consumers to manually override the pagination prop on `Table`. Also fixed complete mode not reacting to dynamic changes in `paginationOptions.pageSize`.
+
+ **Affected components:** `useTable`
+
+- c368cf3: Updated dependency `@types/use-sync-external-store` to `^1.0.0`.
+
+- d0f055f: Added `showPaginationLabel` prop to `TablePagination` and `useTable` pagination options. When set to `false`, the pagination label (e.g., "1 - 20 of 150") is hidden while navigation controls remain visible. Defaults to `true`.
+
+ **Affected components:** `TablePagination`, `useTable`
+
+- feaf3d1: Fixed HeaderNav hover indicator covering tab text when theme uses opaque background colors. Also fixed an incorrect CSS variable reference (`--bui-font-family` → `--bui-font-regular`).
+
+ **Affected components:** Header
+
+## @backstage/plugin-api-docs@0.13.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-catalog@2.0.2-next.1
+
+## @backstage/plugin-app@0.4.3-next.1
+
+### Patch Changes
+
+- e5baa20: Added support for configuring URL redirects on the `app/routes` extension. Redirects can be configured through `app-config` as an array of `{from, to}` path pairs, which will cause navigation to the `from` path to be redirected to the `to` path.
+
+ For example:
+
+ ```yaml
+ app:
+ extensions:
+ - app/routes:
+ config:
+ redirects:
+ - from: /old-path
+ to: /new-path
+ ```
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-app-react@0.2.2-next.1
+
+## @backstage/plugin-app-backend@0.5.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-app-node@0.1.44-next.1
+
+## @backstage/plugin-app-node@0.1.44-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## @backstage/plugin-app-react@0.2.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-app-visualizer@0.2.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-auth@0.1.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+
+## @backstage/plugin-auth-backend@0.28.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-auth0-provider@0.3.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-auth-backend@0.28.0-next.1
+
+## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-github-provider@0.5.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-gitlab-provider@0.4.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-google-provider@0.3.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-guest-provider@0.2.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-oidc-provider@0.4.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-auth-backend@0.28.0-next.1
+
+## @backstage/plugin-auth-backend-module-okta-provider@0.2.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-openshift-provider@0.1.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-catalog@2.0.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-compat-api@0.5.10-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-search-react@1.11.1-next.1
+ - @backstage/plugin-techdocs-react@1.3.10-next.1
+
+## @backstage/plugin-catalog-backend@3.5.1-next.1
+
+### Patch Changes
+
+- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc).
+- 6884814: Improved catalog entity filter query performance by switching from `IN (subquery)` to `EXISTS (correlated subquery)` patterns. This enables PostgreSQL semi-join optimizations and fixes `NOT IN` NULL-semantics pitfalls by using `NOT EXISTS` instead.
+- 9da73bf: Reduced search table write churn during stitching by syncing only changed rows instead of doing a full delete and re-insert. On Postgres this uses a single writable CTE, on MySQL a temporary table merge with deadlock retry, and on SQLite the previous bulk replace.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-openapi-utils@0.6.8-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+
+## @backstage/plugin-catalog-backend-module-aws@0.4.22-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-defaults@0.16.1-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-catalog-backend-module-azure@0.3.16-next.1
+
+### Patch Changes
+
+- 39d27ee: Add Azure DevOps SCM event translation layer for instant catalog reprocessing.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-openapi-utils@0.6.8-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-catalog-backend-module-gcp@0.3.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-catalog-backend-module-gerrit@0.3.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-catalog-backend-module-gitea@0.1.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-catalog-backend-module-github@0.13.1-next.1
+
+### Patch Changes
+
+- b11e338: Fixed a bug where `GithubEntityProvider` with `validateLocationsExist: true` and `filters.branch` configured would always check for the catalog file on the repository's default branch (`HEAD`) instead of the configured branch. This caused repositories to be filtered out when the catalog file only existed on the non-default branch.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-catalog-backend-module-github-org@0.3.21-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-backend-module-github@0.13.1-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.8.2-next.1
+
+### Patch Changes
+
+- 54a8300: Add GitLab SCM event translation layer for instant catalog reprocessing.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-defaults@0.16.1-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-backend-module-gitlab@0.8.2-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-defaults@0.16.1-next.1
+ - @backstage/plugin-catalog-backend@3.5.1-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-catalog-backend-module-ldap@0.12.4-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-catalog-backend-module-logs@0.1.21-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-backend@3.5.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-catalog-backend-module-msgraph@0.9.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-catalog-backend-module-openapi@0.2.21-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-catalog-backend-module-puppetdb@0.2.21-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-catalog-backend-module-unprocessed@0.6.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-catalog-graph@0.6.1-next.1
+
+### Patch Changes
+
+- 0e147e8: Added `title` and `icon` to the new frontend system plugin definition.
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-catalog-import@0.13.12-next.1
+
+### Patch Changes
+
+- fa0593e: Added `title` and `icon` to the new frontend system plugin definition.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-catalog-node@2.1.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-test-utils@1.11.2-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+
+## @backstage/plugin-catalog-react@2.1.2-next.1
+
+### Patch Changes
+
+- eba2f61: Fixed `EntityInfoCard` header overflowing on narrow screens.
+- 0416216: Fixed entity relation cards (e.g., "Has components") only showing one entity at a time by using `paginationOptions: { type: 'none' }` instead of deriving page size from data length.
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-compat-api@0.5.10-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/frontend-test-utils@0.5.2-next.1
+
+## @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-compat-api@0.5.10-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-devtools@0.1.38-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-compat-api@0.5.10-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-devtools-backend@0.5.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+
+## @backstage/plugin-devtools-react@0.2.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-events-backend@0.6.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-openapi-utils@0.6.8-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-events-backend-module-aws-sqs@0.4.21-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-events-backend-module-azure@0.2.30-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.30-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-events-backend-module-bitbucket-server@0.1.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-events-backend-module-gerrit@0.2.30-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-events-backend-module-github@0.4.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-events-backend-module-gitlab@0.3.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-events-backend-module-google-pubsub@0.2.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-events-backend-module-kafka@0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-events-node@0.4.21-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## @backstage/plugin-gateway-backend@1.1.4-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## @backstage/plugin-home@0.9.4-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-compat-api@0.5.10-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-home-react@0.1.37-next.1
+
+## @backstage/plugin-home-react@0.1.37-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-compat-api@0.5.10-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-kubernetes@0.12.18-next.1
+
+### Patch Changes
+
+- d156cf4: Added `title` and `icon` to the new frontend system plugin definition.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-kubernetes-react@0.5.18-next.0
+
+## @backstage/plugin-kubernetes-backend@0.21.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-kubernetes-node@0.4.3-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+
+## @backstage/plugin-kubernetes-node@0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## @backstage/plugin-mcp-actions-backend@0.1.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+
+## @backstage/plugin-mui-to-bui@0.2.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-notifications@0.5.16-next.1
+
+### Patch Changes
+
+- d156cf4: Added `title` and `icon` to the new frontend system plugin definition.
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-notifications-backend@0.6.4-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-notifications-node@0.2.25-next.1
+ - @backstage/plugin-signals-node@0.1.30-next.1
+
+## @backstage/plugin-notifications-backend-module-email@0.3.20-next.1
+
+### Patch Changes
+
+- 19ef9fb: build(deps): bump `nodemailer` from 7.0.13 to 8.0.4
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-notifications-node@0.2.25-next.1
+
+## @backstage/plugin-notifications-backend-module-slack@0.4.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-notifications-node@0.2.25-next.1
+
+## @backstage/plugin-notifications-node@0.2.25-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-signals-node@0.1.30-next.1
+
+## @backstage/plugin-org@0.7.1-next.1
+
+### Patch Changes
+
+- 87eb31c: Fixed `GroupProfileCard` and `UserProfileCard` content overflowing on narrow screens.
+- d156cf4: Added `title` and `icon` to the new frontend system plugin definition.
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-permission-backend@0.7.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+
+## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+
+## @backstage/plugin-permission-node@0.10.12-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+
+## @backstage/plugin-proxy-backend@0.6.12-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-proxy-node@0.1.14-next.1
+
+## @backstage/plugin-proxy-node@0.1.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## @backstage/plugin-scaffolder@1.36.2-next.1
+
+### Patch Changes
+
+- 864a799: Fix the display of the description in `GitlabRepoPicker`:
+
+ - Move `owner.description` helper text outside the `allowedOwners` conditional so it renders for both `Select` and `Autocomplete` modes.
+ - Update the `Autocomplete` label to use `fields.gitlabRepoPicker.owner.inputTitle` instead of `fields.gitlabRepoPicker.owner.title`.
+
+- a7a14b7: Removed custom `IterableDirectoryHandle` and `WritableFileHandle` types in favor of the standard DOM `FileSystemDirectoryHandle` and `FileSystemFileHandle` types, which are now available through the `DOM.AsyncIterable` lib added to the shared TypeScript configuration.
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-scaffolder-react@1.20.1-next.1
+ - @backstage/plugin-techdocs-react@1.3.10-next.1
+
+## @backstage/plugin-scaffolder-backend-module-azure@0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.22-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-defaults@0.16.1-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-gcp@0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-gitea@0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-github@0.9.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-notifications@0.1.21-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-notifications-node@0.2.25-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-rails@0.5.20-next.1
+
+### Patch Changes
+
+- 2905c59: Removed unused `jest-when` dev dependency.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-sentry@0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.21-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.10-next.1
+
+## @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-test-utils@1.11.2-next.1
+
+## @backstage/plugin-scaffolder-node-test-utils@0.3.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-test-utils@1.11.2-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+
+## @backstage/plugin-scaffolder-react@1.20.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/frontend-test-utils@0.5.2-next.1
+
+## @backstage/plugin-search@1.7.1-next.1
+
+### Patch Changes
+
+- 34aebcc: Fixed the `SearchModal` leaving the page in a broken state by not restoring body overflow and aria-hidden attributes when closing.
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-search-react@1.11.1-next.1
+
+## @backstage/plugin-search-backend@2.1.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-openapi-utils@0.6.8-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+ - @backstage/plugin-search-backend-node@1.4.3-next.1
+
+## @backstage/plugin-search-backend-module-catalog@0.3.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-search-backend-node@1.4.3-next.1
+
+## @backstage/plugin-search-backend-module-elasticsearch@1.8.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-search-backend-node@1.4.3-next.1
+
+## @backstage/plugin-search-backend-module-explore@0.3.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-search-backend-node@1.4.3-next.1
+
+## @backstage/plugin-search-backend-module-pg@0.5.54-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-search-backend-node@1.4.3-next.1
+
+## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-search-backend-node@1.4.3-next.1
+
+## @backstage/plugin-search-backend-module-techdocs@0.4.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-search-backend-node@1.4.3-next.1
+ - @backstage/plugin-techdocs-node@1.14.5-next.1
+
+## @backstage/plugin-search-backend-node@1.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## @backstage/plugin-search-react@1.11.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-signals@0.0.30-next.1
+
+### Patch Changes
+
+- d156cf4: Added `title` and `icon` to the new frontend system plugin definition.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-signals-backend@0.3.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+ - @backstage/plugin-signals-node@0.1.30-next.1
+
+## @backstage/plugin-signals-node@0.1.30-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## @backstage/plugin-techdocs@1.17.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-search-react@1.11.1-next.1
+ - @backstage/plugin-techdocs-react@1.3.10-next.1
+
+## @backstage/plugin-techdocs-addons-test-utils@2.0.4-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/test-utils@1.7.17-next.1
+ - @backstage/plugin-catalog@2.0.2-next.1
+ - @backstage/plugin-search-react@1.11.1-next.1
+ - @backstage/plugin-techdocs@1.17.3-next.1
+ - @backstage/plugin-techdocs-react@1.3.10-next.1
+
+## @backstage/plugin-techdocs-backend@2.1.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-catalog-node@2.1.1-next.1
+ - @backstage/plugin-techdocs-node@1.14.5-next.1
+
+## @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-techdocs-react@1.3.10-next.1
+
+## @backstage/plugin-techdocs-node@1.14.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## @backstage/plugin-techdocs-react@1.3.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-user-settings@0.9.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## @backstage/plugin-user-settings-backend@0.4.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-signals-node@0.1.30-next.1
+
+## example-app@0.0.34-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/app-defaults@1.7.7-next.1
+ - @backstage/plugin-app@0.4.3-next.1
+ - @backstage/cli@0.36.1-next.1
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/plugin-catalog-graph@0.6.1-next.1
+ - @backstage/plugin-catalog-import@0.13.12-next.1
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/plugin-org@0.7.1-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/frontend-app-api@0.16.2-next.1
+ - @backstage/core-compat-api@0.5.10-next.1
+ - @backstage/plugin-kubernetes@0.12.18-next.1
+ - @backstage/plugin-notifications@0.5.16-next.1
+ - @backstage/plugin-scaffolder@1.36.2-next.1
+ - @backstage/plugin-search@1.7.1-next.1
+ - @backstage/plugin-signals@0.0.30-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-api-docs@0.13.6-next.1
+ - @backstage/plugin-catalog@2.0.2-next.1
+ - @backstage/plugin-home@0.9.4-next.1
+ - @backstage/plugin-scaffolder-react@1.20.1-next.1
+ - @backstage/plugin-search-react@1.11.1-next.1
+ - @backstage/plugin-techdocs@1.17.3-next.1
+ - @backstage/plugin-user-settings@0.9.2-next.1
+ - @backstage/frontend-defaults@0.5.1-next.1
+ - @backstage/plugin-app-react@0.2.2-next.1
+ - @backstage/plugin-app-visualizer@0.2.2-next.1
+ - @backstage/plugin-auth@0.1.7-next.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.1
+ - @backstage/plugin-devtools@0.1.38-next.1
+ - @backstage/plugin-home-react@0.1.37-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.1
+ - @backstage/plugin-techdocs-react@1.3.10-next.1
+
+## app-example-plugin@0.0.34-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+
+## example-app-legacy@0.2.120-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/app-defaults@1.7.7-next.1
+ - @backstage/cli@0.36.1-next.1
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/plugin-catalog-graph@0.6.1-next.1
+ - @backstage/plugin-catalog-import@0.13.12-next.1
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/plugin-org@0.7.1-next.1
+ - @backstage/frontend-app-api@0.16.2-next.1
+ - @backstage/plugin-kubernetes@0.12.18-next.1
+ - @backstage/plugin-notifications@0.5.16-next.1
+ - @backstage/plugin-scaffolder@1.36.2-next.1
+ - @backstage/plugin-search@1.7.1-next.1
+ - @backstage/plugin-signals@0.0.30-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-api-docs@0.13.6-next.1
+ - @backstage/plugin-catalog@2.0.2-next.1
+ - @backstage/plugin-home@0.9.4-next.1
+ - @backstage/plugin-scaffolder-react@1.20.1-next.1
+ - @backstage/plugin-search-react@1.11.1-next.1
+ - @backstage/plugin-techdocs@1.17.3-next.1
+ - @backstage/plugin-user-settings@0.9.2-next.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.1
+ - @backstage/plugin-devtools@0.1.38-next.1
+ - @backstage/plugin-home-react@0.1.37-next.1
+ - @backstage/plugin-mui-to-bui@0.2.6-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.1
+ - @backstage/plugin-techdocs-react@1.3.10-next.1
+
+## example-backend@0.0.49-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-defaults@0.16.1-next.1
+ - @backstage/plugin-catalog-backend@3.5.1-next.1
+ - @backstage/plugin-scaffolder-backend@3.3.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-app-backend@0.5.13-next.1
+ - @backstage/plugin-auth-backend@0.28.0-next.1
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.2-next.1
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.18-next.1
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.6-next.1
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.13-next.1
+ - @backstage/plugin-catalog-backend-module-logs@0.1.21-next.1
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.21-next.1
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.19-next.1
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.10-next.1
+ - @backstage/plugin-devtools-backend@0.5.16-next.1
+ - @backstage/plugin-events-backend@0.6.1-next.1
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.2-next.1
+ - @backstage/plugin-kubernetes-backend@0.21.3-next.1
+ - @backstage/plugin-mcp-actions-backend@0.1.11-next.1
+ - @backstage/plugin-notifications-backend@0.6.4-next.1
+ - @backstage/plugin-permission-backend@0.7.11-next.1
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.18-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+ - @backstage/plugin-proxy-backend@0.6.12-next.1
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.8-next.1
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.21-next.1
+ - @backstage/plugin-search-backend@2.1.1-next.1
+ - @backstage/plugin-search-backend-module-catalog@0.3.14-next.1
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.2-next.1
+ - @backstage/plugin-search-backend-module-explore@0.3.13-next.1
+ - @backstage/plugin-search-backend-module-techdocs@0.4.13-next.1
+ - @backstage/plugin-search-backend-node@1.4.3-next.1
+ - @backstage/plugin-signals-backend@0.3.14-next.1
+ - @backstage/plugin-techdocs-backend@2.1.7-next.1
+
+## @internal/frontend@0.0.19-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+
+## @internal/scaffolder@0.0.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/plugin-scaffolder-react@1.20.1-next.1
+
+## techdocs-cli-embedded-app@0.2.119-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.36.1-next.1
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/test-utils@1.7.17-next.1
+ - @backstage/plugin-catalog@2.0.2-next.1
+ - @backstage/plugin-techdocs@1.17.3-next.1
+ - @backstage/frontend-defaults@0.5.1-next.1
+ - @backstage/plugin-app-react@0.2.2-next.1
+ - @backstage/plugin-techdocs-react@1.3.10-next.1
+
+## @internal/plugin-todo-list-backend@1.0.49-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
diff --git a/docs/tooling/cli/02-build-system.md b/docs/tooling/cli/02-build-system.md
index 2c7826a149..88ec443107 100644
--- a/docs/tooling/cli/02-build-system.md
+++ b/docs/tooling/cli/02-build-system.md
@@ -294,6 +294,20 @@ will be set up that listens to the protocol, host and port set by `app.baseUrl`
in the configuration. If needed it is also possible to override the listening
options through the `app.listen` configuration.
+For frontend plugin packages using the new frontend system, the recommended way to
+set up the `dev/index` entry point is to use the `createDevApp` helper from
+`@backstage/frontend-dev-utils`. It creates and renders a minimal Backstage app
+with your plugin loaded:
+
+```tsx title="in dev/index.ts"
+import { createDevApp } from '@backstage/frontend-dev-utils';
+import myPlugin from '../src';
+
+createDevApp({ features: [myPlugin] });
+```
+
+For the legacy frontend system, the `@backstage/dev-utils` package provides equivalent helpers.
+
The frontend development bundling is currently based on
[Webpack](https://webpack.js.org/) and
[Webpack Dev Server](https://webpack.js.org/configuration/dev-server/). The
@@ -595,8 +609,8 @@ If set to `true`, any attempt to make a network request in frontend package test
Caching is used sparingly throughout the Backstage build system. It is always used as a way to squeeze out a little bit of extra performance, rather than requirement to keep things fast. The following is a list of places where optional caching is available:
- **TypeScript** - The default `tsconfig.json` used by Backstage projects has `incremental` set to `true`, which enables local caching of type checking results. It is however generally not recommended in CI, where `yarn tsc:full` is preferred, which sets `--incremental false`.
-- **Testing** - The `backstage-cli repo test` command has a `--successCache` flag that enables caching of successful test results. This is done at the package level, meaning that if a package has not been changed since the last test run and it was successful, the testing will be skipped. This is recommended to be used in CI, but not during local development.
-- **Linting** - The `backstage-cli repo lint` command has a `--successCache` flag that enables caching of successful linting results. This is done at the package level, meaning that if a package has not been changed since the last lint run and it was successful, the linting will be skipped. This is recommended to be used in CI, but not during local development.
+- **Testing** - The `backstage-cli repo test` command has a `--success-cache` flag that enables caching of successful test results. This is done at the package level, meaning that if a package has not been changed since the last test run and it was successful, the testing will be skipped. This is recommended to be used in CI, but not during local development.
+- **Linting** - The `backstage-cli repo lint` command has a `--success-cache` flag that enables caching of successful linting results. This is done at the package level, meaning that if a package has not been changed since the last lint run and it was successful, the linting will be skipped. This is recommended to be used in CI, but not during local development.
- **Webpack** - It is possible to enable experimental caching of frontend package builds using the `BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE` environment variable. This will enable the Webpack filesystem cache.
### Debugging Jest Tests
@@ -640,7 +654,7 @@ With that in mind, here are some IDEs configurations to run backstage components
}
```
-3. Add a launch configuration for VS Code in `launch.json` in the `.vscode` folder.
+3. Add a launch configuration for VS Code in `launch.json` in the `.vscode` folder.
A complete configuration for debugging may look like this:
```jsonc
diff --git a/docs/tooling/cli/03-commands.md b/docs/tooling/cli/03-commands.md
index 085175c958..55b5f495e9 100644
--- a/docs/tooling/cli/03-commands.md
+++ b/docs/tooling/cli/03-commands.md
@@ -120,11 +120,11 @@ Usage: backstage-cli repo lint [options]
Lint all packages in the project
Options:
- --format Lint report output format (default: "eslint-formatter-friendly")
- --since [ Only lint packages that changed since the specified ref
- --successCache Enable success caching, which skips running tests for unchanged packages that were successful in the previous run
- --successCacheDir ] Set the success cache location, (default: node_modules/.cache/backstage-cli)
- --fix Attempt to automatically fix violations
+ --format Lint report output format (default: "eslint-formatter-friendly")
+ --since [ Only lint packages that changed since the specified ref
+ --success-cache Enable success caching, which skips running lint for unchanged packages that were successful in the previous run
+ --success-cache-dir ] Set the success cache location, (default: node_modules/.cache/backstage-cli)
+ --fix Attempt to automatically fix violations
```
## repo test
@@ -153,7 +153,7 @@ If arguments are provided, they will be forwarded to Jest and used to filter tes
yarn test packages/app/src/App.test.tsx
```
-If you want to avoid re-running tests that have not changed since the last successful run in CI, you can use the `--successCache` flag. By default this cache is stored in `node_modules/.cache/backstage-cli`, but you can choose a different directory with the `--successCacheDir `.
+If you want to avoid re-running tests that have not changed since the last successful run in CI, you can use the `--success-cache` flag. By default this cache is stored in `node_modules/.cache/backstage-cli`, but you can choose a different directory with the `--success-cache-dir `.
```text
Usage: backstage-cli repo test [options]
@@ -161,11 +161,11 @@ Usage: backstage-cli repo test [options]
Run tests, forwarding args to Jest, defaulting to watch mode
Options:
- --since [ Only test packages that changed since the specified ref
- --successCache Enable success caching, which skips running tests for unchanged packages that were successful in the previous run
- --successCacheDir ] Set the success cache location, (default: node_modules/.cache/backstage-cli)
- --jest-help Show help for Jest CLI options, which are passed through
- -h, --help display help for command
+ --since [ Only test packages that changed since the specified ref
+ --success-cache Enable success caching, which skips running tests for unchanged packages that were successful in the previous run
+ --success-cache-dir ] Set the success cache location, (default: node_modules/.cache/backstage-cli)
+ --jest-help Show help for Jest CLI options, which are passed through
+ -h, --help display help for command
```
## package start
@@ -178,7 +178,7 @@ Usage: backstage-cli package start [options]
Start a package for local development
Options:
- --config Config files to load instead of app-config.yaml (default: [])
+ --config Config files to load instead of app-config.yaml (default: [])
--role Run the command with an explicit package role
--check Enable type checking and linting if available
--inspect Enable debugger in Node.js environments
@@ -204,6 +204,129 @@ Options:
--module-federation Build a package as a module federation remote. Applies to frontend plugin packages only.
```
+## package bundle
+
+:::caution Experimental
+This command is experimental and may receive breaking changes in future releases
+without a deprecation period. It is hidden from the main `--help` output.
+:::
+
+Bundle a plugin for dynamic loading. This creates a self-contained plugin
+package that can be deployed independently and loaded dynamically by a Backstage
+application. Supports both backend and frontend plugins.
+
+Unlike regular builds, the bundle command:
+
+- Creates a fully self-contained plugin deliverable
+
+- Produces module federation assets (frontend) or includes plugin dependencies in the plugin's private `node_modules`, building and packing (with `yarn pack`) the local `workspace:^` dependencies first (backend).
+- Generates a config schema from plugin-related packages only.
+- Validates that the plugin exports valid dynamic loading entry points (backend only)
+
+### Usage
+
+```bash
+# Bundle the current package (output: ./bundle/)
+yarn backstage-cli package bundle
+
+# Bundle to a specific directory (output: ../dynamic-plugins//)
+yarn backstage-cli package bundle --output-destination ../dynamic-plugins
+
+# Override the bundle subdirectory name
+yarn backstage-cli package bundle --output-name my-plugin-bundle
+
+# Clean output before bundling
+yarn backstage-cli package bundle --clean
+
+# Skip building for the plugin and its local dependencies
+yarn backstage-cli package bundle --no-build
+
+# Skip dependency installation and entrypoint validation
+yarn backstage-cli package bundle --no-install
+
+# Stream detailed output from build, pack, and install steps
+yarn backstage-cli package bundle --verbose
+
+# Use a pre-built dist workspace for batch bundling.
+# First, create the workspace with:
+# backstage-cli build-workspace [packages...] --alwaysPack
+# Then pass as --pre-packed-dir:
+yarn backstage-cli package bundle --pre-packed-dir ../dist-workspace
+```
+
+### Options
+
+```text
+Usage: backstage-cli package bundle [options]
+
+Bundle a plugin for dynamic loading
+
+Options:
+ --output-destination Directory in which the bundle subdirectory is created.
+ Defaults to the current package directory.
+ --output-name Name of the bundle subdirectory. Defaults to "bundle" when
+ output stays in the package directory, or to the mangled
+ package name (e.g. myorg-plugin-foo) when
+ --output-destination is specified.
+ --clean Clean the output directory before bundling
+ --no-build Skip building packages (assumes they are already built)
+ --no-install Skip dependency installation and entrypoint validation.
+ --verbose Stream detailed output from internal steps (build, pack,
+ install) to the console. Without this flag, output is
+ captured to per-step log files and only shown on error.
+ --pre-packed-dir Path to a pre-built dist workspace (from
+ build-workspace --alwaysPack). Skips local dependency
+ packing and uses pre-packed packages directly. For frontend
+ plugins, this also enables yarn.lock generation for SBOM.
+```
+
+### Output Contract
+
+The bundle output is a directory that can be deployed as a standalone unit.
+Consumers of the bundle (such as `@backstage/backend-dynamic-feature-service`
+or `@backstage/frontend-dynamic-feature-loader`) can rely on the following
+guarantees:
+
+**All bundles:**
+
+- A `package.json` at the bundle root with entry points configured for dynamic
+ loading. The `backstage.role` and `files` fields are preserved from the source package.
+- A `dist/` directory containing the built plugin code.
+- A `dist/.config-schema.json` file (when any config schemas apply) containing
+ gathered schemas from the plugin, its local workspace dependencies, and
+ third-party dependencies. Schemas from unrelated Backstage packages are excluded.
+- No `scripts` or `devDependencies` in `package.json`.
+
+**Backend plugins** (`backend-plugin`, `backend-plugin-module`):
+
+- A `node_modules/` directory with all production dependencies (including local
+ workspace dependencies), pinned to their exact versions from the source lockfile.
+- `bundleDependencies` is set to `true` in `package.json`.
+
+**Frontend plugins** (`frontend-plugin`, `frontend-plugin-module`):
+
+- `main` points to `dist/remoteEntry.js` (the Module Federation remote entry).
+- `types` points to `dist/@mf-types/index.d.ts` when type declarations are
+ available.
+- No embedded `node_modules/` directory.
+
+### Environment Variables
+
+The bundle command supports the same environment variables as the Backstage yarn plugin
+for resolving `backstage:^` version specifiers:
+
+- `BACKSTAGE_MANIFEST_FILE`: Path to a local manifest file (for offline usage)
+- `BACKSTAGE_VERSIONS_BASE_URL`: Custom base URL for fetching release manifests
+
+### Supported Package Roles
+
+The bundle command supports packages with the following roles:
+
+- `backend-plugin`
+- `backend-plugin-module`
+- `frontend-plugin`
+- `frontend-plugin-module`
+
## package lint
Lint a package. In addition to the default `eslint` behavior, this command will
@@ -414,9 +537,17 @@ package. This essentially calls `yarn pack` in each included package and unpacks
the resulting archive in the target `workspace-dir`.
```text
-Usage: backstage-cli build-workspace [options]
+Usage: backstage-cli build-workspace [options] [packages...]
+
+Options:
+ --alwaysPack Force workspace output to be a result of running `yarn pack` on
+ each package (warning: very slow)
```
+When `--alwaysPack` is used, the output directory can be passed to
+`backstage-cli package bundle --pre-packed-dir` to speed up batch bundling of
+multiple plugins from the same monorepo.
+
## create-github-app
Creates a GitHub App in your GitHub organization. This is an alternative to
diff --git a/docs/tooling/cli/04-templates.md b/docs/tooling/cli/04-templates.md
index 894cc7efd0..69b89a51c7 100644
--- a/docs/tooling/cli/04-templates.md
+++ b/docs/tooling/cli/04-templates.md
@@ -73,7 +73,7 @@ Custom templates can be installed from local directories. To install a template
Each entry in the `templates` array should be a relative path that points to a directory containing a `portable-template.yaml` file. If the path starts with `./` it will be used as is, otherwise it will be resolved as a module within `node_modules`.
-When defining the `templates` array it will override the default set of templates. If you want to keep using one of the build-in templates in the Backstage CLI you can reference them directly within the CLI package. This following is the full list of built-in templates:
+When defining the `templates` array it will override the default set of templates. If you want to keep using one of the built-in templates in the Backstage CLI you can reference them directly within the `@backstage/cli-module-new` package. The following is the full list of built-in templates:
```json
{
@@ -82,16 +82,19 @@ When defining the `templates` array it will override the default set of template
"cli": {
"new": {
"templates": [
- "@backstage/cli/templates/frontend-plugin",
- "@backstage/cli/templates/backend-plugin",
- "@backstage/cli/templates/backend-plugin-module",
- "@backstage/cli/templates/plugin-web-library",
- "@backstage/cli/templates/plugin-node-library",
- "@backstage/cli/templates/plugin-common-library",
- "@backstage/cli/templates/web-library",
- "@backstage/cli/templates/node-library",
- "@backstage/cli/templates/catalog-provider-module",
- "@backstage/cli/templates/scaffolder-backend-module"
+ "@backstage/cli-module-new/templates/frontend-plugin",
+ "@backstage/cli-module-new/templates/frontend-plugin-module",
+ "@backstage/cli-module-new/templates/legacy-frontend-plugin",
+ "@backstage/cli-module-new/templates/backend-plugin",
+ "@backstage/cli-module-new/templates/backend-plugin-module",
+ "@backstage/cli-module-new/templates/plugin-web-library",
+ "@backstage/cli-module-new/templates/plugin-node-library",
+ "@backstage/cli-module-new/templates/plugin-common-library",
+ "@backstage/cli-module-new/templates/web-library",
+ "@backstage/cli-module-new/templates/node-library",
+ "@backstage/cli-module-new/templates/cli-module",
+ "@backstage/cli-module-new/templates/catalog-provider-module",
+ "@backstage/cli-module-new/templates/scaffolder-backend-module"
]
}
}
@@ -99,6 +102,10 @@ When defining the `templates` array it will override the default set of template
}
```
+:::note
+The old `@backstage/cli/templates/*` paths are still supported for backwards compatibility and will be automatically rewritten to `@backstage/cli-module-new/templates/*`.
+:::
+
## Creating your own CLI templates
Each template lives in its own directory and must have a `portable-template.yaml` file that describes the template. The template directory can also contain any files that should be templated or copied to the generated package.
@@ -128,7 +135,7 @@ export function getPluginId() {
}
```
-If you'd like to see more examples, you can find all the default templates and their yaml files [here](https://github.com/backstage/backstage/tree/master/packages/cli/templates).
+If you'd like to see more examples, you can find all the default templates and their yaml files [here](https://github.com/backstage/backstage/tree/master/packages/cli-module-new/templates).
Once your template is ready, [add it to your config](#installing-custom-templates), and you should now be able to select it when running `yarn new`.
diff --git a/docs/tutorials/corporate-proxy.md b/docs/tutorials/corporate-proxy.md
new file mode 100644
index 0000000000..e4346e4f94
--- /dev/null
+++ b/docs/tutorials/corporate-proxy.md
@@ -0,0 +1,33 @@
+---
+id: corporate-proxy
+title: Running Backstage behind a Corporate Proxy
+description: Guide on how to configure Backstage to work behind a corporate proxy using Node.js built-in proxy support.
+---
+
+# Running Backstage behind a Corporate Proxy
+
+Sometimes you have to run Backstage with no direct access to the public internet, except through a corporate proxy. The backend is most likely where you'll run into proxy issues, as it isn't helped by your browser or OS proxy settings.
+
+## Using Node.js built-in proxy support
+
+Starting with Node.js 22.21.0 and 24.5.0, Node.js has built-in support for proxy environment variables. When enabled, native `fetch()`, `node:http`, and `node:https` all respect the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables without any additional packages. See the [Node.js enterprise network configuration docs](https://nodejs.org/en/learn/http/enterprise-network-configuration#proxy-configuration) for full details.
+
+To enable the built-in proxy support, set the `NODE_USE_ENV_PROXY` environment variable along with your proxy settings:
+
+```sh
+export HTTP_PROXY=http://username:password@proxy.example.net:8888
+export HTTPS_PROXY=http://username:password@proxy.example.net:8888
+export NO_PROXY=localhost,127.0.0.1,.internal.company.com
+export NODE_USE_ENV_PROXY=1
+yarn start
+```
+
+### Compatibility with third-party fetch libraries
+
+Per [ADR014](../architecture-decisions/adr014-use-fetch.md), Backstage backend code should use native `fetch()`, which works with Node.js's proxy out of the box. Some core packages and many [community plugins](https://github.com/backstage/community-plugins/) still use `node-fetch` (see [ADR013](../architecture-decisions/adr013-use-node-fetch.md)) or `cross-fetch` (for isomorphic packages). Both libraries delegate to `node:http`/`node:https` internally and do **not** set a custom HTTP agent by default, which means Node.js's proxy works for them as well.
+
+The exception is code that explicitly passes a custom `agent` to its fetch calls (e.g. the Kubernetes plugins, which use `new https.Agent(...)` for TLS client certificates). In those cases, the custom agent takes precedence and the built-in proxy is bypassed. This is generally the desired behavior, since those agents are configured for direct connections to specific endpoints like cluster APIs.
+
+## Legacy approach
+
+If you are on a Node.js version older than 22.21.0, you can use third-party packages to add proxy support. See the [legacy proxy setup guide](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md) for instructions using `undici`, `global-agent`, and `proxy-agent`.
diff --git a/docs/tutorials/enable-public-entry--old.md b/docs/tutorials/enable-public-entry--old.md
new file mode 100644
index 0000000000..cc8e3d4d3d
--- /dev/null
+++ b/docs/tutorials/enable-public-entry--old.md
@@ -0,0 +1,107 @@
+---
+id: enable-public-entry--old
+title: Enabling a public entry point (Old Frontend System)
+description: A guide for how to experiment with public and protected Backstage app bundles
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./enable-public-entry.md) instead.
+::::
+
+# Enable Public Entry (Experimental)
+
+In this tutorial, you will learn how to restrict access to your main Backstage app bundle to authenticated users only.
+
+It is expected that the protected bundle feature will be refined in future development iterations, but for now, here is a simplified explanation of how it works:
+
+Your Backstage app bundle is split into two code entries:
+
+- Public entry point containing login pages;
+- There is also a protected main entry point that contains the code for what you see after signing in.
+
+With that, Backstage's cli and backend will detect public entry point and serve it to unauthenticated users, while serving the main, protected entry point only to authenticated users.
+
+## Requirements
+
+- The app needs to be served by the `app-backend` plugin, or this won't work;
+- Also it will only work for those using `backstage-cli` to build and serve their Backstage app.
+
+## Step-by-step
+
+1. Create a `index-public-experimental.tsx` in your app `src` folder.
+ :::note
+ The filename is a convention, so it is not currently configurable.
+ :::
+
+2. This file is the public entry point for your application, and it should only contain what unauthenticated users should see:
+
+ ```tsx title="in packages/app/src/index-public-experimental.tsx"
+ import ReactDOM from 'react-dom/client';
+ import { createApp } from '@backstage/app-defaults';
+ import { AppRouter } from '@backstage/core-app-api';
+ import {
+ AlertDisplay,
+ OAuthRequestDialog,
+ SignInPage,
+ } from '@backstage/core-components';
+ import {
+ configApiRef,
+ discoveryApiRef,
+ createApiFactory,
+ } from '@backstage/core-plugin-api';
+ import { CookieAuthRedirect } from '@backstage/plugin-auth-react';
+
+ // Notice that this is only setting up what is needed by the sign-in pages
+ const app = createApp({
+ // If you have any custom APIs that your sign-in page depends on, you need to add them here
+ apis: [],
+ components: {
+ SignInPage: props => {
+ return (
+
+ );
+ },
+ },
+ });
+
+ const App = app.createRoot(
+ <>
+
+
+
+ {/* This component triggers an authenticated redirect to the main app, while staying on the same URL */}
+
+
+ >,
+ );
+
+ ReactDOM.createRoot(document.getElementById('root')!).render( );
+ ```
+
+ :::note
+ The frontend will handle cookie refreshing automatically, so you don't have to worry about it.
+ :::
+
+3. Let's verify that everything is working locally. From your project root folder, run the following commands to build the app and start the backend:
+
+ ```sh
+ # building the app package
+ yarn workspace app start
+ # starting the backend api
+ yarn start-backend
+ ```
+
+4. Visit http://localhost:7007 to see the public app and validate that the _index.html_ response only contains a minimal application.
+ :::note
+ Regular app serving will always serve protected apps without authenticating.
+ :::
+
+5. Finally, as soon as you log in, you will be redirected to the main app home page (inspect the page and see that the protected bundle was served from the app backend after the redirect).
+
+That's it!
diff --git a/docs/tutorials/enable-public-entry.md b/docs/tutorials/enable-public-entry.md
index e430768785..4e92071c93 100644
--- a/docs/tutorials/enable-public-entry.md
+++ b/docs/tutorials/enable-public-entry.md
@@ -4,6 +4,13 @@ title: Enabling a public entry point
description: A guide for how to experiment with public and protected Backstage app bundles
---
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./enable-public-entry--old.md)
+instead.
+::::
+
# Enable Public Entry (Experimental)
In this tutorial, you will learn how to restrict access to your main Backstage app bundle to authenticated users only.
@@ -33,51 +40,21 @@ With that, Backstage's cli and backend will detect public entry point and serve
```tsx title="in packages/app/src/index-public-experimental.tsx"
import ReactDOM from 'react-dom/client';
- import { createApp } from '@backstage/app-defaults';
- import { AppRouter } from '@backstage/core-app-api';
- import {
- AlertDisplay,
- OAuthRequestDialog,
- SignInPage,
- } from '@backstage/core-components';
- import {
- configApiRef,
- discoveryApiRef,
- createApiFactory,
- } from '@backstage/core-plugin-api';
- import { CookieAuthRedirect } from '@backstage/plugin-auth-react';
+ import { signInPageModule } from './overrides/SignInPage';
+ import { appModulePublicSignIn } from '@backstage/plugin-app/alpha';
+ import { createApp } from '@backstage/frontend-defaults';
- // Notice that this is only setting up what is needed by the sign-in pages
const app = createApp({
- // If you have any custom APIs that your sign-in page depends on, you need to add them here
- apis: [],
- components: {
- SignInPage: props => {
- return (
-
- );
- },
- },
+ features: [signInPageModule, appModulePublicSignIn],
});
- const App = app.createRoot(
- <>
-
-
-
- {/* This component triggers an authenticated redirect to the main app, while staying on the same URL */}
-
-
- >,
+ ReactDOM.createRoot(document.getElementById('root')!).render(
+ app.createRoot(),
);
-
- ReactDOM.createRoot(document.getElementById('root')!).render( );
```
+ The `signInPageModule` is your custom sign-in page extension that you should already have configured in your app. The `appModulePublicSignIn` from `@backstage/plugin-app/alpha` provides the `CookieAuthRedirect` component that triggers an authenticated redirect to the main app after sign-in.
+
:::note
The frontend will handle cookie refreshing automatically, so you don't have to worry about it.
:::
@@ -99,19 +76,3 @@ With that, Backstage's cli and backend will detect public entry point and serve
5. Finally, as soon as you log in, you will be redirected to the main app home page (inspect the page and see that the protected bundle was served from the app backend after the redirect).
That's it!
-
-## New Frontend System
-
-If your app uses the new frontend system, you can still use the public entry point feature. The `index-public-experimental.tsx` file does end up looking a bit different in this case:
-
-```tsx title="in packages/app/src/index-public-experimental.tsx"
-import ReactDOM from 'react-dom/client';
-import { signInPageModule } from './overrides/SignInPage';
-import { createPublicSignInApp } from '@backstage/frontend-defaults';
-
-const app = createPublicSignInApp({
- features: [signInPageModule],
-});
-
-ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot());
-```
diff --git a/docs/tutorials/quickstart-app-plugin--old.md b/docs/tutorials/quickstart-app-plugin--old.md
new file mode 100644
index 0000000000..59fa6143b8
--- /dev/null
+++ b/docs/tutorials/quickstart-app-plugin--old.md
@@ -0,0 +1,315 @@
+---
+id: quickstart-app-plugin--old
+title: Adding Custom Plugin to Existing Monorepo App (Old Frontend System)
+description: Tutorial for adding a custom plugin to an existing Backstage monorepo application
+---
+
+::::info
+This documentation is for Backstage apps that still use the old frontend
+system. If your app uses the new frontend system, read the
+[current guide](./quickstart-app-plugin.md) instead.
+::::
+
+###### September 15th 2020 - v0.1.1-alpha.21
+
+
+
+> This document takes you through setting up a new plugin for your existing
+> monorepo with a _GitHub provider already setup_. If you don't have either of
+> those, you can clone
+> [simple-backstage-app](https://github.com/johnson-jesse/simple-backstage-app)
+> which this document builds on.
+>
+> This document does not cover authoring a plugin for sharing with the Backstage
+> community. That will have to be a later discussion.
+>
+> We start with a skeleton plugin install. And after verifying its
+> functionality, extend the Sidebar to make our life easy. Finally, we add
+> custom code to display GitHub repository information.
+>
+> This document assumes you have Node.js 16 active along with Yarn and Python.
+> Please note, that at the time of this writing, the current version is
+> 0.1.1-alpha.21. This guide can still be used with future versions, just,
+> verify as you go. If you run into issues, you can compare your setup with mine
+> here >
+> [simple-backstage-app-plugin](https://github.com/johnson-jesse/simple-backstage-app-plugin).
+
+## The Skeleton Plugin
+
+1. Start by using the built-in creator. From the terminal and root of your
+ project run: `yarn new` and select `frontend-plugin`.
+1. Enter a plugin ID. I used `github-playground`
+1. When the process finishes, let's start the backend:
+ `yarn --cwd packages/backend start`
+1. If you see errors starting, refer to
+ [Auth Configuration](https://backstage.io/docs/auth/) for more information on
+ environment variables.
+1. And now the frontend, from a new terminal window and the root of your
+ project: `yarn start`
+1. As usual, a browser window should popup loading the App.
+1. Now manually navigate to our plugin page from your browser:
+ `http://localhost:3000/github-playground`
+1. You should see successful verbiage for this endpoint,
+ `Welcome to github-playground!`
+
+## The Shortcut
+
+Let's add a shortcut.
+
+1. Open and modify `root: packages > app > src > components > Root.tsx` with the
+ following:
+
+```tsx
+import GitHubIcon from '@material-ui/icons/GitHub';
+...
+
+```
+
+Simple! The App will reload with your changes automatically. You should now see
+a GitHub icon displayed in the sidebar. Clicking that will link to our new
+plugin. And now, the API fun begins.
+
+## The Identity
+
+Our first modification will be to extract information from the Identity API.
+
+1. Start by opening
+ `root: plugins > github-playground > src > components > ExampleComponent > ExampleComponent.tsx`
+1. Add two new imports
+
+```tsx
+// Add identityApiRef to the list of imported from core
+import { identityApiRef, useApi } from '@backstage/core-plugin-api';
+```
+
+3. Adjust the ExampleComponent from inline to block
+
+_from inline:_
+
+```tsx
+const ExampleComponent = () => ( ... )
+```
+
+_to block:_
+
+```tsx
+const ExampleComponent = () => {
+
+ return (
+ ...
+ )
+}
+```
+
+4. Now add our hook and const data before the return statement
+
+```tsx
+// our API hook
+const identityApi = useApi(identityApiRef);
+
+// data to use
+const userId = identityApi.getUserId();
+const profile = identityApi.getProfile();
+```
+
+5. Finally, update the InfoCard's jsx to use our new data
+
+```tsx
+
+
+ {`${profile.displayName} | ${profile.email}`}
+
+
+```
+
+If everything is saved, you should see your name, id, and email on the
+github-playground page. Our data accessed is synchronous. So we just grab and
+go.
+
+https://github.com/backstage/backstage/tree/master/contrib
+
+6. Here is the entire file for reference
+ [ExampleComponent.tsx](https://github.com/backstage/backstage/tree/master/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md)
+
+## The Wipe
+
+The last file we will touch is ExampleFetchComponent. Because of the number of
+changes, let's start by wiping this component clean.
+
+1. Start by opening
+ `root: plugins > github-playground > src > components > ExampleFetchComponent > ExampleFetchComponent.tsx`
+1. Replace everything in the file with the following:
+
+```tsx
+import useAsync from 'react-use/lib/useAsync';
+import Alert from '@material-ui/lab/Alert';
+import { Table, TableColumn, Progress } from '@backstage/core-components';
+import { githubAuthApiRef, useApi } from '@backstage/core-plugin-api';
+import { graphql } from '@octokit/graphql';
+
+export const ExampleFetchComponent = () => {
+ return Nothing to see yet
;
+};
+```
+
+3. Save that and ensure you see no errors. Comment out the unused imports if
+ your linter gets in the way.
+
+###### We will add a lot to this file for the sake of ease. Please don't do this in production code!
+
+## The Graph Model
+
+GitHub has a GraphQL API available for interacting. Let's start by adding our
+basic repository query
+
+1. Add the query const statement outside ExampleFetchComponent
+
+```tsx
+const query = `{
+ viewer {
+ repositories(first: 100) {
+ totalCount
+ nodes {
+ name
+ createdAt
+ description
+ diskUsage
+ isFork
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ }
+}`;
+```
+
+2. Using this structure as a guide, we will break our query into type parts
+3. Add the following outside of ExampleFetchComponent
+
+```tsx
+type Node = {
+ name: string;
+ createdAt: string;
+ description: string;
+ diskUsage: number;
+ isFork: boolean;
+};
+
+type Viewer = {
+ repositories: {
+ totalCount: number;
+ nodes: Node[];
+ pageInfo: {
+ endCursor: string;
+ hasNextPage: boolean;
+ };
+ };
+};
+```
+
+## The Table Model
+
+Using Backstage's own component library, let's define a custom table. This
+component will get used if we have data to display.
+
+1. Add the following outside of ExampleFetchComponent
+
+```tsx
+type DenseTableProps = {
+ viewer: Viewer;
+};
+
+export const DenseTable = ({ viewer }: DenseTableProps) => {
+ const columns: TableColumn[] = [
+ { title: 'Name', field: 'name' },
+ { title: 'Created', field: 'createdAt' },
+ { title: 'Description', field: 'description' },
+ { title: 'Disk Usage', field: 'diskUsage' },
+ { title: 'Fork', field: 'isFork' },
+ ];
+
+ return (
+
+ );
+};
+```
+
+## The Fetch
+
+We're ready to flush out our fetch component
+
+1. Add our api hook inside ExampleFetchComponent
+
+```tsx
+const auth = useApi(githubAuthApiRef);
+```
+
+2. The access token we need to make our GitHub request and the request itself is
+ obtained in an asynchronous manner.
+3. Add the `useAsync` block inside the ExampleFetchComponent
+
+```tsx
+const { value, loading, error } = useAsync(async (): Promise => {
+ const token = await auth.getAccessToken();
+
+ const gqlEndpoint = graphql.defaults({
+ // Uncomment baseUrl if using enterprise
+ // baseUrl: 'https://github.MY-BIZ.com/api',
+ headers: {
+ authorization: `token ${token}`,
+ },
+ });
+ const { viewer } = await gqlEndpoint(query);
+ return viewer;
+}, []);
+```
+
+4. The resolved data is conveniently destructured with `value` containing our
+ Viewer type. `loading` as a boolean, self explanatory. And `error` which is
+ present only if necessary. So let's use those as the first 3 of 4 multi
+ return statements.
+5. Add the _if return_ blocks below our async block
+
+```tsx
+if (loading) return ;
+if (error) return {error.message} ;
+if (value && value.repositories) return ;
+```
+
+6. The third line here utilizes our custom table accepting our Viewer type.
+7. Finally, we add our _else return_ block to catch any other scenarios.
+
+```tsx
+return (
+
+);
+```
+
+8. After saving that, and given we don't have any errors, you should see a table
+ with basic information on your repositories.
+9. Here is the entire file for reference
+ [ExampleFetchComponent.tsx](https://github.com/backstage/backstage/tree/master/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md)
+10. We finished! You should see your own GitHub repository's information
+ displayed in a basic table. If you run into issues, you can compare the repo
+ that backs this document,
+ [simple-backstage-app-plugin](https://github.com/johnson-jesse/simple-backstage-app-plugin)
+
+## Where to go from here
+
+> Break apart ExampleFetchComponent into smaller logical parts contained in
+> their own files. Rename your components to something other than ExampleXxx.
+>
+> You might be really proud of a plugin you develop. Consider sharing it with
+> the Backstage community by contributing to the [community-plugins repository](https://github.com/backstage/community-plugins).
diff --git a/docs/tutorials/quickstart-app-plugin.md b/docs/tutorials/quickstart-app-plugin.md
index 1f460173cd..20e2cea453 100644
--- a/docs/tutorials/quickstart-app-plugin.md
+++ b/docs/tutorials/quickstart-app-plugin.md
@@ -1,37 +1,30 @@
---
id: quickstart-app-plugin
title: Adding Custom Plugin to Existing Monorepo App
+description: Tutorial for adding a custom plugin to an existing Backstage monorepo application
---
-###### September 15th 2020 - v0.1.1-alpha.21
-
-
+::::info
+This documentation is written for the new frontend system, which is the default
+in new Backstage apps. If your Backstage app still uses the old frontend system,
+read the [old frontend system version of this guide](./quickstart-app-plugin--old.md)
+instead.
+::::
> This document takes you through setting up a new plugin for your existing
-> monorepo with a _GitHub provider already setup_. If you don't have either of
-> those, you can clone
-> [simple-backstage-app](https://github.com/johnson-jesse/simple-backstage-app)
-> which this document builds on.
+> monorepo with a _GitHub provider already setup_.
>
> This document does not cover authoring a plugin for sharing with the Backstage
> community. That will have to be a later discussion.
>
> We start with a skeleton plugin install. And after verifying its
-> functionality, extend the Sidebar to make our life easy. Finally, we add
-> custom code to display GitHub repository information.
->
-> This document assumes you have Node.js 16 active along with Yarn and Python.
-> Please note, that at the time of this writing, the current version is
-> 0.1.1-alpha.21. This guide can still be used with future versions, just,
-> verify as you go. If you run into issues, you can compare your setup with mine
-> here >
-> [simple-backstage-app-plugin](https://github.com/johnson-jesse/simple-backstage-app-plugin).
+> functionality, we add custom code to display GitHub repository information.
## The Skeleton Plugin
1. Start by using the built-in creator. From the terminal and root of your
project run: `yarn new` and select `frontend-plugin`.
-1. Enter a plugin ID. I used `github-playground`
+1. Enter a plugin ID. We'll use `github-playground` for this tutorial.
1. When the process finishes, let's start the backend:
`yarn --cwd packages/backend start`
1. If you see errors starting, refer to
@@ -40,27 +33,15 @@ title: Adding Custom Plugin to Existing Monorepo App
1. And now the frontend, from a new terminal window and the root of your
project: `yarn start`
1. As usual, a browser window should popup loading the App.
-1. Now manually navigate to our plugin page from your browser:
+1. Now manually navigate to the plugin page from your browser:
`http://localhost:3000/github-playground`
1. You should see successful verbiage for this endpoint,
`Welcome to github-playground!`
-## The Shortcut
-
-Let's add a shortcut.
-
-1. Open and modify `root: packages > app > src > components > Root.tsx` with the
- following:
-
-```tsx
-import GitHubIcon from '@material-ui/icons/GitHub';
-...
-
-```
-
-Simple! The App will reload with your changes automatically. You should now see
-a GitHub icon displayed in the sidebar. Clicking that will link to our new
-plugin. And now, the API fun begins.
+With the new frontend system, plugins are auto-discovered when installed as
+dependencies of your `packages/app` package. The plugin was already added there
+by `yarn new`, so the route and a sidebar item are available without any manual
+wiring in `App.tsx` or `Root.tsx`.
## The Identity
@@ -71,7 +52,6 @@ Our first modification will be to extract information from the Identity API.
1. Add two new imports
```tsx
-// Add identityApiRef to the list of imported from core
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
```
@@ -97,10 +77,8 @@ const ExampleComponent = () => {
4. Now add our hook and const data before the return statement
```tsx
-// our API hook
const identityApi = useApi(identityApiRef);
-// data to use
const userId = identityApi.getUserId();
const profile = identityApi.getProfile();
```
diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md
index bb416bf731..fef4b5b8e0 100644
--- a/docs/tutorials/using-backstage-proxy-within-plugin.md
+++ b/docs/tutorials/using-backstage-proxy-within-plugin.md
@@ -25,7 +25,7 @@ If your plugin requires access to an API, backstage offers
- [Option 2: Defining the API client interface](#defining-the-api-client-interface)
- [Creating the API client](#creating-the-api-client)
- [Bundling your ApiRef with your plugin](#bundling-your-apiref-with-your-plugin)
- - [Using the API in your components](#using-your-plugin-in-your-components)
+ - [Using the API in your components](#using-the-api-in-your-components)
## Setting up the backstage proxy
@@ -46,7 +46,7 @@ proxy:
You can find more details about the proxy config options in the
[proxying section](../plugins/proxying.md).
-# Calling an API using the backstage proxy
+## Calling an API using the backstage proxy
If you followed the previous steps, you should now be able to access your API by
calling `${backend-url}/api/proxy/`. The reason why
@@ -94,15 +94,15 @@ export const AwesomeUsersTable = () => {
This section describes the steps to wrap your API client in a [Utility API](../api/utility-apis.md), which are:
-- use [`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html) to create a
- new [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)
-- register an [`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) with
+- use [`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.createApiRef.html) to create a
+ new [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html)
+- register an [`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiFactory.html) with
your plugin using
- [`createApiFactory`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiFactory.html). This
+ [`createApiFactory`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.createApiFactory.html). This
will wrap your API implementation, associate your `ApiRef` with your
implementation and tell backstage how to instantiate it
- finally, you can use your API in your components by calling
- [`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html)
+ [`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.useApi.html)
### Defining the API client interface
@@ -187,8 +187,8 @@ export class MyAwesomeApiClient implements MyAwesomeApi {
```
> Check out the docs for more information on the
-> [DiscoveryApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.DiscoveryApi.html) or the
-> [FetchApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.FetchApi.html)
+> [DiscoveryApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.DiscoveryApi.html) or the
+> [FetchApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.FetchApi.html)
### Bundling your ApiRef with your plugin
@@ -233,7 +233,7 @@ export const myCustomPlugin = createPlugin({
### Using the API in your components
Now you should be able to access your API using the backstage hook
-[`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html) from within your plugin code.
+[`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.useApi.html) from within your plugin code.
```ts title="plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx"
import { useApi } from '@backstage/core-plugin-api';
diff --git a/lighthouserc.js b/lighthouserc.js
index c7c1275cdb..fc00cd2b9d 100644
--- a/lighthouserc.js
+++ b/lighthouserc.js
@@ -25,19 +25,21 @@ module.exports = {
'http://localhost:3000/docs',
'http://localhost:3000/docs/default/component/backstage',
/** Software Templates */
- 'http://localhost:3000/create',
- 'http://localhost:3000/create/tasks',
- 'http://localhost:3000/create/actions',
- 'http://localhost:3000/create/edit',
- 'http://localhost:3000/create/templating-extensions',
- 'http://localhost:3000/create/templates/default/react-ssr-template',
+ // TODO(Rugvip): Figure out why these don't work after the BUI header switch
+ // 'http://localhost:3000/create',
+ // 'http://localhost:3000/create/tasks',
+ // 'http://localhost:3000/create/actions',
+ // 'http://localhost:3000/create/edit',
+ // 'http://localhost:3000/create/templating-extensions',
+ // 'http://localhost:3000/create/templates/default/react-ssr-template',
/** Search */
'http://localhost:3000/search',
/** Miscellaneous */
- 'http://localhost:3000/settings',
+ // TODO(Rugvip): Figure out why these don't work after the BUI header switch
+ // 'http://localhost:3000/settings/general',
/** plugin-devtools */
- 'http://localhost:3000/devtools',
- 'http://localhost:3000/devtools/config',
+ // 'http://localhost:3000/devtools/info',
+ // 'http://localhost:3000/devtools/config',
/** plugin-explore */
'http://localhost:3000/explore',
'http://localhost:3000/explore/groups',
@@ -54,7 +56,7 @@ module.exports = {
preset: 'desktop',
},
startServerCommand: 'yarn start',
- startServerReadyPattern: 'webpack compiled successfully',
+ startServerReadyPattern: 'compiled.*successfully',
startServerReadyTimeout: 600000,
numberOfRuns: 1,
puppeteerScript: './.lighthouseci/scripts/guest-auth.js',
diff --git a/microsite/README.md b/microsite/README.md
index 62a28fb6a3..42b39acfa4 100644
--- a/microsite/README.md
+++ b/microsite/README.md
@@ -13,6 +13,7 @@ This website was created with [Docusaurus](https://docusaurus.io/).
- [Editing Content](#editing-content)
- [Adding Content](#adding-content)
- [Full Documentation](#full-documentation)
+- [How to patch the stable docs](#patching-the-docs)
# Getting Started
@@ -240,3 +241,7 @@ Full documentation can be found on the [Docusaurus website](https://docusaurus.i
A feedback widget is provided by the `docusaurus-pushfeedback` plugin, which connects to the https://pushfeedback.com service. Styling of the button is configured in `microsite/src/theme/customTheme.scss`, while the plugin itself is configured in `microsite/docusaurus.config.js`.
Feedback submissions are connected to an account owned by @Rugvip and are available on request, reach out to @Rugvip on [Discord](https://discord.gg/backstage-687207715902193673).
+
+# Patching the docs
+
+To patch the documentation for the stable version, you can submit a PR to the correct `patch/v` branch. For example, if `1.48.0` is the stable version, you would submit a fix to the branch `patch/v1.48.0`. Once your fix is in, you can trigger the `deploy_microsite` GH action (or wait until the next run on master) and you should see the fix on the stable version at https://backstage.io/docs.
diff --git a/microsite/blog/2020-03-18-what-is-backstage.mdx b/microsite/blog/2020-03-18-what-is-backstage.mdx
index caac489ca1..2fd04768d6 100644
--- a/microsite/blog/2020-03-18-what-is-backstage.mdx
+++ b/microsite/blog/2020-03-18-what-is-backstage.mdx
@@ -36,7 +36,7 @@ Our internal installation of Backstage has over 100 different integrations — w
3. Centralised technical documentation
4. Review performance of your team’s mobile features
-These are just a few examples. Expect us to continue providing examples of how Backstage is used inside Spotify while we build out more end-2-end use-cases in the open.
+These are just a few examples. Expect us to continue providing examples of how Backstage is used inside Spotify while we build out more end-to-end use-cases in the open.
### 1. Creating a new microservice
diff --git a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx
index d01bd496d1..735191a3e2 100644
--- a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx
+++ b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx
@@ -50,7 +50,7 @@ yarn start
And you are good to go! 👍
-Read the full documentation on how to [create an app](/docs/getting-started/create-an-app) on GitHub.
+Read the full documentation on how to [create an app](/docs/getting-started/) on GitHub.
## What do I get? (Let's get technical...)
diff --git a/microsite/blog/2023-04-26-kubecon-eu-2023.mdx b/microsite/blog/2023-04-26-kubecon-eu-2023.mdx
index 8f369c2d14..983e546bd2 100644
--- a/microsite/blog/2023-04-26-kubecon-eu-2023.mdx
+++ b/microsite/blog/2023-04-26-kubecon-eu-2023.mdx
@@ -1,7 +1,7 @@
---
title: 'ICYMI: KubeCon EU 2023'
author: Bailey Brooks, Spotify
-authorURL: http://github.com/bailey
+authorURL: https://github.com/bailey
authorImageURL: https://pbs.twimg.com/profile_images/1477424303192694785/qCfN6XWW_400x400.jpg
---
diff --git a/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx b/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx
index 169c907e40..d7297ad145 100644
--- a/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx
+++ b/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx
@@ -21,19 +21,19 @@ After exploring several service routes from managed to in-house/owned, CTC opted
The CTC DevOps team created a small special interest group to champion the developer portal build and quickly delved into the big issues impacting their developer experience. From there, the team began creating tasks and templates within Backstage.
-To start, Scott worked with end-user teams outside DevOps early in the process of documenting migration to the new DevOps Kubernetes platform services. When Scott wrote the onboarding documentation, he made it a point to pair with a developer on another team that would be using it. This approach provided him with a quality sounding board, great instantaneous feedback, and a sort of "beta developer" to test V1 documentation clarity., A few weeks later, another colleague on the same end-user team followed the documentation and she didn't reach out to Scott at all.
+To start, Scott worked with end-user teams outside DevOps early in the process of documenting migration to the new DevOps Kubernetes platform services. When Scott wrote the onboarding documentation, he made it a point to pair with a developer on another team that would be using it. This approach provided him with a quality sounding board, great instantaneous feedback, and a sort of "beta developer" to test V1 documentation clarity. A few weeks later, another colleague on the same end-user team followed the documentation and she didn't reach out to Scott at all.
After this early win, the team was ready for broad distribution. Partnering with the senior leadership team, Scott's team began onboarding teams to the new DevOps Kubernetes platform using the templates his team created in Backstage. At CTC, their Backstage instance is set up to automatically scan for deployments in Kubernetes; so if you're in Kubernetes, you're onboarded to Backstage.
-"Backstage made onboarding [to the new DevOps platform] not scary,"" Scott said. "Because now — all of a sudden — you have this recipe for how to do it, you don't have to jump through these hoops, the templates are there and ready for you. You choose what you want based on these templates we have available, and you're off and running on your own.""
+"Backstage made onboarding [to the new DevOps platform] not scary," Scott said. "Because now — all of a sudden — you have this recipe for how to do it, you don't have to jump through these hoops, the templates are there and ready for you. You choose what you want based on these templates we have available, and you're off and running on your own."
Scott's team monitored progress against their onboarding goals partially by how often the DevOps team was getting pinged for support and found that it has made support smoother. They can easily refer people to templates and repo standards instead of making them create something ad hoc.
-"It's really been cool seeing how that progressed. I even have a few teams that didn't talk to DevOps at all. They used a [template] and they have a service ready. We were actually pretty excited about that, because that means now we have a scalable tool able to onboard others onto our platform."" said Scott.
+"It's really been cool seeing how that progressed. I even have a few teams that didn't talk to DevOps at all. They used a [template] and they have a service ready. We were actually pretty excited about that, because that means now we have a scalable tool able to onboard others onto our platform," said Scott.
-Another exciting win for CTC was template additions generated outside the initial toolkit, which has eased the burden substantially for the DevOps team. They were able to create new templates to deploy against best practices such as a template for creating a brand-new Terraform module and Git repo or — Scott's favorite — a template that creates a Java repository along with Jenkins jobs for CI and the Flux CD config for deployment.
+Another exciting win for CTC was template additions generated outside the initial toolkit, which has eased the burden substantially for the DevOps team. They were able to create new templates to deploy against best practices such as a template for creating a brand-new Terraform module and Git repo or — Scott's favorite — a template that creates a Java repository along with Jenkins jobs for CI and the Flux CD config for deployment.
-"The template builds and auto-deploys a Docker image, so if you make a change to your mainline branch, then it will automatically build a new Docker image and deploy it to a Kubernetes cluster."" Scott said. "So within basically 10 minutes of you filling out a form, you actually have something deployed out to a Kubernetes cluster.""
+"The template builds and auto-deploys a Docker image, so if you make a change to your mainline branch, then it will automatically build a new Docker image and deploy it to a Kubernetes cluster," Scott said. "So within basically 10 minutes of you filling out a form, you actually have something deployed out to a Kubernetes cluster."
This process eased flows for both end-user devs and DevOps teams overall.
@@ -49,11 +49,11 @@ For CTC, the journey with Backstage has just begun. They're now looking to drive
When asked about advice he has for other Backstage adopters, Scott talked about the DevOps team's comms strategy and ensuring end-user developers had pathways to provide feedback. In addition to the focus on templates for the new services, having both a dedicated Slack channel and open-door communication with the DevOps team helped reduce onboarding friction to the DevOps Kubernetes platform.
-Outside of that, he believes the approach to documentation is paramount. "Write your documentation from the perspective of, 'Hey, you have this task assigned to you to write a template, here are the exact steps you have to follow.'"" Scott said. "Think of it as a recipe. Removing your familiarity bias will help make this tool more useful for less familiar teams.""
+Outside of that, he believes the approach to documentation is paramount. "Write your documentation from the perspective of, 'Hey, you have this task assigned to you to write a template, here are the exact steps you have to follow'," Scott said. "Think of it as a recipe. Removing your familiarity bias will help make this tool more useful for less familiar teams."
Finally, don't be afraid to step outside the core use cases when it comes to the Backstage framework and finding solutions to meet your needs.
-"Backstage gives you a lot of really easy to use features right out of the box. And one of the great things about open source is you can look at the code and see exactly what the behavior is and work within that behavior. But we've also developed our own processes for custom tasks because — at the end of the day — our platform has some very custom aspects to it."" Scott said.
+"Backstage gives you a lot of really easy to use features right out of the box. And one of the great things about open source is you can look at the code and see exactly what the behavior is and work within that behavior. But we've also developed our own processes for custom tasks because — at the end of the day — our platform has some very custom aspects to it," Scott said.
Interested in more stories from Backstage adopters? Check out these recent posts from [Stash](https://backstage.io/blog/2023/07/08/stash-adopter-post) and [Expedia Group](https://backstage.io/blog/2023/08/17/expedia-proof-of-value-metrics-2).
diff --git a/microsite/blog/2025-12-30-backstage-wrapped-2025.mdx b/microsite/blog/2025-12-30-backstage-wrapped-2025.mdx
index 0f8bdd21cf..d4b66c6a70 100644
--- a/microsite/blog/2025-12-30-backstage-wrapped-2025.mdx
+++ b/microsite/blog/2025-12-30-backstage-wrapped-2025.mdx
@@ -43,7 +43,7 @@ Couldn't spot your name in the videos? Here's one more chance — every contribu
## Mature for a five-year-old
-
+
_The growing Backstage ecosystem, by the numbers_
Earlier this year, the Backstage project [celebrated its 5th birthday][bday] on the main stage at [KubeCon + CloudNativeCon in London][kcon-eu].
diff --git a/microsite/blog/2026-02-25-get-a-jump-on-contribfest.mdx b/microsite/blog/2026-02-25-get-a-jump-on-contribfest.mdx
new file mode 100644
index 0000000000..67941fa436
--- /dev/null
+++ b/microsite/blog/2026-02-25-get-a-jump-on-contribfest.mdx
@@ -0,0 +1,48 @@
+---
+title: 'Get a jump on ContribFest with the new web app'
+author: Elaine de Mattos Silva Bezerra, DB Systel GmbH, Heikki Hellgren, OP Financial Group & André Wanlin, Spotify
+---
+
+
+
+Become a Contrib Champ and join us at ContribFest, where commits become legendary!
+
+We are once again hosting ContribFest at KubeCon + CloudNativeCon. This time around, it's taking place in Amsterdam on March 26, 2026, at 13:45 CET — make sure to [add it to your schedule](https://kccnceu2026.sched.com/event/2EF7v/contribfest-supercharge-your-open-source-impact-backstage-contribfest-live-andre-wanlin-emma-indal-spotify-heikki-hellgren-op-financial-group-elaine-bezerra-db-systel-gmbh?iframe=no). Learn more about what to expect below and get started now by exploring the new [ContribFest web app](https://contribfest.backstage.io/).
+
+{/* truncate */}
+
+## Introducing the ContribFest web app
+
+We're excited to announce the new ContribFest web app: [https://contribfest.backstage.io/](https://contribfest.backstage.io/). The app simplifies local setup and helps you quickly find good issues to work on from the curated list pre-selected by your ContribFest co-hosts.
+
+You'll see that the app is broken down into five sections:
+
+- [Welcome](https://contribfest.backstage.io/): This is where you'll find links to all the things, including the session's slide deck, assignment sheet, the Backstage and Community Plugins repositories, and their respective contribution guides.
+- [Getting Started](https://contribfest.backstage.io/getting-started/): Whether you are new to Backstage or an old hat, use this handy checklist to help you get your local environment set up for contributing, including all the commands. (Make sure you check all the boxes, you never know what might happen! 😉)
+- [Curated Issues](https://contribfest.backstage.io/issues/): This is what you come to the session for: finding an issue that speaks to you and contributing towards it. This section has a list of issues that we've curated — and filters, so you can slice and dice the list to find the perfect issue to work on.
+- [Contrib Champs](https://contribfest.backstage.io/contrib-champs/): We've hosted three other ContribFests in the past — this is where you'll find merged PRs from those sessions, a place to celebrate contributions. Make sure to tag your PRs with “ContribFest”, and maybe your name will show up here one day, too! 🏆
+- [Hall of Hosts](https://contribfest.backstage.io/hall-of-hosts/): ContribFest would not take place without the various community members who have stepped up to help co-host the sessions. This is where you'll see an honor roll of past co-hosts. 🙏
+
+## About those Contrib Champs
+
+The goals of the Backstage ContribFest sessions are many — foster community, work with experts, etc. — but it's pretty obvious that contributions are the most important. It's in the name after all. Here are a few past contributions that we wanted to share to give you an idea of what that looks like:
+
+- [#27694](https://github.com/backstage/backstage/pull/27694) by [hyb175](https://github.com/hyb175) — Add Pagination to Tech Docs Table: for those with lots of entities with TechDocs, this is a massive performance improvement.
+- [#29470](https://github.com/backstage/backstage/pull/29470) by [ioboi](https://github.com/ioboi) — Openshift Auth provider: this allows those using OpenShift to use it to sign into their Backstage instance.
+- [#31770](https://github.com/backstage/backstage/pull/31770) by [theZMC](https://github.com/theZMC) — Render HTML in GitHub-flavored Markdown: with this change in place, HTML will now render correctly in the MarkdownContent component when you are using the GitHub-flavored Markdown mode.
+
+Check out the [Contrib Champs page](https://contribfest.backstage.io/contrib-champs/) to see the full list!
+
+## Using Dev Containers
+
+Along with the new ContribFest web app, we are also looking to use Dev Containers this time around to help streamline the session for those who'd like to use that option to get started. On the [Getting Started page](https://contribfest.backstage.io/getting-started/), pick the Dev Containers radio button and then follow the checklist. To give you a quick preview, you'll need to have the following installed:
+
+- Git, you'll need this to be able to pull down the code
+- Docker Desktop (or Docker Engine on Linux)
+- VS Code with the Dev Containers extension or IntelliJ IDEA Ultimate
+
+Check out our [Dev Containers tutorial](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/devcontainer.md) for a deeper dive into the subject.
+
+## Amsterdam, here we come!
+
+On behalf of the Backstage ContribFest co-host team, thank you for following along. We're looking forward to meeting you in Amsterdam and working together on your contributions. Please be sure to introduce yourself!
diff --git a/microsite/blog/assets/2026-02-25/backstage-contribfest-kubecon-web-app-header.png b/microsite/blog/assets/2026-02-25/backstage-contribfest-kubecon-web-app-header.png
new file mode 100644
index 0000000000..21e726aeb7
Binary files /dev/null and b/microsite/blog/assets/2026-02-25/backstage-contribfest-kubecon-web-app-header.png differ
diff --git a/microsite/data/plugins/aws-amazon-ecr.yaml b/microsite/data/plugins/aws-amazon-ecr.yaml
new file mode 100644
index 0000000000..1749b84f04
--- /dev/null
+++ b/microsite/data/plugins/aws-amazon-ecr.yaml
@@ -0,0 +1,11 @@
+---
+title: Amazon Elastic Container Registry
+author: Amazon Web Services
+authorUrl: https://aws.amazon.com/
+category: Infrastructure
+description: View Amazon ECR repositories and container image scan findings for your components in Backstage.
+documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/ecr#readme
+iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/ecr.png
+npmPackageName: '@aws/amazon-ecr-plugin-for-backstage'
+addedDate: '2026-03-26'
+status: active
diff --git a/microsite/data/plugins/aws-config.yaml b/microsite/data/plugins/aws-config.yaml
new file mode 100644
index 0000000000..a57a601c4b
--- /dev/null
+++ b/microsite/data/plugins/aws-config.yaml
@@ -0,0 +1,11 @@
+---
+title: AWS Config
+author: Amazon Web Services
+authorUrl: https://aws.amazon.com/
+category: Infrastructure
+description: Ingest AWS resources from AWS Config into the Backstage catalog using incremental ingestion.
+documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/core/catalog-config#readme
+iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/config.png
+npmPackageName: '@aws/aws-config-catalog-module-for-backstage'
+addedDate: '2026-03-26'
+status: active
diff --git a/microsite/data/plugins/aws-genai.yaml b/microsite/data/plugins/aws-genai.yaml
new file mode 100644
index 0000000000..f881ccc769
--- /dev/null
+++ b/microsite/data/plugins/aws-genai.yaml
@@ -0,0 +1,11 @@
+---
+title: Generative AI
+author: Amazon Web Services
+authorUrl: https://aws.amazon.com/
+category: Machine Learning
+description: Build generative AI assistants in Backstage that leverage the plugin ecosystem with tool use and LLM integration.
+documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/genai#readme
+iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/bedrock.png
+npmPackageName: '@aws/genai-plugin-for-backstage'
+addedDate: '2026-03-26'
+status: active
diff --git a/microsite/data/plugins/aws-securityhub.yaml b/microsite/data/plugins/aws-securityhub.yaml
new file mode 100644
index 0000000000..b2cec6e6ba
--- /dev/null
+++ b/microsite/data/plugins/aws-securityhub.yaml
@@ -0,0 +1,11 @@
+---
+title: AWS Security Hub
+author: Amazon Web Services
+authorUrl: https://aws.amazon.com/
+category: Security
+description: View and manage AWS Security Hub findings for your resources with severity filtering and AI remediation.
+documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/securityhub#readme
+iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/securityhub.png
+npmPackageName: '@aws/aws-securityhub-plugin-for-backstage'
+addedDate: '2026-03-26'
+status: active
diff --git a/microsite/data/plugins/balajisiva-backstage-plugin-template-builder.yaml b/microsite/data/plugins/balajisiva-backstage-plugin-template-builder.yaml
new file mode 100644
index 0000000000..6b8227c629
--- /dev/null
+++ b/microsite/data/plugins/balajisiva-backstage-plugin-template-builder.yaml
@@ -0,0 +1,11 @@
+---
+title: Template Builder
+author: Balaji Sivasubramanian
+authorUrl: https://github.com/balajisiva
+category: Tooling
+description: Visual editor for creating and managing Backstage scaffolder templates with live YAML preview, flow visualization, and GitHub integration
+documentation: https://github.com/balajisiva/backstage-template-builder#readme
+iconUrl: https://raw.githubusercontent.com/balajisiva/backstage-template-builder/main/plugins/backstage-template-builder/icon.svg
+npmPackageName: '@balajisiva/backstage-plugin-template-builder'
+addedDate: '2026-02-16'
+status: active
diff --git a/microsite/data/plugins/c2l2c-dora-metrics.yaml b/microsite/data/plugins/c2l2c-dora-metrics.yaml
new file mode 100644
index 0000000000..81455c7c3d
--- /dev/null
+++ b/microsite/data/plugins/c2l2c-dora-metrics.yaml
@@ -0,0 +1,11 @@
+---
+title: DORA Metrics
+author: C2L2C
+authorUrl: https://github.com/C2L2C
+category: Monitoring
+description: Per-entity DORA metrics for catalog services. Computes Deployment Frequency, Lead Time, CFR & MTTR from GitHub PRs — multi-environment, trend history, no extra backend.
+documentation: https://github.com/C2L2C/backstage-plugin-dora-metrics#readme
+iconUrl: https://raw.githubusercontent.com/tailwindlabs/heroicons/master/src/24/outline/chart-bar.svg
+npmPackageName: '@c2l2c/backstage-plugin-dora-metrics'
+addedDate: '2026-03-18'
+status: active
diff --git a/microsite/data/plugins/flagsmith.yaml b/microsite/data/plugins/flagsmith.yaml
new file mode 100644
index 0000000000..42f9a8523a
--- /dev/null
+++ b/microsite/data/plugins/flagsmith.yaml
@@ -0,0 +1,11 @@
+---
+title: Flagsmith
+author: Flagsmith
+authorUrl: https://www.flagsmith.com
+category: Feature Flags
+description: View Flagsmith feature flags and usage statistics inside Backstage.
+documentation: https://docs.flagsmith.com/third-party-integrations/backstage
+iconUrl: '/img/flagsmith.svg'
+npmPackageName: '@flagsmith/backstage-plugin'
+addedDate: '2026-02-26'
+status: active
diff --git a/microsite/data/plugins/github-actions.yaml b/microsite/data/plugins/github-actions.yaml
index 6b1af3fe36..55503f4fbb 100644
--- a/microsite/data/plugins/github-actions.yaml
+++ b/microsite/data/plugins/github-actions.yaml
@@ -4,7 +4,7 @@ author: Spotify
authorUrl: https://github.com/spotify
category: CI/CD
description: GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub.
-documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/github-actions/plugins/github-actions
+documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/github/plugins/github-actions
iconUrl: https://avatars2.githubusercontent.com/u/44036562?s=400&v=4
npmPackageName: '@backstage-community/plugin-github-actions'
addedDate: '2021-01-20'
diff --git a/microsite/data/plugins/graphiql.yaml b/microsite/data/plugins/graphiql.yaml
index 12c2f106a5..8d0a37cb65 100644
--- a/microsite/data/plugins/graphiql.yaml
+++ b/microsite/data/plugins/graphiql.yaml
@@ -5,7 +5,7 @@ authorUrl: https://github.com/spotify
category: Debugging
description: Integrates GraphiQL as a tool to browse GraphQL API endpoints inside Backstage.
documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/graphiql/plugins/graphiql
-iconUrl: https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/GraphQL_Logo.svg/1024px-GraphQL_Logo.svg.png
+iconUrl: https://upload.wikimedia.org/wikipedia/commons/1/17/GraphQL_Logo.svg
npmPackageName: '@backstage-community/plugin-graphiql'
addedDate: '2020-11-03'
status: active
diff --git a/microsite/data/plugins/n8n.yaml b/microsite/data/plugins/n8n.yaml
new file mode 100644
index 0000000000..7898c1efe4
--- /dev/null
+++ b/microsite/data/plugins/n8n.yaml
@@ -0,0 +1,11 @@
+---
+title: n8n
+author: André Ahlert Junior
+authorUrl: https://github.com/andreahlert
+category: Other
+description: n8n workflow automation — view workflows and execution history on Backstage entity pages.
+documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/n8n/plugins/n8n
+iconUrl: /img/logo-gradient-on-dark.svg
+npmPackageName: '@backstage-community/plugin-n8n'
+addedDate: '2026-02-15'
+status: active
diff --git a/microsite/data/plugins/rulehub-policy-catalog.yaml b/microsite/data/plugins/rulehub-policy-catalog.yaml
new file mode 100644
index 0000000000..3474ebe3db
--- /dev/null
+++ b/microsite/data/plugins/rulehub-policy-catalog.yaml
@@ -0,0 +1,10 @@
+title: RuleHub Policy Catalog
+author: Rulehub
+authorUrl: https://github.com/rulehub
+category: Security
+description: Backstage frontend plugin that displays a policy/compliance catalog from a static JSON index.
+documentation: https://github.com/rulehub/rulehub-backstage-plugin#readme
+iconUrl: https://github.com/rulehub.png
+npmPackageName: '@rulehub/rulehub-backstage-plugin'
+addedDate: '2026-03-06'
+status: active
diff --git a/microsite/data/plugins/spring-batch-dashboard.yaml b/microsite/data/plugins/spring-batch-dashboard.yaml
new file mode 100644
index 0000000000..ff5885ffc7
--- /dev/null
+++ b/microsite/data/plugins/spring-batch-dashboard.yaml
@@ -0,0 +1,11 @@
+---
+title: Spring Batch Dashboard
+author: zc149
+authorUrl: https://github.com/zc149
+category: Monitoring
+description: Monitor and manage Spring Batch jobs from Backstage.
+documentation: https://github.com/zc149/backstage-plugin-spring-batch-dashboard
+iconUrl: https://raw.githubusercontent.com/zc149/backstage-plugin-spring-batch-dashboard/main/.github/images/icon.png
+npmPackageName: '@jikwan/backstage-plugin-spring-batch-dashboard'
+addedDate: '2026-03-11'
+status: active
diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts
index ce75a2e063..3a47b62627 100644
--- a/microsite/docusaurus.config.ts
+++ b/microsite/docusaurus.config.ts
@@ -19,6 +19,8 @@
/** @type{import('prism-react-renderer').PrismTheme} **/
// @ts-ignore
import { themes } from 'prism-react-renderer';
+import { cpSync, existsSync } from 'node:fs';
+import { resolve as resolvePath } from 'node:path';
import type * as Preset from '@docusaurus/preset-classic';
import { Config } from '@docusaurus/types';
import RedirectPlugin from '@docusaurus/plugin-client-redirects';
@@ -28,7 +30,9 @@ import type * as OpenApiPlugin from 'docusaurus-plugin-openapi-docs';
const backstageTheme = themes.vsDark;
backstageTheme.plain.backgroundColor = '#232323';
-const useVersionedDocs = require('node:fs').existsSync('versions.json');
+const useVersionedDocs = existsSync('versions.json');
+const wellKnownDocsPath = resolvePath(__dirname, '../docs/.well-known');
+const wellKnownPublicPath = '/.well-known';
// This patches the redirect plugin to ignore the error when it tries to override existing fields.
// This lets us add redirects that only apply to the next docs, while the stable docs still contain the source path.
@@ -63,11 +67,63 @@ const defaultOpenApiOptions = {
},
} satisfies OpenApiPlugin.Options;
+const seoDescription =
+ 'Backstage is an open source developer portal framework that centralizes your software catalog, unifies infrastructure tools, and helps teams ship high-quality code faster.';
+
+const seoKeywords = [
+ 'Backstage',
+ 'developer portal',
+ 'internal developer platform',
+ 'platform engineering',
+ 'software catalog',
+ 'software templates',
+ 'TechDocs',
+ 'developer experience',
+ 'IDP',
+ 'open source',
+];
+
const config: Config = {
title: 'Backstage Software Catalog and Developer Platform',
tagline: 'An open source framework for building developer portals',
url: 'https://backstage.io',
baseUrl: '/',
+ headTags: [
+ {
+ tagName: 'link',
+ attributes: {
+ rel: 'preconnect',
+ href: 'https://fonts.googleapis.com',
+ },
+ },
+ {
+ tagName: 'link',
+ attributes: {
+ rel: 'preconnect',
+ href: 'https://fonts.gstatic.com',
+ crossOrigin: 'anonymous',
+ },
+ },
+ {
+ tagName: 'script',
+ attributes: {
+ type: 'application/ld+json',
+ },
+ innerHTML: JSON.stringify({
+ '@context': 'https://schema.org',
+ '@type': 'WebSite',
+ name: 'Backstage',
+ url: 'https://backstage.io',
+ description: seoDescription,
+ image: 'https://backstage.io/img/sharing-opengraph.png',
+ publisher: {
+ '@type': 'Organization',
+ name: 'Spotify',
+ url: 'https://spotify.github.io/',
+ },
+ }),
+ },
+ ],
organizationName: 'Spotify',
projectName: 'backstage',
scripts: [
@@ -201,6 +257,40 @@ const config: Config = {
};
},
}),
+ () => ({
+ name: 'publish-well-known-docs',
+ getPathsToWatch() {
+ return [wellKnownDocsPath];
+ },
+ configureWebpack() {
+ if (!existsSync(wellKnownDocsPath)) {
+ return undefined;
+ }
+
+ return {
+ devServer: {
+ static: [
+ {
+ publicPath: wellKnownPublicPath,
+ directory: wellKnownDocsPath,
+ staticOptions: {
+ dotfiles: 'allow',
+ },
+ },
+ ],
+ },
+ };
+ },
+ async postBuild({ outDir }: { outDir: string }) {
+ if (!existsSync(wellKnownDocsPath)) {
+ return;
+ }
+
+ cpSync(wellKnownDocsPath, resolvePath(outDir, '.well-known'), {
+ recursive: true,
+ });
+ },
+ }),
ctx =>
PatchedRedirectPlugin(ctx, {
id: '@docusaurus/plugin-client-redirects',
@@ -237,7 +327,7 @@ const config: Config = {
},
{
from: '/docs/features/software-templates/testing-scaffolder-alpha',
- to: '/docs/features/software-templates/migrating-to-rjsf-v5',
+ to: '/docs/features/software-templates/',
},
{
from: '/docs/auth/glossary',
@@ -330,6 +420,28 @@ const config: Config = {
],
themes: ['docusaurus-theme-openapi-docs'],
themeConfig: {
+ metadata: [
+ {
+ name: 'description',
+ content: seoDescription,
+ },
+ {
+ name: 'keywords',
+ content: seoKeywords.join(', '),
+ },
+ {
+ property: 'og:site_name',
+ content: 'Backstage',
+ },
+ {
+ property: 'og:type',
+ content: 'website',
+ },
+ {
+ name: 'twitter:card',
+ content: 'summary_large_image',
+ },
+ ],
languageTabs: [
{
highlight: 'javascript',
@@ -432,12 +544,14 @@ const config: Config = {
position: 'right',
className: 'header-github-link',
'aria-label': 'GitHub repository',
+ title: 'GitHub repository',
},
{
href: 'https://discord.gg/backstage-687207715902193673',
position: 'right',
className: 'header-discord-link',
'aria-label': 'Discord community',
+ title: 'Discord community',
},
...(useVersionedDocs
? [
@@ -450,6 +564,13 @@ const config: Config = {
],
},
image: 'img/sharing-opengraph.png',
+ metadata: [
+ {
+ name: 'description',
+ content:
+ 'Backstage is an open source developer portal framework that centralizes your software catalog, unifies infrastructure tools, and helps teams ship high-quality code faster.',
+ },
+ ],
footer: {
links: [
{
diff --git a/microsite/package.json b/microsite/package.json
index 0da6c2c8be..51e1284668 100644
--- a/microsite/package.json
+++ b/microsite/package.json
@@ -20,8 +20,8 @@
},
"prettier": "@backstage/cli/config/prettier",
"resolutions": {
- "@swc/core": "1.15.3",
- "@swc/html": "1.15.3",
+ "@swc/core": "1.15.18",
+ "@swc/html": "1.15.18",
"node-polyfill-webpack-plugin": "^3.0.0"
},
"dependencies": {
diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts
index edbbcbe10c..09d25dfccf 100644
--- a/microsite/sidebars.ts
+++ b/microsite/sidebars.ts
@@ -69,8 +69,16 @@ export default {
]),
sidebarElementWithIndex({ label: 'Using Backstage' }, [
'getting-started/logging-in',
- 'getting-started/register-a-component',
- 'getting-started/create-a-component',
+ 'getting-started/viewing-catalog',
+ 'getting-started/view-what-you-own',
+ 'getting-started/viewing-entity-relationships',
+ 'getting-started/filter-catalog',
+ sidebarElementWithIndex({ label: 'Managing Components' }, [
+ 'getting-started/register-a-component',
+ 'getting-started/create-a-component',
+ 'getting-started/update-a-component',
+ 'getting-started/unregister-delete-component',
+ ]),
]),
'overview/support',
'getting-started/keeping-backstage-updated',
@@ -78,6 +86,14 @@ export default {
...(process.env.GOLDEN_PATH
? [
sidebarElementWithIndex({ label: 'Golden Paths' }, [
+ sidebarElementWithIndex({ label: '000 - Adoption' }, [
+ 'golden-path/adoption/getting-started',
+ 'golden-path/adoption/leadership-buy-in',
+ 'golden-path/adoption/setting-up-a-poc',
+ 'golden-path/adoption/first-stakeholder-feedback',
+ 'golden-path/adoption/customizing-your-instance',
+ 'golden-path/adoption/preparing-for-ga',
+ ]),
sidebarElementWithIndex({ label: '001 - Create an App' }, [
'golden-path/create-app/index',
'golden-path/create-app/npx-create-app',
@@ -105,6 +121,14 @@ export default {
description: 'Features powering the core of Backstage.',
},
[
+ sidebarElementWithIndex(
+ {
+ label: 'AI',
+ description:
+ 'Features in Backstage you can leverage with your AI tools.',
+ },
+ ['ai/skills', 'ai/mcp-actions', 'ai/well-known-actions'],
+ ),
sidebarElementWithIndex(
{
label: 'Auth and Identity',
@@ -286,8 +310,6 @@ export default {
'features/software-templates/writing-custom-field-extensions',
'features/software-templates/writing-custom-step-layouts',
'features/software-templates/authorizing-scaffolder-template-details',
- 'features/software-templates/migrating-to-rjsf-v5',
- 'features/software-templates/migrating-from-v1beta2-to-v1beta3',
'features/software-templates/dry-run-testing',
'features/software-templates/experimental',
'features/software-templates/templating-extensions',
@@ -391,60 +413,7 @@ export default {
'integrations/google-cloud-storage/locations',
]),
sidebarElementWithIndex({ label: 'LDAP' }, ['integrations/ldap/org']),
- ],
- ),
- sidebarElementWithIndex(
- {
- label: 'Plugins',
- description: 'Extend Backstage with custom functionality.',
- },
- [
- 'plugins/index',
- 'plugins/create-a-plugin',
- 'plugins/plugin-development',
- 'plugins/structure-of-a-plugin',
- 'plugins/integrating-plugin-into-software-catalog',
- 'plugins/integrating-search-into-plugins',
- 'plugins/composability',
- 'plugins/internationalization',
- 'plugins/analytics',
- 'plugins/feature-flags',
- sidebarElementWithIndex(
- {
- label: 'OpenAPI',
- description:
- 'Work with OpenAPI specifications and generate clients.',
- },
- [
- 'openapi/01-getting-started',
- 'openapi/generate-client',
- 'openapi/test-case-validation',
- ],
- ),
- sidebarElementWithIndex(
- {
- label: 'Backends and APIs',
- description: 'Build and manage backend services and APIs.',
- },
- [
- 'plugins/proxying',
- 'plugins/backend-plugin',
- 'plugins/call-existing-api',
- ],
- ),
- sidebarElementWithIndex(
- { label: 'Testing', description: 'Testing plugins and modules.' },
- ['plugins/testing'],
- ),
- sidebarElementWithIndex(
- { label: 'Publishing', description: 'Publishing your plugins.' },
- [
- 'plugins/publish-private',
- 'plugins/add-to-directory',
- 'plugins/plugin-directory-audit',
- ],
- ),
- 'plugins/observability',
+ sidebarElementWithIndex({ label: 'Okta' }, ['integrations/okta/org']),
],
),
sidebarElementWithIndex(
@@ -521,6 +490,7 @@ export default {
'backend-system/core-services/identity',
'backend-system/core-services/lifecycle',
'backend-system/core-services/logger',
+ 'backend-system/core-services/metrics',
'backend-system/core-services/permissions',
'backend-system/core-services/permissions-registry',
'backend-system/core-services/plugin-metadata',
@@ -542,15 +512,15 @@ export default {
),
sidebarElementWithIndex(
{
- label: 'New Frontend System',
- description: 'New frontend system components and architecture.',
+ label: 'Frontend System',
+ description: 'Frontend system components and architecture.',
},
[
'frontend-system/index',
sidebarElementWithIndex(
{
label: 'Architecture',
- description: 'Architecture of the new frontend system.',
+ description: 'Architecture of the frontend system.',
differentiator: 'frontend-system/',
},
[
@@ -579,6 +549,9 @@ export default {
'frontend-system/building-plugins/common-extension-blueprints',
'frontend-system/building-plugins/built-in-data-refs',
'frontend-system/building-plugins/migrating',
+ 'frontend-system/building-plugins/internationalization',
+ 'frontend-system/building-plugins/analytics',
+ 'frontend-system/building-plugins/feature-flags',
],
),
sidebarElementWithIndex(
@@ -588,6 +561,7 @@ export default {
},
[
'frontend-system/building-apps/index',
+ 'frontend-system/building-apps/installing-plugins',
'frontend-system/building-apps/configuring-extensions',
'frontend-system/building-apps/built-in-extensions',
'frontend-system/building-apps/plugin-conversion',
@@ -647,6 +621,18 @@ export default {
'conf/user-interface/sidebar',
],
),
+ sidebarElementWithIndex(
+ {
+ label: 'OpenAPI',
+ description:
+ 'Work with OpenAPI specifications and generate clients.',
+ },
+ [
+ 'openapi/01-getting-started',
+ 'openapi/generate-client',
+ 'openapi/test-case-validation',
+ ],
+ ),
],
),
sidebarElementWithIndex(
@@ -674,6 +660,7 @@ export default {
'tutorials/manual-knex-rollback',
'tutorials/switching-sqlite-postgres',
'tutorials/using-backstage-proxy-within-plugin',
+ 'tutorials/corporate-proxy',
'tutorials/enable-public-entry',
'tutorials/setup-opentelemetry',
'tutorials/integrating-event-driven-updates-with-entity-providers',
@@ -700,6 +687,46 @@ export default {
),
],
),
+ sidebarElementWithIndex(
+ {
+ label: 'Plugins (Legacy)',
+ description:
+ 'Legacy plugin development documentation for the old frontend system. For new development, see the Frontend System and Backend System sections under Framework.',
+ },
+ [
+ 'plugins/index',
+ 'plugins/create-a-plugin',
+ 'plugins/plugin-development',
+ 'plugins/structure-of-a-plugin',
+ 'plugins/integrating-plugin-into-software-catalog',
+ 'plugins/integrating-search-into-plugins',
+ 'plugins/composability',
+ 'plugins/internationalization',
+ 'plugins/analytics',
+ 'plugins/feature-flags',
+ sidebarElementWithIndex(
+ {
+ label: 'Backends and APIs',
+ description: 'Build and manage backend services and APIs.',
+ },
+ [
+ 'plugins/proxying',
+ 'plugins/backend-plugin',
+ 'plugins/call-existing-api',
+ ],
+ ),
+ sidebarElementWithIndex(
+ { label: 'Testing', description: 'Testing plugins and modules.' },
+ ['plugins/testing'],
+ ),
+ sidebarElementWithIndex(
+ { label: 'Publishing', description: 'Publishing your plugins.' },
+ ['plugins/add-to-directory', 'plugins/plugin-directory-audit'],
+ ),
+ 'plugins/observability',
+ 'plugins/new-backend-system',
+ ],
+ ),
sidebarElementWithIndex(
{ label: 'FAQ', description: 'Frequently asked questions and answers.' },
['faq/index', 'faq/product', 'faq/technical'],
@@ -713,6 +740,7 @@ export default {
'contribute/index',
'contribute/getting-involved',
'contribute/project-structure',
+ 'contribute/doc-style-guide',
],
),
sidebarElementWithIndex(
diff --git a/microsite/static/img/flagsmith.svg b/microsite/static/img/flagsmith.svg
new file mode 100644
index 0000000000..d8039477ca
--- /dev/null
+++ b/microsite/static/img/flagsmith.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/microsite/static/robots.txt b/microsite/static/robots.txt
new file mode 100644
index 0000000000..b67550f16d
--- /dev/null
+++ b/microsite/static/robots.txt
@@ -0,0 +1,4 @@
+User-agent: *
+Allow: /
+
+Sitemap: https://backstage.io/sitemap.xml
diff --git a/microsite/tsconfig.json b/microsite/tsconfig.json
index 9a5106747e..c51398e2ed 100644
--- a/microsite/tsconfig.json
+++ b/microsite/tsconfig.json
@@ -9,5 +9,6 @@
"@docusaurus/theme-classic",
"@types/webpack-env"
]
- }
+ },
+ "exclude": ["node_modules", "build", ".docusaurus", "static", "blog"]
}
diff --git a/microsite/yarn.lock b/microsite/yarn.lock
index 4cd9f95851..3f17fdd373 100644
--- a/microsite/yarn.lock
+++ b/microsite/yarn.lock
@@ -4053,90 +4053,90 @@ __metadata:
languageName: node
linkType: hard
-"@swc/core-darwin-arm64@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-darwin-arm64@npm:1.15.3"
+"@swc/core-darwin-arm64@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/core-darwin-arm64@npm:1.15.18"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@swc/core-darwin-x64@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-darwin-x64@npm:1.15.3"
+"@swc/core-darwin-x64@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/core-darwin-x64@npm:1.15.18"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@swc/core-linux-arm-gnueabihf@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.3"
+"@swc/core-linux-arm-gnueabihf@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.18"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@swc/core-linux-arm64-gnu@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-linux-arm64-gnu@npm:1.15.3"
+"@swc/core-linux-arm64-gnu@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/core-linux-arm64-gnu@npm:1.15.18"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@swc/core-linux-arm64-musl@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-linux-arm64-musl@npm:1.15.3"
+"@swc/core-linux-arm64-musl@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/core-linux-arm64-musl@npm:1.15.18"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@swc/core-linux-x64-gnu@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-linux-x64-gnu@npm:1.15.3"
+"@swc/core-linux-x64-gnu@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/core-linux-x64-gnu@npm:1.15.18"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@swc/core-linux-x64-musl@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-linux-x64-musl@npm:1.15.3"
+"@swc/core-linux-x64-musl@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/core-linux-x64-musl@npm:1.15.18"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@swc/core-win32-arm64-msvc@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-win32-arm64-msvc@npm:1.15.3"
+"@swc/core-win32-arm64-msvc@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/core-win32-arm64-msvc@npm:1.15.18"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@swc/core-win32-ia32-msvc@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-win32-ia32-msvc@npm:1.15.3"
+"@swc/core-win32-ia32-msvc@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/core-win32-ia32-msvc@npm:1.15.18"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
-"@swc/core-win32-x64-msvc@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-win32-x64-msvc@npm:1.15.3"
+"@swc/core-win32-x64-msvc@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/core-win32-x64-msvc@npm:1.15.18"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
-"@swc/core@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core@npm:1.15.3"
+"@swc/core@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/core@npm:1.15.18"
dependencies:
- "@swc/core-darwin-arm64": "npm:1.15.3"
- "@swc/core-darwin-x64": "npm:1.15.3"
- "@swc/core-linux-arm-gnueabihf": "npm:1.15.3"
- "@swc/core-linux-arm64-gnu": "npm:1.15.3"
- "@swc/core-linux-arm64-musl": "npm:1.15.3"
- "@swc/core-linux-x64-gnu": "npm:1.15.3"
- "@swc/core-linux-x64-musl": "npm:1.15.3"
- "@swc/core-win32-arm64-msvc": "npm:1.15.3"
- "@swc/core-win32-ia32-msvc": "npm:1.15.3"
- "@swc/core-win32-x64-msvc": "npm:1.15.3"
+ "@swc/core-darwin-arm64": "npm:1.15.18"
+ "@swc/core-darwin-x64": "npm:1.15.18"
+ "@swc/core-linux-arm-gnueabihf": "npm:1.15.18"
+ "@swc/core-linux-arm64-gnu": "npm:1.15.18"
+ "@swc/core-linux-arm64-musl": "npm:1.15.18"
+ "@swc/core-linux-x64-gnu": "npm:1.15.18"
+ "@swc/core-linux-x64-musl": "npm:1.15.18"
+ "@swc/core-win32-arm64-msvc": "npm:1.15.18"
+ "@swc/core-win32-ia32-msvc": "npm:1.15.18"
+ "@swc/core-win32-x64-msvc": "npm:1.15.18"
"@swc/counter": "npm:^0.1.3"
"@swc/types": "npm:^0.1.25"
peerDependencies:
@@ -4165,7 +4165,7 @@ __metadata:
peerDependenciesMeta:
"@swc/helpers":
optional: true
- checksum: 10/280330d82328818138ed64fdcf9ea9abde6b6f16eca65a9d4db27dde06a8dfffd2649f3447d2243387277513c7430fa4142cafcfd64e943d682ce6a713cb8c2d
+ checksum: 10/ef198b9feb6eee034e3a912c37988ece2885fec35419e8245d467adbc1fc47a5c3e61869d1bdbe6fff76cbd9186ef278120cbb9746d5f7446576f4a7f15c2dcd
languageName: node
linkType: hard
@@ -4176,91 +4176,91 @@ __metadata:
languageName: node
linkType: hard
-"@swc/html-darwin-arm64@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/html-darwin-arm64@npm:1.15.3"
+"@swc/html-darwin-arm64@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/html-darwin-arm64@npm:1.15.18"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@swc/html-darwin-x64@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/html-darwin-x64@npm:1.15.3"
+"@swc/html-darwin-x64@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/html-darwin-x64@npm:1.15.18"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@swc/html-linux-arm-gnueabihf@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/html-linux-arm-gnueabihf@npm:1.15.3"
+"@swc/html-linux-arm-gnueabihf@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/html-linux-arm-gnueabihf@npm:1.15.18"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@swc/html-linux-arm64-gnu@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/html-linux-arm64-gnu@npm:1.15.3"
+"@swc/html-linux-arm64-gnu@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/html-linux-arm64-gnu@npm:1.15.18"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@swc/html-linux-arm64-musl@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/html-linux-arm64-musl@npm:1.15.3"
+"@swc/html-linux-arm64-musl@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/html-linux-arm64-musl@npm:1.15.18"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@swc/html-linux-x64-gnu@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/html-linux-x64-gnu@npm:1.15.3"
+"@swc/html-linux-x64-gnu@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/html-linux-x64-gnu@npm:1.15.18"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@swc/html-linux-x64-musl@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/html-linux-x64-musl@npm:1.15.3"
+"@swc/html-linux-x64-musl@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/html-linux-x64-musl@npm:1.15.18"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@swc/html-win32-arm64-msvc@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/html-win32-arm64-msvc@npm:1.15.3"
+"@swc/html-win32-arm64-msvc@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/html-win32-arm64-msvc@npm:1.15.18"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@swc/html-win32-ia32-msvc@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/html-win32-ia32-msvc@npm:1.15.3"
+"@swc/html-win32-ia32-msvc@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/html-win32-ia32-msvc@npm:1.15.18"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
-"@swc/html-win32-x64-msvc@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/html-win32-x64-msvc@npm:1.15.3"
+"@swc/html-win32-x64-msvc@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/html-win32-x64-msvc@npm:1.15.18"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
-"@swc/html@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/html@npm:1.15.3"
+"@swc/html@npm:1.15.18":
+ version: 1.15.18
+ resolution: "@swc/html@npm:1.15.18"
dependencies:
"@swc/counter": "npm:^0.1.3"
- "@swc/html-darwin-arm64": "npm:1.15.3"
- "@swc/html-darwin-x64": "npm:1.15.3"
- "@swc/html-linux-arm-gnueabihf": "npm:1.15.3"
- "@swc/html-linux-arm64-gnu": "npm:1.15.3"
- "@swc/html-linux-arm64-musl": "npm:1.15.3"
- "@swc/html-linux-x64-gnu": "npm:1.15.3"
- "@swc/html-linux-x64-musl": "npm:1.15.3"
- "@swc/html-win32-arm64-msvc": "npm:1.15.3"
- "@swc/html-win32-ia32-msvc": "npm:1.15.3"
- "@swc/html-win32-x64-msvc": "npm:1.15.3"
+ "@swc/html-darwin-arm64": "npm:1.15.18"
+ "@swc/html-darwin-x64": "npm:1.15.18"
+ "@swc/html-linux-arm-gnueabihf": "npm:1.15.18"
+ "@swc/html-linux-arm64-gnu": "npm:1.15.18"
+ "@swc/html-linux-arm64-musl": "npm:1.15.18"
+ "@swc/html-linux-x64-gnu": "npm:1.15.18"
+ "@swc/html-linux-x64-musl": "npm:1.15.18"
+ "@swc/html-win32-arm64-msvc": "npm:1.15.18"
+ "@swc/html-win32-ia32-msvc": "npm:1.15.18"
+ "@swc/html-win32-x64-msvc": "npm:1.15.18"
dependenciesMeta:
"@swc/html-darwin-arm64":
optional: true
@@ -4282,7 +4282,7 @@ __metadata:
optional: true
"@swc/html-win32-x64-msvc":
optional: true
- checksum: 10/753ddec5a5fb53a5245b4e5ab990c8dc9e6aff2c3da99ab671be2535d3fc566953dac4aa30c9a435e78a80901384fbc8f43c603c379abe2d21a11303dd804393
+ checksum: 10/5fdef26fa88693c2913d2ee147332ab366bc6d41726ea2b57db31d3992fbf47aa5b881537f32a077535a64f896a7f7af8f0092343d74256fd8c290431e1a8407
languageName: node
linkType: hard
@@ -4311,13 +4311,6 @@ __metadata:
languageName: node
linkType: hard
-"@trysound/sax@npm:0.2.0":
- version: 0.2.0
- resolution: "@trysound/sax@npm:0.2.0"
- checksum: 10/7379713eca480ac0d9b6c7b063e06b00a7eac57092354556c81027066eb65b61ea141a69d0cc2e15d32e05b2834d4c9c2184793a5e36bbf5daf05ee5676af18c
- languageName: node
- linkType: hard
-
"@tybys/wasm-util@npm:^0.10.1":
version: 0.10.1
resolution: "@tybys/wasm-util@npm:0.10.1"
@@ -8554,10 +8547,10 @@ __metadata:
languageName: node
linkType: hard
-"immutable@npm:^5.0.2":
- version: 5.0.2
- resolution: "immutable@npm:5.0.2"
- checksum: 10/89b1117c610024b7a9214eade8b9f1ed38b00c82235f119515cfa5eaf26270eccbc803296d4c3c12f53e50802f042f84d811998910b866363913720da768472e
+"immutable@npm:^5.1.5":
+ version: 5.1.5
+ resolution: "immutable@npm:5.1.5"
+ checksum: 10/7aec2740239772ec8e92e793c991bd809203a97694f4ff3a18e50e28f9a6b02393ad033d87b458037bdf8c0ea37d4446d640e825f6171df3405cf6cf300ce028
languageName: node
linkType: hard
@@ -11422,9 +11415,9 @@ __metadata:
linkType: hard
"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1":
- version: 2.3.1
- resolution: "picomatch@npm:2.3.1"
- checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc
+ version: 2.3.2
+ resolution: "picomatch@npm:2.3.2"
+ checksum: 10/b788ef8148a2415b9dec12f0bb350ae6a5830f8f1950e472abc2f5225494debf7d1b75eb031df0ceaea9e8ec3e7bad599e8dbf3c60d61b42be429ba41bff4426
languageName: node
linkType: hard
@@ -13296,26 +13289,26 @@ __metadata:
linkType: hard
"sass@npm:^1.57.1, sass@npm:^1.89.2":
- version: 1.97.3
- resolution: "sass@npm:1.97.3"
+ version: 1.98.0
+ resolution: "sass@npm:1.98.0"
dependencies:
"@parcel/watcher": "npm:^2.4.1"
chokidar: "npm:^4.0.0"
- immutable: "npm:^5.0.2"
+ immutable: "npm:^5.1.5"
source-map-js: "npm:>=0.6.2 <2.0.0"
dependenciesMeta:
"@parcel/watcher":
optional: true
bin:
sass: sass.js
- checksum: 10/707ef8e525ed32d375e737346140d4b675f44de208df996c2df3407f5e62f3f38226ea1faf41a9fd4b068201e67b3a7e152b9e9c3b098daa847dd480c735f038
+ checksum: 10/37d134d07639dc8fc8557495c3b98801bb736f0d1fc5fbfb2e0dba3c21200f447cf3e6166cd285603c922171366696c1d2e766af2c4cf82d882a3dc4d4e39f00
languageName: node
linkType: hard
-"sax@npm:^1.2.4":
- version: 1.2.4
- resolution: "sax@npm:1.2.4"
- checksum: 10/09b79ff6dc09689a24323352117c94593c69db348997b2af0edbd82fa08aba47d778055bf9616b57285bb73d25d790900c044bf631a8f10c8252412e3f3fe5dd
+"sax@npm:^1.2.4, sax@npm:^1.5.0":
+ version: 1.5.0
+ resolution: "sax@npm:1.5.0"
+ checksum: 10/9012ff37dda7a7ac5da45db2143b04036103e8bef8d586c3023afd5df6caf0ebd7f38017eee344ad2e2247eded7d38e9c42cf291d8dd91781352900ac0fd2d9f
languageName: node
linkType: hard
@@ -14132,19 +14125,19 @@ __metadata:
linkType: hard
"svgo@npm:^3.0.2, svgo@npm:^3.2.0":
- version: 3.3.2
- resolution: "svgo@npm:3.3.2"
+ version: 3.3.3
+ resolution: "svgo@npm:3.3.3"
dependencies:
- "@trysound/sax": "npm:0.2.0"
commander: "npm:^7.2.0"
css-select: "npm:^5.1.0"
css-tree: "npm:^2.3.1"
css-what: "npm:^6.1.0"
csso: "npm:^5.0.5"
picocolors: "npm:^1.0.0"
+ sax: "npm:^1.5.0"
bin:
svgo: ./bin/svgo
- checksum: 10/82fdea9b938884d808506104228e4d3af0050d643d5b46ff7abc903ff47a91bbf6561373394868aaf07a28f006c4057b8fbf14bbd666298abdd7cc590d4f7700
+ checksum: 10/f3c1b4d05d1704483e53515d5995af5f06a2718df85e3a8320f57bb256b8dc926b84c87a1a9b98e9d3ca1224314cc0676a803bdd03163508292f2d45c7077096
languageName: node
linkType: hard
diff --git a/mkdocs.yml b/mkdocs.yml
index c18d74494a..e01ab2a431 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -13,6 +13,7 @@ plugins:
nav:
- Overview:
- What is Backstage?: 'overview/what-is-backstage.md'
+ - Technical overview: 'overview/technical-overview.md'
- Architecture overview: 'overview/architecture-overview.md'
- Project Roadmap: 'overview/roadmap.md'
- Vision: 'overview/vision.md'
@@ -37,8 +38,15 @@ nav:
- Kubernetes: 'deployment/k8s.md'
- Using Backstage:
- Logging in: 'getting-started/logging-in.md'
- - Register a component: 'getting-started/register-a-component.md'
- - Create a component: 'getting-started/create-a-component.md'
+ - Viewing the Catalog: 'getting-started/viewing-catalog.md'
+ - Viewing what you own: 'getting-started/view-what-you-own.md'
+ - Viewing entity relationships: 'getting-started/viewing-entity-relationships.md'
+ - Filtering the Catalog: 'getting-started/filter-catalog.md'
+ - Managing Components:
+ - Register a component: 'getting-started/register-a-component.md'
+ - Create a component: 'getting-started/create-a-component.md'
+ - Update a component: 'getting-started/update-a-component.md'
+ - Unregister/delete a component: 'getting-started/unregister-delete-component.md'
- Keeping Backstage Updated: 'getting-started/keeping-backstage-updated.md'
- Core Features:
- Software Catalog:
@@ -72,7 +80,6 @@ nav:
- Writing Custom Actions: 'features/software-templates/writing-custom-actions.md'
- Writing Custom Step Layouts: 'features/software-templates/writing-custom-step-layouts.md'
- Templating Extensions: 'features/software-templates/templating-extensions.md'
- - Migrating from v1beta2 to v1beta3 templates: 'features/software-templates/migrating-from-v1beta2-to-v1beta3.md'
- Dry Run Testing: 'features/software-templates/dry-run-testing.md'
- Backstage Search:
- Overview: 'features/search/README.md'
@@ -133,7 +140,7 @@ nav:
- Locations: 'integrations/google-cloud-storage/locations.md'
- LDAP:
- Org Data: 'integrations/ldap/org.md'
- - Plugins:
+ - Plugins (Legacy):
- Intro to plugins: 'plugins/index.md'
- Create a Backstage Plugin: 'plugins/create-a-plugin.md'
- Plugin Development: 'plugins/plugin-development.md'
@@ -154,9 +161,9 @@ nav:
- Testing:
- Testing with Jest: 'plugins/testing.md'
- Publishing:
- - Publish private: 'plugins/publish-private.md'
- Add to Directory: 'plugins/add-to-directory.md'
- Observability: 'plugins/observability.md'
+ - New Backend System: 'plugins/new-backend-system.md'
- Configuration:
- Static Configuration in Backstage: 'conf/index.md'
- Reading Backstage Configuration: 'conf/reading.md'
diff --git a/package.json b/package.json
index a0a530adb8..82e8b96376 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "root",
- "version": "1.48.0",
+ "version": "1.50.0-next.1",
"backstage": {
"cli": {
"new": {
@@ -16,15 +16,11 @@
"type": "git",
"url": "https://github.com/backstage/backstage"
},
- "workspaces": {
- "packages": [
- "packages/*",
- "plugins/*"
- ]
- },
+ "workspaces": [
+ "packages/*",
+ "plugins/*"
+ ],
"scripts": {
- "build-storybook": "storybook build --output-dir dist-storybook",
- "build-storybook:chromatic": "STORYBOOK_STORY_SET=chromatic storybook build --stats-json --output-dir dist-storybook",
"build:all": "backstage-cli repo build --all",
"build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(api-docs|api-docs-module-protoc-gen-doc|app-visualizer|catalog-graph|catalog-import|catalog-unprocessed-entities|config-schema|example-todo-list|example-todo-list-backend)'",
"build:api-reports": "yarn build:api-reports:only --tsc",
@@ -32,6 +28,8 @@
"build:backend": "yarn workspace example-backend build",
"build:knip-reports": "backstage-repo-tools knip-reports",
"build:plugins-report": "node ./scripts/build-plugins-report",
+ "build-storybook": "storybook build --output-dir dist-storybook",
+ "build-storybook:chromatic": "STORYBOOK_STORY_SET=chromatic storybook build --stats-json --output-dir dist-storybook",
"clean": "backstage-cli repo clean",
"create-plugin": "echo \"use 'yarn new' instead\"",
"dev": "echo \"use 'yarn start' instead\"",
@@ -54,10 +52,10 @@
"snyk:test": "npx snyk test --yarn-workspaces --strict-out-of-sync=false",
"snyk:test:package": "yarn snyk:test --include",
"start": "backstage-cli repo start",
- "start-backend": "echo \"Use 'yarn start example-backend' instead\"",
"start:docker": "docker compose -f docker-compose.deps.yml up --wait && BACKSTAGE_ENV=docker yarn start",
"start:legacy": "yarn start example-app-legacy example-backend",
"start:microsite": "cd microsite/ && yarn start",
+ "start-backend": "echo \"Use 'yarn start example-backend' instead\"",
"storybook": "storybook dev -p 6006",
"sync-issue-templates": "node ./.github/ISSUE_TEMPLATE/sync.js",
"techdocs-cli": "node scripts/techdocs-cli.js",
@@ -127,6 +125,7 @@
},
"devDependencies": {
"@backstage/cli": "workspace:*",
+ "@backstage/cli-defaults": "workspace:*",
"@backstage/codemods": "workspace:*",
"@backstage/create-app": "workspace:*",
"@backstage/e2e-test-utils": "workspace:*",
@@ -137,13 +136,13 @@
"@octokit/rest": "^19.0.3",
"@playwright/test": "^1.32.3",
"@spotify/eslint-plugin": "^15.0.0",
- "@storybook/addon-a11y": "^10.3.0-alpha.1",
- "@storybook/addon-docs": "^10.3.0-alpha.1",
- "@storybook/addon-links": "^10.3.0-alpha.1",
- "@storybook/addon-mcp": "^0.2.2",
- "@storybook/addon-themes": "^10.3.0-alpha.1",
- "@storybook/addon-vitest": "^10.3.0-alpha.1",
- "@storybook/react-vite": "^10.3.0-alpha.1",
+ "@storybook/addon-a11y": "^10.3.3",
+ "@storybook/addon-docs": "^10.3.3",
+ "@storybook/addon-links": "^10.3.3",
+ "@storybook/addon-mcp": "^0.4.2",
+ "@storybook/addon-themes": "^10.3.3",
+ "@storybook/addon-vitest": "^10.3.3",
+ "@storybook/react-vite": "^10.3.3",
"@techdocs/cli": "workspace:*",
"@types/cacheable-request": "^8.3.6",
"@types/jest": "^30",
@@ -165,21 +164,21 @@
"jest": "^30",
"js-yaml": "^4.1.1",
"jsdom": "^27",
- "lint-staged": "^15.0.0",
+ "lint-staged": "^16.0.0",
"madge": "^8.0.0",
"minimist": "^1.2.5",
- "node-gyp": "^10.0.0",
+ "node-gyp": "^12.0.0",
"prettier": "^2.2.1",
"semver": "^7.5.3",
"shx": "^0.4.0",
"sloc": "^0.3.1",
- "sort-package-json": "^2.8.0",
- "storybook": "^10.3.0-alpha.1",
+ "sort-package-json": "^3.0.0",
+ "storybook": "^10.3.3",
"ts-morph": "^24.0.0",
"typedoc": "^0.28.0",
"typescript": "~5.7.0",
"vite": "^7.1.5",
- "zod": "^3.25.76"
+ "zod": "^3.25.76 || ^4.0.0"
},
"packageManager": "yarn@4.8.1",
"engines": {
diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md
index a5516a33a9..1bfbb15648 100644
--- a/packages/app-defaults/CHANGELOG.md
+++ b/packages/app-defaults/CHANGELOG.md
@@ -1,5 +1,47 @@
# @backstage/app-defaults
+## 1.7.7-next.1
+
+### Patch Changes
+
+- 400aa23: Added `FetchMiddlewares.clarifyFailures()` to the default fetch API middleware stack.
+- Updated dependencies
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+
+## 1.7.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-permission-react@0.4.42-next.0
+
+## 1.7.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/core-app-api@1.19.6
+ - @backstage/plugin-permission-react@0.4.41
+
+## 1.7.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-permission-react@0.4.41-next.0
+
## 1.7.5
### Patch Changes
diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json
index 71b5da64d7..74a712f952 100644
--- a/packages/app-defaults/package.json
+++ b/packages/app-defaults/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/app-defaults",
- "version": "1.7.5",
+ "version": "1.7.7-next.1",
"description": "Provides the default wiring of a Backstage App",
"backstage": {
"role": "web-library"
diff --git a/packages/app-defaults/src/defaults/apis.ts b/packages/app-defaults/src/defaults/apis.ts
index 97989dff91..b591154246 100644
--- a/packages/app-defaults/src/defaults/apis.ts
+++ b/packages/app-defaults/src/defaults/apis.ts
@@ -113,6 +113,7 @@ export const apis = [
identityApi,
config: configApi,
}),
+ FetchMiddlewares.clarifyFailures(),
],
});
},
diff --git a/packages/app-example-plugin/CHANGELOG.md b/packages/app-example-plugin/CHANGELOG.md
index a23772bb3b..fc4424312f 100644
--- a/packages/app-example-plugin/CHANGELOG.md
+++ b/packages/app-example-plugin/CHANGELOG.md
@@ -1,5 +1,45 @@
# app-example-plugin
+## 0.0.34-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/core-components@0.18.9-next.0
+
+## 0.0.34-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+
+## 0.0.33
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.8
+ - @backstage/frontend-plugin-api@0.15.0
+
+## 0.0.33-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-components@0.18.8-next.1
+
+## 0.0.33-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/core-components@0.18.8-next.0
+
## 0.0.32
### Patch Changes
diff --git a/packages/app-example-plugin/package.json b/packages/app-example-plugin/package.json
index 2bdfd931fd..bd031450e2 100644
--- a/packages/app-example-plugin/package.json
+++ b/packages/app-example-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "app-example-plugin",
- "version": "0.0.32",
+ "version": "0.0.34-next.1",
"description": "Backstage internal example plugin",
"backstage": {
"role": "frontend-plugin",
diff --git a/packages/app-example-plugin/report.api.md b/packages/app-example-plugin/report.api.md
index 70b3446a2b..432174da68 100644
--- a/packages/app-example-plugin/report.api.md
+++ b/packages/app-example-plugin/report.api.md
@@ -87,7 +87,6 @@ const examplePlugin: OverridableFrontendPlugin<
>;
};
params: {
- defaultPath?: [Error: `Use the 'path' param instead`];
path: string;
title?: string;
icon?: IconElement;
diff --git a/packages/app-legacy/CHANGELOG.md b/packages/app-legacy/CHANGELOG.md
index 2212011ecd..b769f0188f 100644
--- a/packages/app-legacy/CHANGELOG.md
+++ b/packages/app-legacy/CHANGELOG.md
@@ -1,5 +1,246 @@
# example-app-legacy
+## 0.2.120-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/app-defaults@1.7.7-next.1
+ - @backstage/cli@0.36.1-next.1
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/plugin-catalog-graph@0.6.1-next.1
+ - @backstage/plugin-catalog-import@0.13.12-next.1
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/plugin-org@0.7.1-next.1
+ - @backstage/frontend-app-api@0.16.2-next.1
+ - @backstage/plugin-kubernetes@0.12.18-next.1
+ - @backstage/plugin-notifications@0.5.16-next.1
+ - @backstage/plugin-scaffolder@1.36.2-next.1
+ - @backstage/plugin-search@1.7.1-next.1
+ - @backstage/plugin-signals@0.0.30-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-api-docs@0.13.6-next.1
+ - @backstage/plugin-catalog@2.0.2-next.1
+ - @backstage/plugin-home@0.9.4-next.1
+ - @backstage/plugin-scaffolder-react@1.20.1-next.1
+ - @backstage/plugin-search-react@1.11.1-next.1
+ - @backstage/plugin-techdocs@1.17.3-next.1
+ - @backstage/plugin-user-settings@0.9.2-next.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.1
+ - @backstage/plugin-devtools@0.1.38-next.1
+ - @backstage/plugin-home-react@0.1.37-next.1
+ - @backstage/plugin-mui-to-bui@0.2.6-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.1
+ - @backstage/plugin-techdocs-react@1.3.10-next.1
+
+## 0.2.120-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog@2.0.2-next.0
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-scaffolder-react@1.20.1-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/plugin-techdocs@1.17.3-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/plugin-signals@0.0.30-next.0
+ - @backstage/plugin-scaffolder@1.36.2-next.0
+ - @backstage/frontend-app-api@0.16.2-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.0
+ - @backstage/plugin-api-docs@0.13.6-next.0
+ - @backstage/plugin-catalog-graph@0.6.1-next.0
+ - @backstage/plugin-catalog-import@0.13.12-next.0
+ - @backstage/plugin-home@0.9.4-next.0
+ - @backstage/plugin-org@0.7.1-next.0
+ - @backstage/plugin-user-settings@0.9.2-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/plugin-devtools@0.1.38-next.0
+ - @backstage/plugin-mui-to-bui@0.2.6-next.0
+ - @backstage/plugin-notifications@0.5.16-next.0
+ - @backstage/plugin-search@1.7.1-next.0
+ - @backstage/cli@0.36.1-next.0
+ - @backstage/plugin-kubernetes@0.12.18-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.36-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.0
+ - @backstage/app-defaults@1.7.7-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/plugin-auth-react@0.1.26-next.0
+ - @backstage/plugin-home-react@0.1.37-next.0
+ - @backstage/plugin-search-react@1.11.1-next.0
+ - @backstage/plugin-techdocs-react@1.3.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.42-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## 0.2.119
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.36.0
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/ui@0.13.0
+ - @backstage/frontend-app-api@0.16.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/core-components@0.18.8
+ - @backstage/plugin-user-settings@0.9.1
+ - @backstage/plugin-search-react@1.11.0
+ - @backstage/plugin-search@1.7.0
+ - @backstage/plugin-api-docs@0.13.5
+ - @backstage/plugin-devtools@0.1.37
+ - @backstage/core-app-api@1.19.6
+ - @backstage/plugin-scaffolder@1.36.0
+ - @backstage/plugin-techdocs@1.17.1
+ - @backstage/plugin-catalog@2.0.0
+ - @backstage/plugin-catalog-graph@0.6.0
+ - @backstage/plugin-catalog-import@0.13.11
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27
+ - @backstage/plugin-home@0.9.3
+ - @backstage/plugin-kubernetes@0.12.17
+ - @backstage/plugin-mui-to-bui@0.2.5
+ - @backstage/plugin-org@0.7.0
+ - @backstage/plugin-scaffolder-react@1.20.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34
+ - @backstage/plugin-notifications@0.5.15
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/plugin-home-react@0.1.36
+ - @backstage/plugin-kubernetes-cluster@0.0.35
+ - @backstage/catalog-model@1.7.7
+ - @backstage/app-defaults@1.7.6
+ - @backstage/integration-react@1.2.16
+ - @backstage/plugin-auth-react@0.1.25
+ - @backstage/plugin-signals@0.0.29
+ - @backstage/plugin-techdocs-react@1.3.9
+
+## 0.2.119-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/cli@0.36.0-next.2
+ - @backstage/core-app-api@1.19.6-next.1
+ - @backstage/plugin-techdocs@1.17.1-next.2
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/frontend-app-api@0.16.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-org@0.7.0-next.2
+ - @backstage/plugin-catalog-graph@0.6.0-next.2
+ - @backstage/plugin-mui-to-bui@0.2.5-next.2
+ - @backstage/plugin-scaffolder-react@1.20.0-next.2
+ - @backstage/plugin-api-docs@0.13.5-next.2
+ - @backstage/plugin-catalog@2.0.0-next.2
+ - @backstage/plugin-catalog-import@0.13.11-next.2
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.1
+ - @backstage/plugin-devtools@0.1.37-next.2
+ - @backstage/plugin-home@0.9.3-next.2
+ - @backstage/plugin-home-react@0.1.36-next.1
+ - @backstage/plugin-kubernetes@0.12.17-next.2
+ - @backstage/plugin-notifications@0.5.15-next.1
+ - @backstage/plugin-scaffolder@1.35.5-next.2
+ - @backstage/plugin-search@1.6.2-next.2
+ - @backstage/plugin-search-react@1.10.5-next.1
+ - @backstage/plugin-signals@0.0.29-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.2
+ - @backstage/plugin-techdocs-react@1.3.9-next.1
+ - @backstage/plugin-user-settings@0.9.1-next.2
+
+## 0.2.119-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.1
+ - @backstage/cli@0.36.0-next.1
+ - @backstage/plugin-devtools@0.1.37-next.1
+ - @backstage/plugin-scaffolder@1.35.5-next.1
+ - @backstage/plugin-api-docs@0.13.5-next.1
+ - @backstage/plugin-techdocs@1.17.1-next.1
+ - @backstage/plugin-catalog@1.34.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/plugin-scaffolder-react@1.19.8-next.1
+ - @backstage/plugin-mui-to-bui@0.2.5-next.1
+ - @backstage/plugin-catalog-graph@0.5.8-next.1
+ - @backstage/plugin-catalog-import@0.13.11-next.1
+ - @backstage/plugin-home@0.9.3-next.1
+ - @backstage/plugin-org@0.6.50-next.1
+ - @backstage/app-defaults@1.7.6-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-app-api@0.15.1-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-auth-react@0.1.25-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0
+ - @backstage/plugin-home-react@0.1.36-next.0
+ - @backstage/plugin-kubernetes@0.12.17-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.35-next.1
+ - @backstage/plugin-notifications@0.5.15-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-search@1.6.2-next.1
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-signals@0.0.29-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+ - @backstage/plugin-user-settings@0.9.1-next.1
+
+## 0.2.119-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.12.1-next.0
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-search@1.6.2-next.0
+ - @backstage/plugin-api-docs@0.13.5-next.0
+ - @backstage/cli@0.35.5-next.0
+ - @backstage/plugin-scaffolder@1.35.5-next.0
+ - @backstage/plugin-catalog@1.33.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/plugin-mui-to-bui@0.2.5-next.0
+ - @backstage/plugin-techdocs@1.17.1-next.0
+ - @backstage/app-defaults@1.7.6-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-app-api@0.15.1-next.0
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-auth-react@0.1.25-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-catalog-graph@0.5.8-next.0
+ - @backstage/plugin-catalog-import@0.13.11-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0
+ - @backstage/plugin-devtools@0.1.37-next.0
+ - @backstage/plugin-home@0.9.3-next.0
+ - @backstage/plugin-home-react@0.1.36-next.0
+ - @backstage/plugin-kubernetes@0.12.17-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.35-next.0
+ - @backstage/plugin-notifications@0.5.15-next.0
+ - @backstage/plugin-org@0.6.50-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-scaffolder-react@1.19.8-next.0
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-signals@0.0.29-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.0
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+ - @backstage/plugin-user-settings@0.9.1-next.0
+
## 0.2.118
### Patch Changes
diff --git a/packages/app-legacy/package.json b/packages/app-legacy/package.json
index 6be63b7a14..ca96c0a1ab 100644
--- a/packages/app-legacy/package.json
+++ b/packages/app-legacy/package.json
@@ -1,6 +1,6 @@
{
"name": "example-app-legacy",
- "version": "0.2.118",
+ "version": "0.2.120-next.1",
"backstage": {
"role": "frontend"
},
@@ -92,7 +92,7 @@
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.0.0",
- "@types/jquery": "^3.3.34",
+ "@types/jquery": "^4.0.0",
"@types/react": "*",
"@types/react-dom": "*",
"@types/zen-observable": "^0.8.0",
diff --git a/packages/app-legacy/src/components/catalog/EntityPage.tsx b/packages/app-legacy/src/components/catalog/EntityPage.tsx
index 08d4d921b5..6a8daffad6 100644
--- a/packages/app-legacy/src/components/catalog/EntityPage.tsx
+++ b/packages/app-legacy/src/components/catalog/EntityPage.tsx
@@ -166,11 +166,11 @@ const overviewContent = (
{entityWarningContent}
-
+
-
+
@@ -186,7 +186,7 @@ const overviewContent = (
-
+
);
@@ -215,10 +215,10 @@ const serviceEntityPage = (
-
+
-
+
@@ -246,10 +246,10 @@ const websiteEntityPage = (
-
+
-
+
@@ -299,7 +299,7 @@ const apiPage = (
-
+
@@ -330,13 +330,10 @@ const userPage = (
{entityWarningContent}
-
+
-
+
@@ -349,13 +346,10 @@ const groupPage = (
{entityWarningContent}
-
+
-
+
@@ -374,25 +368,24 @@ const systemPage = (
{entityWarningContent}
-
+
-
+
-
+
-
+
-
+
{entityWarningContent}
-
+
-
+
-
+
@@ -437,13 +430,13 @@ const resourcePage = (
{entityWarningContent}
-
+
-
+
-
+
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index daa9908400..5897809ed5 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -1,5 +1,282 @@
# example-app
+## 0.0.34-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/app-defaults@1.7.7-next.1
+ - @backstage/plugin-app@0.4.3-next.1
+ - @backstage/cli@0.36.1-next.1
+ - @backstage/ui@0.14.0-next.1
+ - @backstage/plugin-catalog-graph@0.6.1-next.1
+ - @backstage/plugin-catalog-import@0.13.12-next.1
+ - @backstage/core-app-api@1.20.0-next.1
+ - @backstage/plugin-catalog-react@2.1.2-next.1
+ - @backstage/plugin-org@0.7.1-next.1
+ - @backstage/frontend-plugin-api@0.16.0-next.1
+ - @backstage/frontend-app-api@0.16.2-next.1
+ - @backstage/core-compat-api@0.5.10-next.1
+ - @backstage/plugin-kubernetes@0.12.18-next.1
+ - @backstage/plugin-notifications@0.5.16-next.1
+ - @backstage/plugin-scaffolder@1.36.2-next.1
+ - @backstage/plugin-search@1.7.1-next.1
+ - @backstage/plugin-signals@0.0.30-next.1
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/core-plugin-api@1.12.5-next.1
+ - @backstage/plugin-api-docs@0.13.6-next.1
+ - @backstage/plugin-catalog@2.0.2-next.1
+ - @backstage/plugin-home@0.9.4-next.1
+ - @backstage/plugin-scaffolder-react@1.20.1-next.1
+ - @backstage/plugin-search-react@1.11.1-next.1
+ - @backstage/plugin-techdocs@1.17.3-next.1
+ - @backstage/plugin-user-settings@0.9.2-next.1
+ - @backstage/frontend-defaults@0.5.1-next.1
+ - @backstage/plugin-app-react@0.2.2-next.1
+ - @backstage/plugin-app-visualizer@0.2.2-next.1
+ - @backstage/plugin-auth@0.1.7-next.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.1
+ - @backstage/plugin-devtools@0.1.38-next.1
+ - @backstage/plugin-home-react@0.1.37-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.1
+ - @backstage/plugin-techdocs-react@1.3.10-next.1
+
+## 0.0.34-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog@2.0.2-next.0
+ - @backstage/ui@0.14.0-next.0
+ - @backstage/plugin-scaffolder-react@1.20.1-next.0
+ - @backstage/plugin-catalog-react@2.1.1-next.0
+ - @backstage/plugin-techdocs@1.17.3-next.0
+ - @backstage/plugin-app@0.4.3-next.0
+ - @backstage/core-components@0.18.9-next.0
+ - @backstage/frontend-plugin-api@0.15.2-next.0
+ - @backstage/plugin-signals@0.0.30-next.0
+ - @backstage/plugin-scaffolder@1.36.2-next.0
+ - @backstage/frontend-app-api@0.16.2-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.0
+ - @backstage/plugin-app-visualizer@0.2.2-next.0
+ - @backstage/core-compat-api@0.5.10-next.0
+ - @backstage/plugin-api-docs@0.13.6-next.0
+ - @backstage/plugin-catalog-graph@0.6.1-next.0
+ - @backstage/plugin-catalog-import@0.13.12-next.0
+ - @backstage/plugin-home@0.9.4-next.0
+ - @backstage/plugin-org@0.7.1-next.0
+ - @backstage/plugin-user-settings@0.9.2-next.0
+ - @backstage/core-app-api@1.19.7-next.0
+ - @backstage/plugin-auth@0.1.7-next.0
+ - @backstage/plugin-devtools@0.1.38-next.0
+ - @backstage/plugin-notifications@0.5.16-next.0
+ - @backstage/plugin-search@1.7.1-next.0
+ - @backstage/cli@0.36.1-next.0
+ - @backstage/plugin-kubernetes@0.12.18-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.36-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.0
+ - @backstage/frontend-defaults@0.5.1-next.0
+ - @backstage/app-defaults@1.7.7-next.0
+ - @backstage/integration-react@1.2.17-next.0
+ - @backstage/plugin-auth-react@0.1.26-next.0
+ - @backstage/plugin-home-react@0.1.37-next.0
+ - @backstage/plugin-search-react@1.11.1-next.0
+ - @backstage/plugin-techdocs-react@1.3.10-next.0
+ - @backstage/core-plugin-api@1.12.5-next.0
+ - @backstage/plugin-app-react@0.2.2-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/config@1.3.6
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-permission-react@0.4.42-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## 0.0.33
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.36.0
+ - @backstage/plugin-catalog-react@2.1.0
+ - @backstage/ui@0.13.0
+ - @backstage/core-compat-api@0.5.9
+ - @backstage/frontend-app-api@0.16.0
+ - @backstage/core-plugin-api@1.12.4
+ - @backstage/plugin-app-visualizer@0.2.1
+ - @backstage/plugin-auth@0.1.6
+ - @backstage/core-components@0.18.8
+ - @backstage/plugin-user-settings@0.9.1
+ - @backstage/frontend-plugin-api@0.15.0
+ - @backstage/plugin-search-react@1.11.0
+ - @backstage/plugin-search@1.7.0
+ - @backstage/plugin-api-docs@0.13.5
+ - @backstage/plugin-devtools@0.1.37
+ - @backstage/core-app-api@1.19.6
+ - @backstage/plugin-scaffolder@1.36.0
+ - @backstage/plugin-techdocs@1.17.1
+ - @backstage/plugin-catalog@2.0.0
+ - @backstage/plugin-catalog-graph@0.6.0
+ - @backstage/plugin-catalog-import@0.13.11
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27
+ - @backstage/plugin-home@0.9.3
+ - @backstage/plugin-kubernetes@0.12.17
+ - @backstage/plugin-org@0.7.0
+ - @backstage/plugin-scaffolder-react@1.20.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34
+ - @backstage/plugin-app@0.4.1
+ - @backstage/plugin-app-react@0.2.1
+ - @backstage/plugin-notifications@0.5.15
+ - @backstage/plugin-permission-react@0.4.41
+ - @backstage/frontend-defaults@0.5.0
+ - @backstage/plugin-home-react@0.1.36
+ - @backstage/plugin-kubernetes-cluster@0.0.35
+ - @backstage/catalog-model@1.7.7
+ - @backstage/app-defaults@1.7.6
+ - @backstage/integration-react@1.2.16
+ - @backstage/plugin-auth-react@0.1.25
+ - @backstage/plugin-signals@0.0.29
+ - @backstage/plugin-techdocs-react@1.3.9
+
+## 0.0.33-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.5.9-next.2
+ - @backstage/ui@0.13.0-next.2
+ - @backstage/cli@0.36.0-next.2
+ - @backstage/core-app-api@1.19.6-next.1
+ - @backstage/plugin-techdocs@1.17.1-next.2
+ - @backstage/frontend-plugin-api@0.15.0-next.1
+ - @backstage/core-plugin-api@1.12.4-next.1
+ - @backstage/frontend-app-api@0.16.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.2
+ - @backstage/core-components@0.18.8-next.1
+ - @backstage/plugin-org@0.7.0-next.2
+ - @backstage/plugin-catalog-graph@0.6.0-next.2
+ - @backstage/plugin-app-react@0.2.1-next.1
+ - @backstage/plugin-app@0.4.1-next.2
+ - @backstage/frontend-defaults@0.5.0-next.1
+ - @backstage/plugin-scaffolder-react@1.20.0-next.2
+ - @backstage/plugin-api-docs@0.13.5-next.2
+ - @backstage/plugin-catalog@2.0.0-next.2
+ - @backstage/plugin-app-visualizer@0.2.1-next.2
+ - @backstage/plugin-auth@0.1.6-next.1
+ - @backstage/plugin-catalog-import@0.13.11-next.2
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.1
+ - @backstage/plugin-devtools@0.1.37-next.2
+ - @backstage/plugin-home@0.9.3-next.2
+ - @backstage/plugin-home-react@0.1.36-next.1
+ - @backstage/plugin-kubernetes@0.12.17-next.2
+ - @backstage/plugin-notifications@0.5.15-next.1
+ - @backstage/plugin-scaffolder@1.35.5-next.2
+ - @backstage/plugin-search@1.6.2-next.2
+ - @backstage/plugin-search-react@1.10.5-next.1
+ - @backstage/plugin-signals@0.0.29-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.2
+ - @backstage/plugin-techdocs-react@1.3.9-next.1
+ - @backstage/plugin-user-settings@0.9.1-next.2
+
+## 0.0.33-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.13.0-next.1
+ - @backstage/cli@0.36.0-next.1
+ - @backstage/plugin-devtools@0.1.37-next.1
+ - @backstage/plugin-scaffolder@1.35.5-next.1
+ - @backstage/plugin-api-docs@0.13.5-next.1
+ - @backstage/plugin-techdocs@1.17.1-next.1
+ - @backstage/plugin-catalog@1.34.0-next.1
+ - @backstage/plugin-catalog-react@2.1.0-next.1
+ - @backstage/plugin-scaffolder-react@1.19.8-next.1
+ - @backstage/plugin-app@0.4.1-next.1
+ - @backstage/plugin-app-visualizer@0.2.1-next.1
+ - @backstage/plugin-catalog-graph@0.5.8-next.1
+ - @backstage/plugin-catalog-import@0.13.11-next.1
+ - @backstage/plugin-home@0.9.3-next.1
+ - @backstage/plugin-org@0.6.50-next.1
+ - @backstage/app-defaults@1.7.6-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-compat-api@0.5.9-next.1
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-app-api@0.15.1-next.0
+ - @backstage/frontend-defaults@0.4.1-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/integration-react@1.2.16-next.1
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-app-react@0.2.1-next.0
+ - @backstage/plugin-auth@0.1.6-next.0
+ - @backstage/plugin-auth-react@0.1.25-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0
+ - @backstage/plugin-home-react@0.1.36-next.0
+ - @backstage/plugin-kubernetes@0.12.17-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.35-next.1
+ - @backstage/plugin-notifications@0.5.15-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-search@1.6.2-next.1
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-signals@0.0.29-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.1
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+ - @backstage/plugin-user-settings@0.9.1-next.1
+
+## 0.0.33-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.12.1-next.0
+ - @backstage/plugin-search-react@1.10.5-next.0
+ - @backstage/plugin-search@1.6.2-next.0
+ - @backstage/plugin-api-docs@0.13.5-next.0
+ - @backstage/cli@0.35.5-next.0
+ - @backstage/frontend-plugin-api@0.14.2-next.0
+ - @backstage/plugin-scaffolder@1.35.5-next.0
+ - @backstage/plugin-app@0.4.1-next.0
+ - @backstage/plugin-app-visualizer@0.2.1-next.0
+ - @backstage/plugin-catalog@1.33.1-next.0
+ - @backstage/plugin-catalog-react@2.0.1-next.0
+ - @backstage/plugin-techdocs@1.17.1-next.0
+ - @backstage/app-defaults@1.7.6-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.6-next.0
+ - @backstage/core-compat-api@0.5.9-next.0
+ - @backstage/core-components@0.18.8-next.0
+ - @backstage/core-plugin-api@1.12.4-next.0
+ - @backstage/frontend-app-api@0.15.1-next.0
+ - @backstage/frontend-defaults@0.4.1-next.0
+ - @backstage/integration-react@1.2.16-next.0
+ - @backstage/theme@0.7.2
+ - @backstage/plugin-app-react@0.2.1-next.0
+ - @backstage/plugin-auth@0.1.6-next.0
+ - @backstage/plugin-auth-react@0.1.25-next.0
+ - @backstage/plugin-catalog-common@1.1.8
+ - @backstage/plugin-catalog-graph@0.5.8-next.0
+ - @backstage/plugin-catalog-import@0.13.11-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0
+ - @backstage/plugin-devtools@0.1.37-next.0
+ - @backstage/plugin-home@0.9.3-next.0
+ - @backstage/plugin-home-react@0.1.36-next.0
+ - @backstage/plugin-kubernetes@0.12.17-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.35-next.0
+ - @backstage/plugin-notifications@0.5.15-next.0
+ - @backstage/plugin-org@0.6.50-next.0
+ - @backstage/plugin-permission-react@0.4.41-next.0
+ - @backstage/plugin-scaffolder-react@1.19.8-next.0
+ - @backstage/plugin-search-common@1.2.22
+ - @backstage/plugin-signals@0.0.29-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.0
+ - @backstage/plugin-techdocs-react@1.3.9-next.0
+ - @backstage/plugin-user-settings@0.9.1-next.0
+
## 0.0.32
### Patch Changes
diff --git a/packages/app/app-config.yaml b/packages/app/app-config.yaml
deleted file mode 100644
index 2000e785b3..0000000000
--- a/packages/app/app-config.yaml
+++ /dev/null
@@ -1,267 +0,0 @@
-app:
- packages: 'all' # ✨
-
- routes:
- bindings:
- catalog.viewTechDoc: techdocs.docRoot
- org.catalogIndex: catalog.catalogIndex
-
- pluginOverrides:
- - match:
- pluginId: pages
- info:
- description: 'This description was overridden in packages/app/app-config.yaml'
- - match:
- pluginId: /^catalog(-.*)?$/
- info:
- ownerEntityRefs: [cubic-belugas]
- - match:
- packageName: '@backstage/plugin-scaffolder'
- info:
- ownerEntityRefs: [cubic-belugas]
-
- extensions:
- # set availableLanguages example
- - api:app/app-language:
- config:
- availableLanguages: ['en', 'es', 'fr', 'de', 'ja']
- defaultLanguage: 'en'
- - entity-card:org/members-list:
- config:
- showAggregateMembersToggle: true
- initialRelationAggregation: aggregated
- - entity-card:org/ownership:
- config:
- ownedKinds: ['Component', 'API', 'System']
-
- # - apis.plugin.graphiql.browse.gitlab: true
- # - graphiql-endpoint:graphiql/gitlab: true
-
- - nav-item:search: false
- - nav-item:user-settings: false
- - nav-item:catalog
- - nav-item:api-docs
- - nav-item:scaffolder
- - nav-item:app-visualizer
-
- # Pages
- - page:catalog/entity:
- config:
- showNavItemIcons: true
- groups:
- # placing a tab at the beginning
- - overview:
- title: Overview
- # example disabling a default group
- # - development: false
- # example overriding a default group title
- - documentation:
- title: Docs
- icon: docs
- - deployment:
- title: Deployments
- # example adding a new group
- - custom:
- title: Custom
-
- # Entity page cards
- - entity-card:catalog/about:
- config:
- type: info
- - entity-card:catalog/labels
- - entity-card:catalog/links:
- config:
- filter:
- kind: component
- metadata.links:
- $exists: true
- # filter: kind:component has:links
- type: info
- # - entity-card:linguist/languages
- - entity-card:catalog-graph/relations:
- config:
- height: 300
- - entity-card:api-docs/has-apis
- - entity-card:api-docs/consumed-apis
- - entity-card:api-docs/provided-apis
- - entity-card:api-docs/providing-components
- - entity-card:api-docs/consuming-components
- # Org Plugin
- - entity-card:org/group-profile
- - entity-card:org/members-list
- - entity-card:org/ownership
- - entity-card:org/user-profile:
- config:
- maxRelations: 5
- hideIcons: true
- # - entity-card:azure-devops/readme
-
- # Entity page contents
- - entity-content:catalog/overview:
- config:
- group: overview
- - entity-content:api-docs/definition
- - entity-content:api-docs/apis:
- config:
- # example associating with a default group
- group: documentation
- icon: kind:api
- - entity-content:techdocs:
- config:
- group: documentation
- icon: techdocs
- - entity-content:kubernetes/kubernetes:
- config:
- # example disassociating with a default group
- group: false
- # - entity-content:azure-devops/pipelines
- # - entity-content:azure-devops/pull-requests
- # - entity-content:azure-devops/git-tags
-
- # Enable the catalog-unprocessed-entities tab in devtools
- - devtools-content:catalog-unprocessed-entities: true
- # Disable the catalog-unprocessed-entities element outside devtools
- - page:catalog-unprocessed-entities: false
-
- # scmAuthExtension: >-
- # createScmAuthExtension({
- # id: 'apis.scmAuth.addons.ghe',
- # factory({ bind }) {
- # bind.scmAuthAddon({
- # baseUrl: 'https://github.spotify.net',
- # api: githubAuthApiRef,
- # })
- # }
- # })
-
- # externalRouteRefs:
- # bind(catalogPlugin.externalRoutes, {
- # createComponent: scaffolderPlugin.routes.root,
- # viewTechDoc: techdocsPlugin.routes.docRoot,
- # createFromTemplate: scaffolderPlugin.routes.selectedTemplate,
- # });
- # bind(apiDocsPlugin.externalRoutes, {
- # registerApi: catalogImportPlugin.routes.importPage,
- # });
- # bind(scaffolderPlugin.externalRoutes, {
- # registerComponent: catalogImportPlugin.routes.importPage,
- # viewTechDoc: techdocsPlugin.routes.docRoot,
- # });
- # bind(orgPlugin.externalRoutes, {
- # catalogIndex: catalogPlugin.routes.catalogIndex,
- # });
-
- # extensions:
- # - plugin.catalog:
- # config:
- # externalRoutes:
- # createComponent: plugin.scaffolder.page
- # viewTechDoc: plugin.techdocs.docRootPage
- # createFromTemplate: plugin.scaffolder.templatePage
- # - graphiql.page:
- # config:
- # path: /
- # - apis.auth.providers.github:
- # config:
- # provider: ghe
-
- # - core.signInPage:
- # props:
- # provider:
- # id: google
- # title: Google
- # message: Sign In using Google
- # apiRef: googleAuthApiRef # ???
-
- # - core.nav:
- # config:
- # logo: assets/logo.png
- # layout:
- # - label: Search
- # icon: search
- # to: /search
- # items:
- # - point: search
- # - type: divider
- # - label: Menu
- # icon: menu
- # items:
- # - label: Home
- # icon: home
- # to: /catalog
- # - label: Create...
- # icon: create
- # to: /create
- # - type: divider
- # - type: scroll-wrapper
- # items:
- # - label: Tech Radar
- # icon: map
- # to: /tech-radar
- # - label: GraphiQL
- # icon: graphiql
- # to: /graphiql
- # - type: divider
- # - point: shortcuts
- # - type: space
- # - type: divider
- # - label: Settings
- # icon: avatar
- # to: /settings
- # items:
- # - point: settings
- # - core.nav/search: '@backstage/plugin-search#SidebarSearchModal'
- # - core.nav/shortcuts:
- # use: '@backstage/plugin-shortcuts#Shortcuts'
- # config:
- # allowExternalLinks: true
- # - core.nav/settings: '@backstage/plugin-user-settings#SidebarSettings'
-
- # - core.pages.index:
- # at:
- # point: core.routes
- # config:
- # path: /
- # use: 'react-router-dom#Navigate'
- # config:
- # to: /catalog
-
- # - scaffolder.page:
- # config:
- # groups:
- # - title: Recommended
- # filter:
- # entity.metadata.tags: recommended
- # - scaffolder.page/fields: '@backstage/plugin-scaffolder#LowerCaseValuePickerFieldExtension'
- # - scaffolder.page/layout: '@backstage/plugin-scaffolder#TwoColumnLayout'
-
- # - search.page/content: 'app#customSearchPage' # custom search page from somewhere
-
- # - user-settings.page.routes.advanced:
- # at:
- # point: user-settings.page/routes
- # config:
- # title: Advanced
- # path: /advanced
- # use: '@backstage/plugin-user-settings#AdvancedSettings'
-
- # - entity.card.orphanWarning
- # - entity.card.processingErrors
- # - entity.card.about
- # - entity.card.catalogGraph
- # - entity.card.pagerDuty
- # - entity.card.links
- # - entity.card.labels
- # - entity.card.githubInsightsLanguages
- # - entity.card.githubInsightsReleases
- # - entity.card.githubInsightsReadme:
- # config:
- # maxHeight: 350 # Throwing this config in to have an example, but in practice rely on default
- # - entity.card.subcomponentsCard
- # - entity.card.userProfile
- # - entity.card.ownership
- # - entity.card.likeDislikeRatings
- # - entity.content.dependsOnComponents
- # - entity.content.codeInsights
- # - entity.content.todo
- # - entity.content.feedbackResponse
diff --git a/packages/app/e2e-tests/HomePage.test.ts b/packages/app/e2e-tests/HomePage.test.ts
index 4261cc435b..1e359f66e7 100644
--- a/packages/app/e2e-tests/HomePage.test.ts
+++ b/packages/app/e2e-tests/HomePage.test.ts
@@ -43,7 +43,9 @@ test('Should render the home page', async ({ page }) => {
await enterButton.click();
// Wait for sign-in to complete
- await expect(page.getByRole('link', { name: 'Catalog' })).toBeVisible();
+ await expect(
+ page.getByRole('navigation').getByRole('link', { name: 'Catalog' }),
+ ).toBeVisible();
await page.goto('/home');
// The home page should render with the custom homepage grid
diff --git a/packages/app/e2e-tests/SearchPage.test.ts b/packages/app/e2e-tests/SearchPage.test.ts
index d8989275f0..927592a421 100644
--- a/packages/app/e2e-tests/SearchPage.test.ts
+++ b/packages/app/e2e-tests/SearchPage.test.ts
@@ -24,7 +24,9 @@ test('the results are rendered as expected', async ({ page }) => {
await enterButton.click();
// Wait for sign-in to complete before navigating
- await expect(page.getByRole('link', { name: 'Catalog' })).toBeVisible();
+ await expect(
+ page.getByRole('navigation').getByRole('link', { name: 'Catalog' }),
+ ).toBeVisible();
// Set up route interception BEFORE navigating to the search page
await page.route(`**/api/search/query?term=*`, async route => {
diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts
index 2efe991f7a..d93b6eed32 100644
--- a/packages/app/e2e-tests/app.test.ts
+++ b/packages/app/e2e-tests/app.test.ts
@@ -24,6 +24,12 @@ test('App should render the welcome page', async ({ page }) => {
await enterButton.click();
// Verify the sidebar navigation is visible after sign-in
- await expect(page.getByRole('link', { name: 'Catalog' })).toBeVisible();
- await expect(page.getByRole('link', { name: 'APIs' })).toBeVisible();
+ await expect(
+ page
+ .getByRole('navigation')
+ .getByRole('link', { name: 'Catalog', exact: true }),
+ ).toBeVisible();
+ await expect(
+ page.getByRole('link', { name: 'APIs', exact: true }),
+ ).toBeVisible();
});
diff --git a/packages/app/package.json b/packages/app/package.json
index b0d46befa8..0016ecbdc0 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "example-app",
- "version": "0.0.32",
+ "version": "0.0.34-next.1",
"backstage": {
"role": "frontend"
},
@@ -19,7 +19,7 @@
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
- "start": "backstage-cli package start --config ../../app-config.yaml --config app-config.yaml",
+ "start": "backstage-cli package start",
"test": "backstage-cli package test"
},
"browserslist": {
@@ -98,7 +98,7 @@
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.0.0",
- "@types/jquery": "^3.3.34",
+ "@types/jquery": "^4.0.0",
"@types/react": "*",
"@types/react-dom": "*",
"@types/zen-observable": "^0.8.0",
diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index 88de62663b..6a6814ae4a 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -50,40 +50,9 @@ import { convertLegacyPageExtension } from '@backstage/core-compat-api';
import { convertLegacyEntityContentExtension } from '@backstage/plugin-catalog-react/alpha';
import { pluginInfoResolver } from './pluginInfoResolver';
import { appModuleNav } from './modules/appModuleNav';
-import devtoolsPlugin from '@backstage/plugin-devtools/alpha';
-import { unprocessedEntitiesDevToolsContent } from '@backstage/plugin-catalog-unprocessed-entities/alpha';
import catalogPlugin from '@backstage/plugin-catalog/alpha';
import InfoIcon from '@material-ui/icons/Info';
-/*
-
-# Notes
-
-TODO:
- - proper createApp
- - connect extensions and plugins, provide method?
- - higher level API for creating standard extensions + higher order framework API for creating those?
- - extension config schema + validation
- - figure out how to resolve configured extension ref to runtime value, e.g. '@backstage/plugin-graphiql#GraphiqlPage'
- - make sure all shorthands work + tests
- - figure out package structure / how to ship, frontend-plugin-api/frontend-app-api
- - figure out routing, useRouteRef in the new system
- - Legacy plugins / interop
- - dynamic updates, runtime API
-
-*/
-
-/* core */
-
-// const discoverPackages = async () => {
-// // stub for now, deferring package discovery til later
-// return ['@backstage/plugin-graphiql'];
-// };
-
-/* graphiql package */
-
-/* app.tsx */
-
/**
* TechDocs does support the new frontend system so this conversion is not
* strictly necessary, but it's left here to provide a demo of the utilities for
@@ -91,7 +60,6 @@ TODO:
*/
const convertedTechdocsPlugin = convertLegacyPlugin(techdocsPlugin, {
extensions: [
- // TODO: We likely also need a way to convert an entire tree similar to collectLegacyRoutes
convertLegacyPageExtension(TechDocsIndexPage, {
name: 'index',
path: '/docs',
@@ -155,11 +123,6 @@ const notFoundErrorPageModule = createFrontendModule({
extensions: [notFoundErrorPage],
});
-const devtoolsPluginUnprocessed = createFrontendModule({
- pluginId: 'catalog-unprocessed-entities',
- extensions: [unprocessedEntitiesDevToolsContent],
-});
-
const collectedLegacyPlugins = convertLegacyAppRoot(
} />
@@ -178,67 +141,11 @@ const app = createApp({
notFoundErrorPageModule,
appModuleNav,
customHomePageModule,
- devtoolsPlugin,
- devtoolsPluginUnprocessed,
...collectedLegacyPlugins,
],
advanced: {
pluginInfoResolver,
},
- /* Handled through config instead */
- // bindRoutes({ bind }) {
- // bind(pagesPlugin.externalRoutes, { pageX: pagesPlugin.routes.pageX });
- // },
});
-// const legacyApp = createLegacyApp({ plugins: [legacyGraphiqlPlugin] });
-
export default app.createRoot();
-
-// const routes = (
-//
-// {/* } />
-// } />
-// }
-// >
-//
-//
-//
-//
-//
-//
-
-//
-//
-//
-//
-//
-
-//
-//
-//
-//
-//
-//
-// }
-// /> */}
-// {/* }
-// /> */}
-// } />
-//
-// );
-
-// export default app.createRoot(
-// <>
-// {/*
-// */}
-// {routes}
-// >,
-// );
diff --git a/packages/app/src/examples/pagesPlugin.tsx b/packages/app/src/examples/pagesPlugin.tsx
index 35426b589b..3d69dce677 100644
--- a/packages/app/src/examples/pagesPlugin.tsx
+++ b/packages/app/src/examples/pagesPlugin.tsx
@@ -23,6 +23,9 @@ import {
PageBlueprint,
FrontendPluginInfo,
useAppNode,
+ createExtensionBlueprint,
+ createExtensionInput,
+ coreExtensionData,
} from '@backstage/frontend-plugin-api';
import { useEffect, useState } from 'react';
import { Route, Routes } from 'react-router-dom';
@@ -33,11 +36,6 @@ export const externalPageXRouteRef = createExternalRouteRef({
defaultTarget: 'pages.pageX',
});
export const pageXRouteRef = createRouteRef();
-// const page2RouteRef = createSubRouteRef({
-// id: 'page2',
-// parent: page1RouteRef,
-// path: '/page2',
-// });
function PluginInfo() {
const node = useAppNode();
@@ -65,7 +63,8 @@ const IndexPage = PageBlueprint.make({
const page1Link = useRouteRef(page1RouteRef);
return (
- op
+
Example Pages Plugin
+
Navigation
{page1Link && (
Page 1
@@ -83,6 +82,54 @@ const IndexPage = PageBlueprint.make({
Settings
+
+
Permission Enablement Examples
+
+ The following pages demonstrate conditional extension enablement
+ via the if predicate using permissions. They will
+ only appear when the user has the required permissions.
+
+
+
+
+ Permission Gated Example
+ {' '}
+ — requires catalog.entity.create
+
+
+
+ Permission Card Example
+ {' '}
+ — a page that is always visible, but individual cards on it are
+ toggled by permissions
+
+
+
+
Feature Flag Enablement Examples
+
+ The following pages demonstrate conditional extension enablement
+ via the if predicate. They will only appear in the
+ router tree when their conditions are satisfied. Toggle the
+ relevant feature flags in Settings,
+ then refresh the app to see the pages appear.
+
+
+
+ Feature Flag Example —
+ requires the experimental-features flag
+
+
+ All Flags Example —
+ requires both experimental-features and{' '}
+ advanced-features ($all)
+
+
+ Any Flag Example — requires{' '}
+ either experimental-features or{' '}
+ beta-access ($any)
+
+
+
);
@@ -101,14 +148,12 @@ const Page1 = PageBlueprint.make({
const Component = () => {
const indexLink = useRouteRef(indexRouteRef);
const xLink = useRouteRef(externalPageXRouteRef);
- // const page2Link = useRouteRef(page2RouteRef);
return (
This is page 1
{indexLink &&
Go back}
Page 2
- {/*
Page 2 */}
{xLink &&
Page X}
@@ -136,7 +181,6 @@ const ExternalPage = PageBlueprint.make({
loader: async () => {
const Component = () => {
const indexLink = useRouteRef(indexRouteRef);
- // const pageXLink = useRouteRef(pageXRouteRef);
return (
@@ -150,15 +194,272 @@ const ExternalPage = PageBlueprint.make({
},
});
+// Example: Page enabled only when a single feature flag is active.
+//
+// The `if` predicate is evaluated once at app startup (before the router
+// tree is built), so this page simply won't exist in the app until the flag is
+// toggled and the page is refreshed.
+//
+// To test: enable the 'experimental-features' flag in Settings, then refresh.
+const FeatureFlagPage = PageBlueprint.make({
+ name: 'featureFlagExample',
+ params: {
+ path: '/feature-flag-example',
+ loader: async () => {
+ const Component = () => {
+ const indexLink = useRouteRef(indexRouteRef);
+ return (
+
+
Feature Flag Enabled Page
+
+ This page is only present in the app when the{' '}
+ experimental-features feature flag is active.
+
+
+ It uses a simple{' '}
+
+ {'{ featureFlags: { $contains: "experimental-features" } }'}
+ {' '}
+ predicate.
+
+ {indexLink &&
Go back}
+
+ );
+ };
+ return
;
+ },
+ },
+ if: { featureFlags: { $contains: 'experimental-features' } },
+});
+
+// Example: Page enabled only when ALL of several feature flags are active.
+//
+// The $all operator requires every nested predicate to be satisfied. This page
+// won't appear unless both 'experimental-features' and 'advanced-features' are
+// enabled at the same time.
+//
+// To test: enable BOTH flags in Settings, then refresh.
+const AllFlagsPage = PageBlueprint.make({
+ name: 'allFlagsExample',
+ params: {
+ path: '/all-flags-example',
+ loader: async () => {
+ const Component = () => {
+ const indexLink = useRouteRef(indexRouteRef);
+ return (
+
+
All Flags Required Page
+
+ This page requires both {' '}
+ experimental-features and{' '}
+ advanced-features to be active simultaneously.
+
+
+ It uses a $all predicate to AND the two conditions
+ together.
+
+ {indexLink &&
Go back}
+
+ );
+ };
+ return
;
+ },
+ },
+ if: {
+ $all: [
+ { featureFlags: { $contains: 'experimental-features' } },
+ { featureFlags: { $contains: 'advanced-features' } },
+ ],
+ },
+});
+
+// Example: Page enabled when ANY one of several feature flags is active.
+//
+// The $any operator is satisfied as soon as at least one nested predicate
+// matches. Enabling either 'experimental-features' or 'beta-access' will make
+// this page appear.
+//
+// To test: enable at least one of the two flags in Settings, then refresh.
+const AnyFlagPage = PageBlueprint.make({
+ name: 'anyFlagExample',
+ params: {
+ path: '/any-flag-example',
+ loader: async () => {
+ const Component = () => {
+ const indexLink = useRouteRef(indexRouteRef);
+ return (
+
+
Any Flag Sufficient Page
+
+ This page appears when either {' '}
+ experimental-features or beta-access is
+ active.
+
+
+ It uses a $any predicate to OR the two conditions
+ together.
+
+ {indexLink &&
Go back}
+
+ );
+ };
+ return
;
+ },
+ },
+ if: {
+ $any: [
+ { featureFlags: { $contains: 'experimental-features' } },
+ { featureFlags: { $contains: 'beta-access' } },
+ ],
+ },
+});
+
+// Blueprint for cards that attach to the PermissionCardPage below.
+//
+// Each card receives a title and description and renders a simple bordered card.
+// Individual card instances can be selectively enabled via the `if`
+// predicate, so only the cards the user is allowed to see will be instantiated.
+const PermissionExampleCardBlueprint = createExtensionBlueprint({
+ kind: 'permission-example-card',
+ attachTo: { id: 'page:pages/permissionCardExample', input: 'cards' },
+ output: [coreExtensionData.reactElement],
+ *factory(params: { title: string; description: string }) {
+ yield coreExtensionData.reactElement(
+
+
{params.title}
+
{params.description}
+
,
+ );
+ },
+});
+
+// Example: Page with cards that are individually toggled by permissions.
+//
+// The page itself is always present. What changes is which cards are
+// instantiated inside it — each card declares its own `enabled` predicate
+// and is only wired into the page if that predicate is satisfied at startup.
+//
+// To test: make sure you do NOT have the catalog.entity.create permission and
+// refresh the page — the "Restricted Card" below should disappear.
+const PermissionCardPage = PageBlueprint.makeWithOverrides({
+ name: 'permissionCardExample',
+ inputs: {
+ cards: createExtensionInput([coreExtensionData.reactElement]),
+ },
+ factory(originalFactory, { inputs }) {
+ return originalFactory({
+ path: '/permission-card-example',
+ loader: async () => {
+ const Component = () => {
+ const indexLink = useRouteRef(indexRouteRef);
+ const cards = inputs.cards.map(card =>
+ card.get(coreExtensionData.reactElement),
+ );
+ return (
+
+
Permission-Gated Card Example
+
+ This page is always visible. The cards below are individually
+ gated — each one declares its own{' '}
+ {'if: { permissions: { $contains: "..." } }'}{' '}
+ predicate. Cards whose predicate fails are never instantiated,
+ so they simply won't appear here.
+
+
+ {cards.length > 0 ? (
+ cards
+ ) : (
+
+ No cards are visible — you may lack the required
+ permissions.
+
+ )}
+
+ {indexLink &&
Go back}
+
+ );
+ };
+ return
;
+ },
+ });
+ },
+});
+
+// Always-visible card — no predicate, every user sees this.
+const PublicCard = PermissionExampleCardBlueprint.make({
+ name: 'public',
+ params: {
+ title: 'Public Card',
+ description: 'This card is visible to everyone regardless of permissions.',
+ },
+});
+
+// Permission-gated card — only instantiated when the user has
+// the catalog.entity.create permission.
+const RestrictedCard = PermissionExampleCardBlueprint.make({
+ name: 'restricted',
+ params: {
+ title: 'Restricted Card',
+ description:
+ 'This card is only visible to users who have the catalog.entity.create permission.',
+ },
+ if: { permissions: { $contains: 'catalog.entity.create' } },
+});
+
+// Feature flag-gated card — only instantiated when the user has
+// the experimental-card FF enabled.
+const FeatureFlagCard = PermissionExampleCardBlueprint.make({
+ name: 'feature-flag',
+ params: {
+ title: 'Feature Flagged Card',
+ description: 'Visible only with the experimental-card FF active.',
+ },
+ if: { featureFlags: { $contains: 'experimental-card' } },
+});
+
+// Example: Page enabled only when the user is allowed to create catalog entities.
+//
+// The `if` predicate is evaluated once at app startup (after sign-in),
+// so this page simply won't exist in the router tree if the user lacks the
+// required permission.
+const PermissionGatedPage = PageBlueprint.make({
+ name: 'permissionGatedExample',
+ params: {
+ path: '/permission-gated-example',
+ loader: async () => {
+ const Component = () => {
+ const indexLink = useRouteRef(indexRouteRef);
+ return (
+
+
Permission Gated Page
+
+ This page is only present when the user has the{' '}
+ catalog.entity.create permission.
+
+ {indexLink &&
Go back}
+
+ );
+ };
+ return
;
+ },
+ },
+ if: { permissions: { $contains: 'catalog.entity.create' } },
+});
+
export const pagesPlugin = createFrontendPlugin({
pluginId: 'pages',
- // routes: {
- // index: indexRouteRef,
- // // reference in config:
- // // 'plugin.pages.routes.index'
- // // OR
- // // 'page1'
- // },
info: {
packageJson: () => import('../../package.json'),
manifest: () => import('../../catalog-info.yaml'),
@@ -170,5 +471,23 @@ export const pagesPlugin = createFrontendPlugin({
externalRoutes: {
pageX: externalPageXRouteRef,
},
- extensions: [IndexPage, Page1, ExternalPage],
+ featureFlags: [
+ { name: 'experimental-features' },
+ { name: 'advanced-features' },
+ { name: 'beta-access' },
+ { name: 'experimental-card' },
+ ],
+ extensions: [
+ IndexPage,
+ Page1,
+ ExternalPage,
+ FeatureFlagPage,
+ AllFlagsPage,
+ AnyFlagPage,
+ PermissionCardPage,
+ PublicCard,
+ RestrictedCard,
+ PermissionGatedPage,
+ FeatureFlagCard,
+ ],
});
diff --git a/packages/app/src/modules/appModuleNav.tsx b/packages/app/src/modules/appModuleNav.tsx
index a507679c73..044cd96e23 100644
--- a/packages/app/src/modules/appModuleNav.tsx
+++ b/packages/app/src/modules/appModuleNav.tsx
@@ -27,15 +27,11 @@ import {
} from '@backstage/core-components';
import SearchIcon from '@material-ui/icons/Search';
import MenuIcon from '@material-ui/icons/Menu';
-import BuildIcon from '@material-ui/icons/Build';
import { createFrontendModule } from '@backstage/frontend-plugin-api';
import { NavContentBlueprint } from '@backstage/plugin-app-react';
import { SidebarSearchModal } from '@backstage/plugin-search';
import { NotificationsSidebarItem } from '@backstage/plugin-notifications';
-import {
- Settings,
- UserSettingsSignInAvatar,
-} from '@backstage/plugin-user-settings';
+import { UserSettingsSignInAvatar } from '@backstage/plugin-user-settings';
import { makeStyles } from '@material-ui/core/styles';
const useSidebarLogoStyles = makeStyles({
@@ -111,7 +107,9 @@ export const appModuleNav = createFrontendModule({
text={item.title}
/>
));
- nav.take('page:home'); // Skip home
+ // Consume without rendering — these are handled by dedicated sidebar groups
+ nav.take('page:home');
+ nav.take('page:search');
return (
@@ -136,8 +134,8 @@ export const appModuleNav = createFrontendModule({
to="/settings"
>
-
-
+ {nav.take('page:devtools')}
+ {nav.take('page:user-settings')}
);
diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md
index f1d26a7f4a..f46cfe28bb 100644
--- a/packages/backend-app-api/CHANGELOG.md
+++ b/packages/backend-app-api/CHANGELOG.md
@@ -1,5 +1,52 @@
# @backstage/backend-app-api
+## 1.6.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## 1.6.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## 1.6.0
+
+### Minor Changes
+
+- 545557a: Registration errors should be forwarded as BackendStartupResult
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+
+## 1.6.0-next.1
+
+### Minor Changes
+
+- 545557a: Registration errors should be forwarded as BackendStartupResult
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+
+## 1.5.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
## 1.5.0
### Minor Changes
diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json
index 97e5964e39..2ebcc05941 100644
--- a/packages/backend-app-api/package.json
+++ b/packages/backend-app-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-app-api",
- "version": "1.5.0",
+ "version": "1.6.1-next.1",
"description": "Core API used by Backstage backend apps",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts
index 0491faa96f..f782295e71 100644
--- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts
+++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts
@@ -899,7 +899,7 @@ describe('BackendInitializer', () => {
});
it('should reject duplicate plugins', async () => {
- const init = new BackendInitializer([]);
+ const init = new BackendInitializer(baseFactories);
init.add(
createBackendPlugin({
pluginId: 'test',
@@ -922,13 +922,24 @@ describe('BackendInitializer', () => {
},
}),
);
- await expect(init.start()).rejects.toThrow(
+
+ const err = await init.start().then(
+ () => {
+ throw new Error('Expected BackendStartupError to be thrown');
+ },
+ (e: BackendStartupError) => e,
+ );
+
+ expect(err).toBeInstanceOf(BackendStartupError);
+ const plugin = err?.result.plugins.find(p => p.pluginId === 'test');
+ expect(plugin?.failure?.error.message).toBe(
"Plugin 'test' is already registered",
);
+ expect(plugin?.failure?.allowed).toBe(false);
});
it('should reject duplicate modules', async () => {
- const init = new BackendInitializer([]);
+ const init = new BackendInitializer(baseFactories);
init.add(testPlugin);
init.add(
createBackendModule({
@@ -954,8 +965,202 @@ describe('BackendInitializer', () => {
},
}),
);
- await expect(init.start()).rejects.toThrow(
- "Module 'mod' for plugin 'test' is already registered",
+
+ const err = await init.start().then(
+ () => {
+ throw new Error('Expected BackendStartupError to be thrown');
+ },
+ (e: BackendStartupError) => e,
+ );
+
+ expect(err).toBeInstanceOf(BackendStartupError);
+ const plugin = err?.result.plugins.find(p => p.pluginId === 'test');
+ const modResult = plugin?.modules.find(
+ m =>
+ m.failure?.error.message ===
+ "Module 'mod' for plugin 'test' is already registered",
+ );
+ expect(modResult).toBeDefined();
+ expect(modResult?.failure?.allowed).toBe(false);
+ });
+
+ it('should allow other plugins to continue when one has a registration error', async () => {
+ const pluginAInit = jest.fn(async () => {});
+ const init = new BackendInitializer(baseFactories);
+ init.add(
+ createBackendPlugin({
+ pluginId: 'plugin-a',
+ register(reg) {
+ reg.registerInit({
+ deps: {},
+ init: pluginAInit,
+ });
+ },
+ }),
+ );
+ init.add(
+ createBackendPlugin({
+ pluginId: 'plugin-b',
+ register(reg) {
+ reg.registerInit({
+ deps: {},
+ async init() {},
+ });
+ },
+ }),
+ );
+ init.add(
+ createBackendPlugin({
+ pluginId: 'plugin-b',
+ register(reg) {
+ reg.registerInit({
+ deps: {},
+ async init() {},
+ });
+ },
+ }),
+ );
+
+ const err = await init.start().then(
+ () => {
+ throw new Error('Expected BackendStartupError to be thrown');
+ },
+ (e: BackendStartupError) => e,
+ );
+
+ expect(err).toBeInstanceOf(BackendStartupError);
+ // plugin-a should have started successfully
+ expect(pluginAInit).toHaveBeenCalled();
+ const pluginA = err?.result.plugins.find(p => p.pluginId === 'plugin-a');
+ expect(pluginA?.failure).toBeUndefined();
+ // plugin-b should have a registration failure
+ const pluginB = err?.result.plugins.find(p => p.pluginId === 'plugin-b');
+ expect(pluginB?.failure?.error.message).toBe(
+ "Plugin 'plugin-b' is already registered",
+ );
+ });
+
+ it('should permit registration errors for plugins with onPluginBootFailure: continue', async () => {
+ const init = new BackendInitializer([
+ ...baseFactories,
+ mockServices.rootConfig.factory({
+ data: {
+ backend: {
+ startup: {
+ plugins: { test: { onPluginBootFailure: 'continue' } },
+ },
+ },
+ },
+ }),
+ ]);
+ init.add(
+ createBackendPlugin({
+ pluginId: 'test',
+ register(reg) {
+ reg.registerInit({
+ deps: {},
+ async init() {},
+ });
+ },
+ }),
+ );
+ init.add(
+ createBackendPlugin({
+ pluginId: 'test',
+ register(reg) {
+ reg.registerInit({
+ deps: {},
+ async init() {},
+ });
+ },
+ }),
+ );
+
+ const { result } = await init.start();
+ const plugin = result.plugins.find(p => p.pluginId === 'test');
+ expect(plugin?.failure?.error.message).toBe(
+ "Plugin 'test' is already registered",
+ );
+ expect(plugin?.failure?.allowed).toBe(true);
+ });
+
+ it('should attribute duplicate extension point errors to the correct plugin', async () => {
+ const extensionPoint = createExtensionPoint
({ id: 'shared-ext' });
+ const init = new BackendInitializer(baseFactories);
+ init.add(
+ createBackendPlugin({
+ pluginId: 'plugin-a',
+ register(reg) {
+ reg.registerExtensionPoint(extensionPoint, 'a');
+ reg.registerInit({
+ deps: {},
+ async init() {},
+ });
+ },
+ }),
+ );
+ init.add(
+ createBackendPlugin({
+ pluginId: 'plugin-b',
+ register(reg) {
+ reg.registerExtensionPoint(extensionPoint, 'b');
+ reg.registerInit({
+ deps: {},
+ async init() {},
+ });
+ },
+ }),
+ );
+
+ const err = await init.start().then(
+ () => {
+ throw new Error('Expected BackendStartupError to be thrown');
+ },
+ (e: BackendStartupError) => e,
+ );
+
+ expect(err).toBeInstanceOf(BackendStartupError);
+ // plugin-a should succeed (registered first)
+ const pluginA = err?.result.plugins.find(p => p.pluginId === 'plugin-a');
+ expect(pluginA?.failure).toBeUndefined();
+ // plugin-b should fail due to duplicate extension point
+ const pluginB = err?.result.plugins.find(p => p.pluginId === 'plugin-b');
+ expect(pluginB?.failure?.error.message).toBe(
+ "ExtensionPoint with ID 'shared-ext' is already registered",
+ );
+ });
+
+ it('should attribute invalid registration type errors to plugin when pluginId is available', async () => {
+ const init = new BackendInitializer(baseFactories);
+ // Create a fake registration with an invalid type but valid pluginId
+ const fakeFeature = {
+ $$type: '@backstage/BackendFeature' as const,
+ version: 'v1' as const,
+ featureType: 'registrations' as const,
+ getRegistrations: () => [
+ {
+ type: 'invalid-type',
+ pluginId: 'broken-plugin',
+ init: { deps: {}, func: async () => {} },
+ extensionPoints: [],
+ },
+ ],
+ };
+ init.add(fakeFeature as any);
+
+ const err = await init.start().then(
+ () => {
+ throw new Error('Expected BackendStartupError to be thrown');
+ },
+ (e: BackendStartupError) => e,
+ );
+
+ expect(err).toBeInstanceOf(BackendStartupError);
+ const plugin = err?.result.plugins.find(
+ p => p.pluginId === 'broken-plugin',
+ );
+ expect(plugin?.failure?.error.message).toBe(
+ "Invalid registration type 'invalid-type'",
);
});
diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts
index d51befb644..8ebab723da 100644
--- a/packages/backend-app-api/src/wiring/BackendInitializer.ts
+++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts
@@ -310,77 +310,6 @@ export class BackendInitializer {
// Initialize all root scoped services
await this.#serviceRegistry.initializeEagerServicesWithScope('root');
- const pluginInits = new Map();
- const moduleInits = new Map>();
-
- // Enumerate all registrations
- for (const feature of this.#registrations) {
- for (const r of feature.getRegistrations()) {
- const provides = new Set>();
-
- if (r.type === 'plugin' || r.type === 'module') {
- // Handle v1 format: Array, unknown]>
- for (const [extRef, extImpl] of r.extensionPoints) {
- if (this.#extensionPoints.has(extRef.id)) {
- throw new Error(
- `ExtensionPoint with ID '${extRef.id}' is already registered`,
- );
- }
- this.#extensionPoints.set(extRef.id, {
- pluginId: r.pluginId,
- factory: () => extImpl,
- });
- provides.add(extRef);
- }
- } else if (r.type === 'plugin-v1.1' || r.type === 'module-v1.1') {
- // Handle v1.1 format: Array
- for (const extReg of r.extensionPoints) {
- if (this.#extensionPoints.has(extReg.extensionPoint.id)) {
- throw new Error(
- `ExtensionPoint with ID '${extReg.extensionPoint.id}' is already registered`,
- );
- }
- this.#extensionPoints.set(extReg.extensionPoint.id, {
- pluginId: r.pluginId,
- factory: extReg.factory,
- });
- provides.add(extReg.extensionPoint);
- }
- }
-
- if (r.type === 'plugin' || r.type === 'plugin-v1.1') {
- if (pluginInits.has(r.pluginId)) {
- throw new Error(`Plugin '${r.pluginId}' is already registered`);
- }
- pluginInits.set(r.pluginId, {
- provides,
- consumes: new Set(Object.values(r.init.deps)),
- init: r.init,
- });
- } else if (r.type === 'module' || r.type === 'module-v1.1') {
- let modules = moduleInits.get(r.pluginId);
- if (!modules) {
- modules = new Map();
- moduleInits.set(r.pluginId, modules);
- }
- if (modules.has(r.moduleId)) {
- throw new Error(
- `Module '${r.moduleId}' for plugin '${r.pluginId}' is already registered`,
- );
- }
- modules.set(r.moduleId, {
- provides,
- consumes: new Set(Object.values(r.init.deps)),
- init: r.init,
- });
- } else {
- throw new Error(`Invalid registration type '${(r as any).type}'`);
- }
- }
- }
-
- const pluginIds = [...pluginInits.keys()];
-
const rootConfig = await this.#serviceRegistry.get(
coreServices.rootConfig,
'root',
@@ -390,15 +319,32 @@ export class BackendInitializer {
'root',
);
+ const allRegistrations = this.#registrations.flatMap(f =>
+ f.getRegistrations(),
+ );
+
+ const allPluginIds = [
+ ...new Set(
+ allRegistrations.flatMap(r =>
+ 'pluginId' in r && typeof r.pluginId === 'string' ? [r.pluginId] : [],
+ ),
+ ),
+ ];
+
const resultCollector = createInitializationResultCollector({
- pluginIds,
+ pluginIds: allPluginIds,
logger: rootLogger,
allowBootFailurePredicate: createAllowBootFailurePredicate(rootConfig),
});
+ const { pluginInits, moduleInits } = this.#enumerateRegistrations(
+ allRegistrations,
+ resultCollector,
+ );
+
// All plugins are initialized in parallel
await Promise.all(
- pluginIds.map(async pluginId => {
+ [...pluginInits.keys()].map(async pluginId => {
try {
// Initialize all eager services
await this.#serviceRegistry.initializeEagerServicesWithScope(
@@ -491,6 +437,104 @@ export class BackendInitializer {
return { result };
}
+ #enumerateRegistrations(
+ allRegistrations: ReturnType<
+ InternalBackendRegistrations['getRegistrations']
+ >,
+ resultCollector: ReturnType,
+ ): {
+ pluginInits: Map;
+ moduleInits: Map>;
+ } {
+ const pluginInits = new Map();
+ const moduleInits = new Map>();
+
+ for (const r of allRegistrations) {
+ const addedExtensionPointIds: string[] = [];
+ try {
+ const provides = new Set>();
+
+ if (r.type === 'plugin' || r.type === 'module') {
+ // Handle v1 format: Array, unknown]>
+ for (const [extRef, extImpl] of r.extensionPoints) {
+ if (this.#extensionPoints.has(extRef.id)) {
+ throw new Error(
+ `ExtensionPoint with ID '${extRef.id}' is already registered`,
+ );
+ }
+ this.#extensionPoints.set(extRef.id, {
+ pluginId: r.pluginId,
+ factory: () => extImpl,
+ });
+ addedExtensionPointIds.push(extRef.id);
+ provides.add(extRef);
+ }
+ } else if (r.type === 'plugin-v1.1' || r.type === 'module-v1.1') {
+ // Handle v1.1 format: Array
+ for (const extReg of r.extensionPoints) {
+ if (this.#extensionPoints.has(extReg.extensionPoint.id)) {
+ throw new Error(
+ `ExtensionPoint with ID '${extReg.extensionPoint.id}' is already registered`,
+ );
+ }
+ this.#extensionPoints.set(extReg.extensionPoint.id, {
+ pluginId: r.pluginId,
+ factory: extReg.factory,
+ });
+ addedExtensionPointIds.push(extReg.extensionPoint.id);
+ provides.add(extReg.extensionPoint);
+ }
+ }
+
+ if (r.type === 'plugin' || r.type === 'plugin-v1.1') {
+ if (pluginInits.has(r.pluginId)) {
+ throw new Error(`Plugin '${r.pluginId}' is already registered`);
+ }
+ pluginInits.set(r.pluginId, {
+ provides,
+ consumes: new Set(Object.values(r.init.deps)),
+ init: r.init,
+ });
+ } else if (r.type === 'module' || r.type === 'module-v1.1') {
+ let modules = moduleInits.get(r.pluginId);
+ if (!modules) {
+ modules = new Map();
+ moduleInits.set(r.pluginId, modules);
+ }
+ if (modules.has(r.moduleId)) {
+ throw new Error(
+ `Module '${r.moduleId}' for plugin '${r.pluginId}' is already registered`,
+ );
+ }
+ modules.set(r.moduleId, {
+ provides,
+ consumes: new Set(Object.values(r.init.deps)),
+ init: r.init,
+ });
+ } else {
+ throw new Error(`Invalid registration type '${(r as any).type}'`);
+ }
+ } catch (error: unknown) {
+ assertError(error);
+ // Clean up partially registered extension points
+ for (const id of addedExtensionPointIds) {
+ this.#extensionPoints.delete(id);
+ }
+ if ('pluginId' in r && 'moduleId' in r) {
+ resultCollector.onPluginModuleResult(r.pluginId, r.moduleId, error);
+ } else if ('pluginId' in r) {
+ pluginInits.delete(r.pluginId);
+ moduleInits.delete(r.pluginId);
+ resultCollector.onPluginResult(r.pluginId, error);
+ } else {
+ throw error;
+ }
+ }
+ }
+
+ return { pluginInits, moduleInits };
+ }
+
// It's fine to call .stop() multiple times, which for example can happen with manual stop + process exit
async stop(): Promise {
instanceRegistry.unregister(this);
diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md
index da970f1f45..f635723677 100644
--- a/packages/backend-defaults/CHANGELOG.md
+++ b/packages/backend-defaults/CHANGELOG.md
@@ -1,5 +1,138 @@
# @backstage/backend-defaults
+## 0.16.1-next.1
+
+### Patch Changes
+
+- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions.
+- 5cd814f: Refactored auditor severity log level mappings to use `zod/v4` with schema-driven defaults and type inference.
+- 6e2aaab: Fixed `AwsS3UrlReader` failing to read files from S3 buckets configured with custom endpoint hosts. When an integration was configured with a specific endpoint like `https://bucket-1.s3.eu-central-1.amazonaws.com`, the URL parser incorrectly fell through to the non-AWS code path, always defaulting the region to `us-east-1` instead of extracting it from the hostname.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/backend-app-api@1.6.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+
+## 0.16.1-next.0
+
+### Patch Changes
+
+- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/config-loader@1.10.10-next.0
+ - @backstage/backend-app-api@1.6.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/backend-dev-utils@0.1.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@2.0.0
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+
+## 0.16.0
+
+### Minor Changes
+
+- 42960f1: The actions registry invoke endpoint now accepts direct user credentials in addition to service principals, enabling CLI and other direct user clients to invoke actions.
+- 0e7d8f9: The scheduler service now uses the metrics service to create metrics, providing plugin-scoped attribution.
+- 527cf88: **BREAKING** Removed deprecated `BitbucketUrlReader`. Use the `BitbucketCloudUrlReader` or the `BitbucketServerUrlReader` instead.
+
+### Patch Changes
+
+- cc8348e: Added permissions integration to the actions registry. Actions registered with a `visibilityPermission` field are now checked against the permissions framework when listing and invoking. Denied actions are filtered from list results, and invoking a denied action returns a `404 Not Found` as if the action does not exist. Permissions are automatically registered with the `PermissionsRegistryService` so they appear in the permission policy system.
+- dee4283: Added `pluginId` field to `ActionsServiceAction` type, populated from the registering plugin's metadata.
+- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available.
+- 638e6c7: chore(deps): bump `yauzl` from 3.2.0 to 3.2.1
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500.
+- d933f62: Add configurable throttling and retry mechanism for GitLab integration.
+- b99158a: Fixed `yarn backstage-cli config:check --strict --config app-config.yaml` config validation error by adding
+ an optional `default` type discriminator to PostgreSQL connection configuration,
+ allowing `config:check` to properly validate `default` connection configurations.
+- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
+- 5fcbef2: Updated dependency `express-rate-limit` to `^8.0.0`.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/cli-node@0.3.0
+ - @backstage/integration@2.0.0
+ - @backstage/config-loader@1.10.9
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/plugin-auth-node@0.6.14
+ - @backstage/backend-app-api@1.6.0
+ - @backstage/plugin-events-node@0.4.20
+
+## 0.16.0-next.2
+
+### Patch Changes
+
+- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available.
+- 5fcbef2: Updated dependency `express-rate-limit` to `^8.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/integration@2.0.0-next.2
+ - @backstage/backend-app-api@1.6.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+ - @backstage/plugin-permission-node@0.10.11-next.1
+
+## 0.16.0-next.1
+
+### Minor Changes
+
+- 0e7d8f9: The scheduler service now uses the metrics service to create metrics, providing plugin-scoped attribution.
+- 527cf88: **BREAKING** Removed deprecated `BitbucketUrlReader`. Use the `BitbucketCloudUrlReader` or the `BitbucketServerUrlReader` instead.
+
+### Patch Changes
+
+- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500.
+- Updated dependencies
+ - @backstage/cli-node@0.2.19-next.1
+ - @backstage/integration@2.0.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.1
+ - @backstage/backend-app-api@1.5.1-next.0
+ - @backstage/backend-dev-utils@0.1.7
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
+## 0.15.3-next.0
+
+### Patch Changes
+
+- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
+- d933f62: Add configurable throttling and retry mechanism for GitLab integration.
+- b99158a: Fixed `yarn backstage-cli config:check --strict --config app-config.yaml` config validation error by adding
+ an optional `default` type discriminator to PostgreSQL connection configuration,
+ allowing `config:check` to properly validate `default` connection configurations.
+- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
+- Updated dependencies
+ - @backstage/cli-node@0.2.19-next.0
+ - @backstage/integration@1.21.0-next.0
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/backend-app-api@1.5.1-next.0
+ - @backstage/backend-dev-utils@0.1.7
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.20
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
## 0.15.2
### Patch Changes
diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json
index 6f705de7ba..33a262aadd 100644
--- a/packages/backend-defaults/package.json
+++ b/packages/backend-defaults/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-defaults",
- "version": "0.15.2",
+ "version": "0.16.1-next.1",
"description": "Backend defaults used by Backstage backend apps",
"backstage": {
"role": "node-library"
@@ -144,6 +144,7 @@
"@backstage/integration-aws-node": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
+ "@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-permission-node": "workspace:^",
"@backstage/types": "workspace:^",
"@google-cloud/storage": "^7.0.0",
@@ -164,7 +165,7 @@
"cron": "^3.0.0",
"express": "^4.22.0",
"express-promise-router": "^4.1.0",
- "express-rate-limit": "^7.5.0",
+ "express-rate-limit": "^8.2.2",
"fs-extra": "^11.2.0",
"git-url-parse": "^15.0.0",
"helmet": "^6.0.0",
@@ -193,9 +194,9 @@
"uuid": "^11.0.0",
"winston": "^3.2.1",
"winston-transport": "^4.5.0",
- "yauzl": "^3.0.0",
+ "yauzl": "^3.2.1",
"yn": "^4.0.0",
- "zod": "^3.25.76",
+ "zod": "^3.25.76 || ^4.0.0",
"zod-to-json-schema": "^3.25.1"
},
"devDependencies": {
@@ -220,7 +221,7 @@
"msw": "^1.0.0",
"node-mocks-http": "^1.0.0",
"supertest": "^7.0.0",
- "wait-for-expect": "^3.0.2"
+ "wait-for-expect": "^4.0.0"
},
"peerDependencies": {
"@google-cloud/cloud-sql-connector": "^1.4.0",
diff --git a/packages/backend-defaults/report-scheduler.api.md b/packages/backend-defaults/report-scheduler.api.md
index e0a85710a5..44a503d18c 100644
--- a/packages/backend-defaults/report-scheduler.api.md
+++ b/packages/backend-defaults/report-scheduler.api.md
@@ -6,6 +6,7 @@
import { DatabaseService } from '@backstage/backend-plugin-api';
import { HttpRouterService } from '@backstage/backend-plugin-api';
import { LoggerService } from '@backstage/backend-plugin-api';
+import { MetricsService } from '@backstage/backend-plugin-api/alpha';
import { PluginMetadataService } from '@backstage/backend-plugin-api';
import { RootLifecycleService } from '@backstage/backend-plugin-api';
import { SchedulerService } from '@backstage/backend-plugin-api';
@@ -17,6 +18,7 @@ export class DefaultSchedulerService {
static create(options: {
database: DatabaseService;
logger: LoggerService;
+ metrics: MetricsService;
rootLifecycle: RootLifecycleService;
httpRouter: HttpRouterService;
pluginMetadata: PluginMetadataService;
diff --git a/packages/backend-defaults/report-urlReader.api.md b/packages/backend-defaults/report-urlReader.api.md
index 78a35911b8..c32a63231b 100644
--- a/packages/backend-defaults/report-urlReader.api.md
+++ b/packages/backend-defaults/report-urlReader.api.md
@@ -10,7 +10,6 @@ import { AzureCredentialsManager } from '@backstage/integration';
import { AzureDevOpsCredentialsProvider } from '@backstage/integration';
import { AzureIntegration } from '@backstage/integration';
import { BitbucketCloudIntegration } from '@backstage/integration';
-import { BitbucketIntegration } from '@backstage/integration';
import { BitbucketServerIntegration } from '@backstage/integration';
import { Config } from '@backstage/config';
import { GerritIntegration } from '@backstage/integration';
@@ -190,38 +189,6 @@ export class BitbucketServerUrlReader implements UrlReaderService {
toString(): string;
}
-// @public @deprecated
-export class BitbucketUrlReader implements UrlReaderService {
- constructor(
- integration: BitbucketIntegration,
- logger: LoggerService,
- deps: {
- treeResponseFactory: ReadTreeResponseFactory;
- },
- );
- // (undocumented)
- static factory: ReaderFactory;
- // (undocumented)
- read(url: string): Promise;
- // (undocumented)
- readTree(
- url: string,
- options?: UrlReaderServiceReadTreeOptions,
- ): Promise;
- // (undocumented)
- readUrl(
- url: string,
- options?: UrlReaderServiceReadUrlOptions,
- ): Promise;
- // (undocumented)
- search(
- url: string,
- options?: UrlReaderServiceSearchOptions,
- ): Promise;
- // (undocumented)
- toString(): string;
-}
-
// @public
export class FetchUrlReader implements UrlReaderService {
static factory: ReaderFactory;
diff --git a/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.test.ts b/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.test.ts
index dbce47bea4..3f600dcbbc 100644
--- a/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.test.ts
+++ b/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.test.ts
@@ -66,6 +66,7 @@ describe('actionsServiceFactory', () => {
const mockActionsDefinition: ActionsServiceAction = {
description: 'my mock description',
id: 'my-plugin:test',
+ pluginId: 'my-plugin',
name: 'testy',
title: 'Test',
schema: {
@@ -755,6 +756,7 @@ describe('actionsServiceFactory', () => {
{
description: 'Test',
id: 'plugin-with-action:with-validation',
+ pluginId: 'plugin-with-action',
name: 'with-validation',
schema: {
input: {
diff --git a/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts
index 7348e0a185..1f6f108589 100644
--- a/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts
+++ b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts
@@ -16,24 +16,25 @@
import {
AuthService,
+ BackstageCredentials,
HttpAuthService,
LoggerService,
+ PermissionsRegistryService,
+ PermissionsService,
PluginMetadataService,
} from '@backstage/backend-plugin-api';
import PromiseRouter from 'express-promise-router';
import { Router, json } from 'express';
-import { z, AnyZodObject } from 'zod';
+import { z, AnyZodObject } from 'zod/v3';
import zodToJsonSchema from 'zod-to-json-schema';
import {
ActionsRegistryActionOptions,
ActionsRegistryService,
} from '@backstage/backend-plugin-api/alpha';
-import {
- ForwardedError,
- InputError,
- NotAllowedError,
- NotFoundError,
-} from '@backstage/errors';
+import { InputError, NotAllowedError, NotFoundError } from '@backstage/errors';
+import { AuthorizeResult } from '@backstage/plugin-permission-common';
+
+type ActionEntry = [string, ActionsRegistryActionOptions];
export class DefaultActionsRegistryService implements ActionsRegistryService {
private actions: Map> =
@@ -43,17 +44,23 @@ export class DefaultActionsRegistryService implements ActionsRegistryService {
private readonly httpAuth: HttpAuthService;
private readonly auth: AuthService;
private readonly metadata: PluginMetadataService;
+ private readonly permissions: PermissionsService;
+ private readonly permissionsRegistry: PermissionsRegistryService;
private constructor(
logger: LoggerService,
httpAuth: HttpAuthService,
auth: AuthService,
metadata: PluginMetadataService,
+ permissions: PermissionsService,
+ permissionsRegistry: PermissionsRegistryService,
) {
this.logger = logger;
this.httpAuth = httpAuth;
this.auth = auth;
this.metadata = metadata;
+ this.permissions = permissions;
+ this.permissionsRegistry = permissionsRegistry;
}
static create({
@@ -61,24 +68,46 @@ export class DefaultActionsRegistryService implements ActionsRegistryService {
logger,
auth,
metadata,
+ permissions,
+ permissionsRegistry,
}: {
httpAuth: HttpAuthService;
logger: LoggerService;
auth: AuthService;
metadata: PluginMetadataService;
+ permissions: PermissionsService;
+ permissionsRegistry: PermissionsRegistryService;
}): DefaultActionsRegistryService {
- return new DefaultActionsRegistryService(logger, httpAuth, auth, metadata);
+ return new DefaultActionsRegistryService(
+ logger,
+ httpAuth,
+ auth,
+ metadata,
+ permissions,
+ permissionsRegistry,
+ );
}
createRouter(): Router {
const router = PromiseRouter();
router.use(json());
- router.get('/.backstage/actions/v1/actions', (_, res) => {
+ router.get('/.backstage/actions/v1/actions', async (req, res) => {
+ const credentials = await this.httpAuth.credentials(req);
+ const entries = Array.from(this.actions.entries());
+
+ const allowedActions = await this.filterByPermissions(
+ entries,
+ credentials,
+ );
+
return res.json({
- actions: Array.from(this.actions.entries()).map(([id, action]) => ({
+ actions: allowedActions.map(([id, action]) => ({
id,
- ...action,
+ name: action.name,
+ title: action.title,
+ description: action.description,
+ pluginId: this.metadata.getId(),
attributes: {
// Inspired by the @modelcontextprotocol/sdk defaults for the hints.
// https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812
@@ -86,6 +115,7 @@ export class DefaultActionsRegistryService implements ActionsRegistryService {
idempotent: action.attributes?.idempotent ?? false,
readOnly: action.attributes?.readOnly ?? false,
},
+ examples: action.examples,
schema: {
input: action.schema?.input
? zodToJsonSchema(action.schema.input(z))
@@ -102,15 +132,9 @@ export class DefaultActionsRegistryService implements ActionsRegistryService {
'/.backstage/actions/v1/actions/:actionId/invoke',
async (req, res) => {
const credentials = await this.httpAuth.credentials(req);
- if (this.auth.isPrincipal(credentials, 'user')) {
- if (!credentials.principal.actor) {
- throw new NotAllowedError(
- `Actions must be invoked by a service, not a user`,
- );
- }
- } else if (this.auth.isPrincipal(credentials, 'none')) {
+ if (this.auth.isPrincipal(credentials, 'none')) {
throw new NotAllowedError(
- `Actions must be invoked by a service, not an anonymous request`,
+ `Actions must be invoked by an authenticated principal, not an anonymous request`,
);
}
@@ -120,6 +144,18 @@ export class DefaultActionsRegistryService implements ActionsRegistryService {
throw new NotFoundError(`Action "${req.params.actionId}" not found`);
}
+ if (action.visibilityPermission) {
+ const [decision] = await this.permissions.authorize(
+ [{ permission: action.visibilityPermission }],
+ { credentials },
+ );
+ if (decision.result !== AuthorizeResult.ALLOW) {
+ throw new NotFoundError(
+ `Action "${req.params.actionId}" not found`,
+ );
+ }
+ }
+
const input = action.schema?.input
? action.schema.input(z).safeParse(req.body)
: ({ success: true, data: undefined } as const);
@@ -131,31 +167,24 @@ export class DefaultActionsRegistryService implements ActionsRegistryService {
);
}
- try {
- const result = await action.action({
- input: input.data,
- credentials,
- logger: this.logger,
- });
+ const result = await action.action({
+ input: input.data,
+ credentials,
+ logger: this.logger,
+ });
- const output = action.schema?.output
- ? action.schema.output(z).safeParse(result?.output)
- : ({ success: true, data: result?.output } as const);
+ const output = action.schema?.output
+ ? action.schema.output(z).safeParse(result?.output)
+ : ({ success: true, data: result?.output } as const);
- if (!output.success) {
- throw new InputError(
- `Invalid output from action "${req.params.actionId}"`,
- output.error,
- );
- }
-
- res.json({ output: output.data });
- } catch (error) {
- throw new ForwardedError(
- `Failed execution of action "${req.params.actionId}"`,
- error,
+ if (!output.success) {
+ throw new InputError(
+ `Invalid output from action "${req.params.actionId}"`,
+ output.error,
);
}
+
+ res.json({ output: output.data });
},
);
return router;
@@ -171,6 +200,38 @@ export class DefaultActionsRegistryService implements ActionsRegistryService {
throw new Error(`Action with id "${id}" is already registered`);
}
+ if (options.visibilityPermission) {
+ this.permissionsRegistry.addPermissions([options.visibilityPermission]);
+ }
+
this.actions.set(id, options);
}
+
+ private async filterByPermissions(
+ entries: ActionEntry[],
+ credentials: BackstageCredentials,
+ ): Promise {
+ const permissionedEntries = entries.filter(
+ ([_, action]) => action.visibilityPermission,
+ );
+
+ if (permissionedEntries.length === 0) {
+ return entries;
+ }
+
+ const decisions = await this.permissions.authorize(
+ permissionedEntries.map(([_, action]) => ({
+ permission: action.visibilityPermission!,
+ })),
+ { credentials },
+ );
+
+ const deniedIds = new Set(
+ permissionedEntries
+ .filter((_, index) => decisions[index].result !== AuthorizeResult.ALLOW)
+ .map(([id]) => id),
+ );
+
+ return entries.filter(([id]) => !deniedIds.has(id));
+ }
}
diff --git a/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts
index 79c103c284..39392c101c 100644
--- a/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts
+++ b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts
@@ -22,8 +22,12 @@ import {
import { httpRouterServiceFactory } from '../../../entrypoints/httpRouter';
import request from 'supertest';
import { actionsRegistryServiceFactory } from './actionsRegistryServiceFactory';
-import { InputError } from '@backstage/errors';
+import { InputError, NotFoundError } from '@backstage/errors';
import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha';
+import {
+ AuthorizeResult,
+ createPermission,
+} from '@backstage/plugin-permission-common';
describe('actionsRegistryServiceFactory', () => {
const defaultServices = [
@@ -107,6 +111,57 @@ describe('actionsRegistryServiceFactory', () => {
expect(true).toBe(true);
});
+
+ it('should enforce types on example input and output', () => {
+ createBackendPlugin({
+ pluginId: 'my-plugin',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ actionsRegistry: actionsRegistryServiceRef,
+ },
+ async init({ actionsRegistry }) {
+ actionsRegistry.register({
+ name: 'test',
+ title: 'Test',
+ description: 'Test',
+ schema: {
+ input: z =>
+ z.object({
+ name: z.string(),
+ }),
+ output: z =>
+ z.object({
+ ok: z.boolean(),
+ }),
+ },
+ examples: [
+ {
+ title: 'Valid example',
+ input: { name: 'test' },
+ output: { ok: true },
+ },
+ {
+ title: 'Bad input',
+ // @ts-expect-error - name must be a string
+ input: { name: 123 },
+ },
+ {
+ title: 'Bad output',
+ input: { name: 'test' },
+ // @ts-expect-error - ok must be a boolean
+ output: { ok: 'yes' },
+ },
+ ],
+ action: async () => ({ output: { ok: true } }),
+ });
+ },
+ });
+ },
+ });
+
+ expect(true).toBe(true);
+ });
});
describe('/.backstage/actions/v1/actions', () => {
@@ -282,6 +337,79 @@ describe('actionsRegistryServiceFactory', () => {
});
});
+ it('should return examples in the action list', async () => {
+ const pluginSubject = createBackendPlugin({
+ pluginId: 'my-plugin',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ actionsRegistry: actionsRegistryServiceRef,
+ },
+ async init({ actionsRegistry }) {
+ actionsRegistry.register({
+ name: 'test',
+ title: 'Test',
+ description: 'Test',
+ schema: {
+ input: z =>
+ z.object({
+ name: z.string(),
+ }),
+ output: z =>
+ z.object({
+ ok: z.boolean(),
+ }),
+ },
+ examples: [
+ {
+ title: 'Basic usage',
+ description: 'A simple example',
+ input: { name: 'world' },
+ output: { ok: true },
+ },
+ {
+ title: 'Without output',
+ input: { name: 'test' },
+ },
+ ],
+ action: async () => ({ output: { ok: true } }),
+ });
+ },
+ });
+ },
+ });
+
+ const { server } = await startTestBackend({
+ features: [pluginSubject, ...defaultServices],
+ });
+
+ const { body, status } = await request(server).get(
+ '/api/my-plugin/.backstage/actions/v1/actions',
+ );
+
+ expect(status).toBe(200);
+
+ expect(body).toMatchObject({
+ actions: [
+ {
+ name: 'test',
+ examples: [
+ {
+ title: 'Basic usage',
+ description: 'A simple example',
+ input: { name: 'world' },
+ output: { ok: true },
+ },
+ {
+ title: 'Without output',
+ input: { name: 'test' },
+ },
+ ],
+ },
+ ],
+ });
+ });
+
it('should forces registration of input and output schema as objects', async () => {
const pluginSubject = createBackendPlugin({
pluginId: 'my-plugin',
@@ -439,7 +567,7 @@ describe('actionsRegistryServiceFactory', () => {
});
});
- it('should throw an error if the action is invoked by a user', async () => {
+ it('should allow actions to be invoked by a user', async () => {
const testServices = [
actionsRegistryServiceFactory,
httpRouterServiceFactory,
@@ -460,12 +588,8 @@ describe('actionsRegistryServiceFactory', () => {
name: 'test',
});
- expect(status).toBe(403);
- expect(body).toMatchObject({
- error: {
- message: 'Actions must be invoked by a service, not a user',
- },
- });
+ expect(status).toBe(200);
+ expect(body).toMatchObject({ output: { ok: true } });
});
it('should validate the output of the action if provided', async () => {
@@ -510,7 +634,7 @@ describe('actionsRegistryServiceFactory', () => {
expect(body).toMatchObject({ output: { ok: true } });
});
- it('should return the error from the action if it throws', async () => {
+ it('should forward the original error when the action throws a known error', async () => {
const { server } = await startTestBackend({
features: [pluginSubject, ...defaultServices],
});
@@ -528,11 +652,341 @@ describe('actionsRegistryServiceFactory', () => {
expect(status).toBe(400);
expect(body).toMatchObject({
error: {
- message: expect.stringContaining(
- 'Failed execution of action "my-plugin:test"',
- ),
+ name: 'InputError',
+ message: 'test',
+ },
+ });
+ });
+
+ it('should forward a NotFoundError from the action with 404 status', async () => {
+ const { server } = await startTestBackend({
+ features: [pluginSubject, ...defaultServices],
+ });
+
+ mockAction.mockRejectedValue(new NotFoundError('entity not found'));
+
+ const { body, status } = await request(server)
+ .post(
+ '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke',
+ )
+ .send({
+ name: 'test',
+ });
+
+ expect(status).toBe(404);
+ expect(body).toMatchObject({
+ error: {
+ name: 'NotFoundError',
+ message: 'entity not found',
},
});
});
});
+
+ describe('permissions', () => {
+ const testPermission = createPermission({
+ name: 'test.action.use',
+ attributes: {},
+ });
+
+ it('should filter out actions with denied permissions when listing', async () => {
+ const pluginSubject = createBackendPlugin({
+ pluginId: 'my-plugin',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ actionsRegistry: actionsRegistryServiceRef,
+ },
+ async init({ actionsRegistry }) {
+ actionsRegistry.register({
+ name: 'public-action',
+ title: 'Public Action',
+ description: 'No permission required',
+ schema: {
+ input: z => z.object({}),
+ output: z => z.object({}),
+ },
+ action: async () => ({ output: {} }),
+ });
+ actionsRegistry.register({
+ name: 'protected-action',
+ title: 'Protected Action',
+ description: 'Permission required',
+ visibilityPermission: testPermission,
+ schema: {
+ input: z => z.object({}),
+ output: z => z.object({}),
+ },
+ action: async () => ({ output: {} }),
+ });
+ },
+ });
+ },
+ });
+
+ const { server } = await startTestBackend({
+ features: [
+ pluginSubject,
+ actionsRegistryServiceFactory,
+ httpRouterServiceFactory,
+ mockServices.httpAuth.factory({
+ defaultCredentials: mockCredentials.service('user:default/mock'),
+ }),
+ mockServices.permissions.factory({
+ result: AuthorizeResult.DENY,
+ }),
+ ],
+ });
+
+ const { body, status } = await request(server).get(
+ '/api/my-plugin/.backstage/actions/v1/actions',
+ );
+
+ expect(status).toBe(200);
+ expect(body.actions).toHaveLength(1);
+ expect(body.actions[0].name).toBe('public-action');
+ });
+
+ it('should include actions with allowed permissions when listing', async () => {
+ const pluginSubject = createBackendPlugin({
+ pluginId: 'my-plugin',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ actionsRegistry: actionsRegistryServiceRef,
+ },
+ async init({ actionsRegistry }) {
+ actionsRegistry.register({
+ name: 'protected-action',
+ title: 'Protected Action',
+ description: 'Permission required',
+ visibilityPermission: testPermission,
+ schema: {
+ input: z => z.object({}),
+ output: z => z.object({}),
+ },
+ action: async () => ({ output: {} }),
+ });
+ },
+ });
+ },
+ });
+
+ const { server } = await startTestBackend({
+ features: [
+ pluginSubject,
+ actionsRegistryServiceFactory,
+ httpRouterServiceFactory,
+ mockServices.httpAuth.factory({
+ defaultCredentials: mockCredentials.service('user:default/mock'),
+ }),
+ mockServices.permissions.factory({
+ result: AuthorizeResult.ALLOW,
+ }),
+ ],
+ });
+
+ const { body, status } = await request(server).get(
+ '/api/my-plugin/.backstage/actions/v1/actions',
+ );
+
+ expect(status).toBe(200);
+ expect(body.actions).toHaveLength(1);
+ expect(body.actions[0].name).toBe('protected-action');
+ });
+
+ it('should return 404 when invoking an action with denied permission', async () => {
+ const pluginSubject = createBackendPlugin({
+ pluginId: 'my-plugin',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ actionsRegistry: actionsRegistryServiceRef,
+ },
+ async init({ actionsRegistry }) {
+ actionsRegistry.register({
+ name: 'protected-action',
+ title: 'Protected Action',
+ description: 'Permission required',
+ visibilityPermission: testPermission,
+ schema: {
+ input: z => z.object({}),
+ output: z => z.object({}),
+ },
+ action: async () => ({ output: {} }),
+ });
+ },
+ });
+ },
+ });
+
+ const { server } = await startTestBackend({
+ features: [
+ pluginSubject,
+ actionsRegistryServiceFactory,
+ httpRouterServiceFactory,
+ mockServices.httpAuth.factory({
+ defaultCredentials: mockCredentials.service('user:default/mock'),
+ }),
+ mockServices.permissions.factory({
+ result: AuthorizeResult.DENY,
+ }),
+ ],
+ });
+
+ const { body, status } = await request(server).post(
+ '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:protected-action/invoke',
+ );
+
+ expect(status).toBe(404);
+ expect(body).toMatchObject({
+ error: {
+ message: 'Action "my-plugin:protected-action" not found',
+ },
+ });
+ });
+
+ it('should allow invoking an action when permission is granted', async () => {
+ const mockAction = jest.fn().mockResolvedValue({ output: { ok: true } });
+
+ const pluginSubject = createBackendPlugin({
+ pluginId: 'my-plugin',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ actionsRegistry: actionsRegistryServiceRef,
+ },
+ async init({ actionsRegistry }) {
+ actionsRegistry.register({
+ name: 'protected-action',
+ title: 'Protected Action',
+ description: 'Permission required',
+ visibilityPermission: testPermission,
+ schema: {
+ input: z => z.object({}),
+ output: z => z.object({ ok: z.boolean() }),
+ },
+ action: mockAction,
+ });
+ },
+ });
+ },
+ });
+
+ const { server } = await startTestBackend({
+ features: [
+ pluginSubject,
+ actionsRegistryServiceFactory,
+ httpRouterServiceFactory,
+ mockServices.httpAuth.factory({
+ defaultCredentials: mockCredentials.service('user:default/mock'),
+ }),
+ mockServices.permissions.factory({
+ result: AuthorizeResult.ALLOW,
+ }),
+ ],
+ });
+
+ const { body, status } = await request(server).post(
+ '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:protected-action/invoke',
+ );
+
+ expect(status).toBe(200);
+ expect(body).toMatchObject({ output: { ok: true } });
+ expect(mockAction).toHaveBeenCalled();
+ });
+
+ it('should pass the correct permission to the authorize call', async () => {
+ const permissionsMock = mockServices.permissions.mock({
+ authorize: async () => [{ result: AuthorizeResult.ALLOW }],
+ });
+
+ const pluginSubject = createBackendPlugin({
+ pluginId: 'my-plugin',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ actionsRegistry: actionsRegistryServiceRef,
+ },
+ async init({ actionsRegistry }) {
+ actionsRegistry.register({
+ name: 'protected-action',
+ title: 'Protected Action',
+ description: 'Permission required',
+ visibilityPermission: testPermission,
+ schema: {
+ input: z => z.object({}),
+ output: z => z.object({}),
+ },
+ action: async () => ({ output: {} }),
+ });
+ },
+ });
+ },
+ });
+
+ const { server } = await startTestBackend({
+ features: [
+ pluginSubject,
+ actionsRegistryServiceFactory,
+ httpRouterServiceFactory,
+ mockServices.httpAuth.factory({
+ defaultCredentials: mockCredentials.service('user:default/mock'),
+ }),
+ permissionsMock.factory,
+ ],
+ });
+
+ await request(server).get('/api/my-plugin/.backstage/actions/v1/actions');
+
+ expect(permissionsMock.authorize).toHaveBeenCalledWith(
+ [{ permission: testPermission }],
+ expect.objectContaining({ credentials: expect.anything() }),
+ );
+ });
+
+ it('should register the permission with the permissions registry', async () => {
+ const permissionsRegistryMock = mockServices.permissionsRegistry.mock();
+
+ const pluginSubject = createBackendPlugin({
+ pluginId: 'my-plugin',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ actionsRegistry: actionsRegistryServiceRef,
+ },
+ async init({ actionsRegistry }) {
+ actionsRegistry.register({
+ name: 'protected-action',
+ title: 'Protected Action',
+ description: 'Permission required',
+ visibilityPermission: testPermission,
+ schema: {
+ input: z => z.object({}),
+ output: z => z.object({}),
+ },
+ action: async () => ({ output: {} }),
+ });
+ },
+ });
+ },
+ });
+
+ await startTestBackend({
+ features: [
+ pluginSubject,
+ actionsRegistryServiceFactory,
+ httpRouterServiceFactory,
+ mockServices.httpAuth.factory({
+ defaultCredentials: mockCredentials.service('user:default/mock'),
+ }),
+ permissionsRegistryMock.factory,
+ ],
+ });
+
+ expect(permissionsRegistryMock.addPermissions).toHaveBeenCalledWith([
+ testPermission,
+ ]);
+ });
+ });
});
diff --git a/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts
index 8c19b8148c..4ac6b2c782 100644
--- a/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts
+++ b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts
@@ -32,13 +32,25 @@ export const actionsRegistryServiceFactory = createServiceFactory({
httpAuth: coreServices.httpAuth,
logger: coreServices.logger,
auth: coreServices.auth,
+ permissions: coreServices.permissions,
+ permissionsRegistry: coreServices.permissionsRegistry,
},
- factory: ({ metadata, httpRouter, httpAuth, logger, auth }) => {
+ factory: ({
+ metadata,
+ httpRouter,
+ httpAuth,
+ logger,
+ auth,
+ permissions,
+ permissionsRegistry,
+ }) => {
const actionsRegistryService = DefaultActionsRegistryService.create({
httpAuth,
logger,
auth,
metadata,
+ permissions,
+ permissionsRegistry,
});
httpRouter.use(actionsRegistryService.createRouter());
diff --git a/packages/backend-defaults/src/entrypoints/auditor/utils.ts b/packages/backend-defaults/src/entrypoints/auditor/utils.ts
index 719a227bf9..0513aa84b2 100644
--- a/packages/backend-defaults/src/entrypoints/auditor/utils.ts
+++ b/packages/backend-defaults/src/entrypoints/auditor/utils.ts
@@ -16,8 +16,20 @@
import type { Config } from '@backstage/config';
import { InputError } from '@backstage/errors';
-import { z } from 'zod';
-import { CONFIG_ROOT_KEY, severityLogLevelMappingsSchema } from './types';
+import { z } from 'zod/v4';
+
+const CONFIG_ROOT_KEY = 'backend.auditor';
+
+const logLevel = z.enum(['debug', 'info', 'warn', 'error']);
+
+const severityLogLevelMappingsSchema = z.object({
+ low: logLevel.default('debug'),
+ medium: logLevel.default('info'),
+ high: logLevel.default('info'),
+ critical: logLevel.default('info'),
+});
+
+type SeverityLogLevelMappings = z.infer;
/**
* Gets the `backend.auditor.severityLogLevelMappings` configuration.
@@ -26,41 +38,32 @@ import { CONFIG_ROOT_KEY, severityLogLevelMappingsSchema } from './types';
* @returns The validated severity-to-log-level mappings.
* @throws error - {@link @backstage/errors#InputError} if the mapping configuration is invalid.
*/
-export function getSeverityLogLevelMappings(config: Config) {
+export function getSeverityLogLevelMappings(
+ config: Config,
+): SeverityLogLevelMappings {
const auditorConfig = config.getOptionalConfig(CONFIG_ROOT_KEY);
- const severityLogLevelMappings = {
- low:
- auditorConfig?.getOptionalString('severityLogLevelMappings.low') ??
- 'debug',
- medium:
- auditorConfig?.getOptionalString('severityLogLevelMappings.medium') ??
- 'info',
- high:
- auditorConfig?.getOptionalString('severityLogLevelMappings.high') ??
- 'info',
- critical:
- auditorConfig?.getOptionalString('severityLogLevelMappings.critical') ??
- 'info',
- } as Required>;
+ const input = {
+ low: auditorConfig?.getOptionalString('severityLogLevelMappings.low'),
+ medium: auditorConfig?.getOptionalString('severityLogLevelMappings.medium'),
+ high: auditorConfig?.getOptionalString('severityLogLevelMappings.high'),
+ critical: auditorConfig?.getOptionalString(
+ 'severityLogLevelMappings.critical',
+ ),
+ };
- const res = severityLogLevelMappingsSchema.safeParse(
- severityLogLevelMappings,
- );
- if (!res.success) {
- const key = res.error.issues.at(0)?.path.at(0) as string;
- const value = (
- res.error.issues.at(0) as unknown as Record
- ).received as string;
- const validKeys = (
- res.error.issues.at(0) as unknown as Record
- ).options as string[];
+ const parsed = severityLogLevelMappingsSchema.safeParse(input);
+
+ if (!parsed.success) {
+ const issue = parsed.error.issues[0];
+ const key = issue.path[0] as keyof typeof input;
+ const receivedValue = input[key];
throw new InputError(
- `The configuration value for 'backend.auditor.severityLogLevelMappings.${key}' was given an invalid value: '${value}'. Expected one of the following valid values: '${validKeys.join(
+ `The configuration value for '${CONFIG_ROOT_KEY}.severityLogLevelMappings.${key}' was given an invalid value: '${receivedValue}'. Expected one of the following valid values: '${logLevel.options.join(
', ',
)}'.`,
);
}
- return severityLogLevelMappings;
+ return parsed.data;
}
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.test.ts
index 61f9bd0639..3de69ba566 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.test.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.test.ts
@@ -20,6 +20,7 @@ import waitForExpect from 'wait-for-expect';
import { DefaultSchedulerService } from './DefaultSchedulerService';
import { createTestScopedSignal } from './__testUtils__/createTestScopedSignal';
import { PluginMetadataService } from '@backstage/backend-plugin-api';
+import { metricsServiceMock } from '@backstage/backend-test-utils/alpha';
jest.setTimeout(60_000);
@@ -32,6 +33,7 @@ describe('TaskScheduler', () => {
getId: () => 'test',
} satisfies PluginMetadataService;
const testScopedSignal = createTestScopedSignal();
+ const metrics = metricsServiceMock.mock();
it.each(databases.eachSupportedId())(
'can return a working v1 plugin impl, %p',
@@ -42,6 +44,7 @@ describe('TaskScheduler', () => {
const manager = DefaultSchedulerService.create({
database,
logger,
+ metrics,
rootLifecycle,
httpRouter,
pluginMetadata,
@@ -71,6 +74,7 @@ describe('TaskScheduler', () => {
const manager = DefaultSchedulerService.create({
database,
logger,
+ metrics,
rootLifecycle,
httpRouter,
pluginMetadata,
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.ts
index 0a7b9fbb87..07531cffc8 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.ts
@@ -27,6 +27,7 @@ import { Duration } from 'luxon';
import { migrateBackendTasks } from '../database/migrateBackendTasks';
import { PluginTaskSchedulerImpl } from './PluginTaskSchedulerImpl';
import { PluginTaskSchedulerJanitor } from './PluginTaskSchedulerJanitor';
+import { MetricsService } from '@backstage/backend-plugin-api/alpha';
/**
* Default implementation of the task scheduler service.
@@ -37,6 +38,7 @@ export class DefaultSchedulerService {
static create(options: {
database: DatabaseService;
logger: LoggerService;
+ metrics: MetricsService;
rootLifecycle: RootLifecycleService;
httpRouter: HttpRouterService;
pluginMetadata: PluginMetadataService;
@@ -67,6 +69,7 @@ export class DefaultSchedulerService {
options.pluginMetadata.getId(),
databaseFactory,
options.logger,
+ options.metrics,
options.rootLifecycle,
);
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts
index 14fedfb3c0..04c2b17ad3 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts
@@ -16,6 +16,7 @@
import { LocalTaskWorker } from './LocalTaskWorker';
import { mockServices } from '@backstage/backend-test-utils';
+import { ConflictError } from '@backstage/errors';
import waitFor from 'wait-for-expect';
jest.setTimeout(10_000);
@@ -110,6 +111,54 @@ describe('LocalTaskWorker', () => {
controller.abort();
});
+ it('can cancel a running task', async () => {
+ let receivedSignal: AbortSignal | undefined;
+ const fn = jest.fn(async (signal: AbortSignal) => {
+ receivedSignal = signal;
+ await new Promise(r => setTimeout(r, 5000));
+ });
+ const controller = new AbortController();
+
+ const worker = new LocalTaskWorker('a', fn, logger);
+ worker.start(
+ {
+ version: 2,
+ cadence: 'PT10S',
+ timeoutAfterDuration: 'PT10S',
+ },
+ { signal: controller.signal },
+ );
+
+ await waitFor(() => {
+ expect(fn).toHaveBeenCalledTimes(1);
+ });
+
+ expect(receivedSignal?.aborted).toBe(false);
+ worker.cancel();
+ expect(receivedSignal?.aborted).toBe(true);
+
+ controller.abort();
+ });
+
+ it('cannot cancel a task that is not running', async () => {
+ const fn = jest.fn();
+ const controller = new AbortController();
+
+ const worker = new LocalTaskWorker('a', fn, logger);
+ worker.start(
+ {
+ version: 2,
+ initialDelayDuration: 'PT1000S',
+ cadence: 'PT10S',
+ timeoutAfterDuration: 'PT10S',
+ },
+ { signal: controller.signal },
+ );
+
+ expect(() => worker.cancel()).toThrow(ConflictError);
+ controller.abort();
+ });
+
it('goes through the expected states', async () => {
const fn = jest
.fn()
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts
index efecb1c0ed..168649436c 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts
@@ -29,6 +29,7 @@ import { delegateAbortController, serializeError, sleep } from './util';
*/
export class LocalTaskWorker {
private abortWait: AbortController | undefined;
+ private taskAbortController: AbortController | undefined;
#taskState: Exclude = {
status: 'idle',
};
@@ -93,6 +94,13 @@ export class LocalTaskWorker {
this.abortWait.abort();
}
+ cancel(): void {
+ if (!this.taskAbortController) {
+ throw new ConflictError(`Task ${this.taskId} is not running`);
+ }
+ this.taskAbortController.abort();
+ }
+
taskState(): TaskApiTasksResponse['taskState'] {
return this.#taskState;
}
@@ -134,10 +142,10 @@ export class LocalTaskWorker {
): Promise {
// Abort the task execution either if the worker is stopped, or if the
// task timeout is hit
- const taskAbortController = delegateAbortController(signal);
+ this.taskAbortController = delegateAbortController(signal);
const timeoutDuration = Duration.fromISO(settings.timeoutAfterDuration);
const timeoutHandle = setTimeout(() => {
- taskAbortController.abort();
+ this.taskAbortController?.abort();
}, timeoutDuration.as('milliseconds'));
this.#taskState = {
@@ -152,7 +160,7 @@ export class LocalTaskWorker {
};
try {
- await this.fn(taskAbortController.signal);
+ await this.fn(this.taskAbortController.signal);
this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!;
this.#taskState.lastRunError = undefined;
} catch (e) {
@@ -162,7 +170,8 @@ export class LocalTaskWorker {
// release resources
clearTimeout(timeoutHandle);
- taskAbortController.abort();
+ this.taskAbortController.abort();
+ this.taskAbortController = undefined;
}
/**
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts
index 5872ae793e..cc8aac5f51 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts
@@ -27,6 +27,7 @@ import {
parseDuration,
} from './PluginTaskSchedulerImpl';
import { createDeferred } from '@backstage/types';
+import { metricsServiceMock } from '@backstage/backend-test-utils/alpha';
jest.setTimeout(60_000);
@@ -56,6 +57,7 @@ describe('PluginTaskManagerImpl', () => {
'myplugin',
async () => knex,
mockServices.logger.mock(),
+ metricsServiceMock.mock(),
{
addShutdownHook,
addBeforeShutdownHook: jest.fn(),
@@ -397,6 +399,100 @@ describe('PluginTaskManagerImpl', () => {
);
});
+ describe('cancelTask with local scope', () => {
+ it('can cancel a running task', async () => {
+ const { manager } = await init('SQLITE_3');
+
+ const promise = createDeferred();
+
+ await manager.scheduleTask({
+ id: 'task1',
+ timeout: Duration.fromMillis(5000),
+ frequency: Duration.fromObject({ years: 1 }),
+ fn: async () => {
+ promise.resolve();
+ await new Promise(r => setTimeout(r, 20000));
+ },
+ scope: 'local',
+ });
+
+ await promise;
+ await expect(manager.cancelTask('task1')).resolves.toBeUndefined();
+ }, 60_000);
+
+ it('cannot cancel a task that is not running', async () => {
+ const { manager } = await init('SQLITE_3');
+
+ const fn = jest.fn();
+ await manager.scheduleTask({
+ id: 'task1',
+ timeout: Duration.fromMillis(5000),
+ frequency: Duration.fromObject({ years: 1 }),
+ initialDelay: Duration.fromObject({ years: 1 }),
+ fn,
+ scope: 'local',
+ });
+
+ await expect(manager.cancelTask('task1')).rejects.toThrow(ConflictError);
+ }, 60_000);
+ });
+
+ describe('cancelTask with global scope', () => {
+ it.each(databases.eachSupportedId())(
+ 'can cancel a running task, %p',
+ async databaseId => {
+ const { manager } = await init(databaseId);
+
+ const promise = createDeferred();
+
+ await manager.scheduleTask({
+ id: 'task1',
+ timeout: Duration.fromMillis(5000),
+ frequency: Duration.fromObject({ years: 1 }),
+ fn: async () => {
+ promise.resolve();
+ await new Promise(r => setTimeout(r, 20000));
+ },
+ scope: 'global',
+ });
+
+ await promise;
+ await expect(manager.cancelTask('task1')).resolves.toBeUndefined();
+ },
+ );
+
+ it.each(databases.eachSupportedId())(
+ 'cannot cancel a non-existent task, %p',
+ async databaseId => {
+ const { manager } = await init(databaseId);
+
+ await expect(manager.cancelTask('nonexistent')).rejects.toThrow(
+ NotFoundError,
+ );
+ },
+ );
+
+ it.each(databases.eachSupportedId())(
+ 'cannot cancel a task that is not running, %p',
+ async databaseId => {
+ const { manager } = await init(databaseId);
+
+ await manager.scheduleTask({
+ id: 'task1',
+ timeout: Duration.fromMillis(5000),
+ frequency: Duration.fromObject({ years: 1 }),
+ initialDelay: Duration.fromObject({ years: 1 }),
+ fn: jest.fn(),
+ scope: 'global',
+ });
+
+ await expect(manager.cancelTask('task1')).rejects.toThrow(
+ ConflictError,
+ );
+ },
+ );
+ });
+
describe('parseDuration', () => {
it('should parse durations', () => {
expect(parseDuration({ milliseconds: 5000 })).toEqual('PT5S');
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts
index 90f5d08e4a..2aad8647fe 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts
@@ -24,7 +24,13 @@ import {
SchedulerServiceTaskRunner,
SchedulerServiceTaskScheduleDefinition,
} from '@backstage/backend-plugin-api';
-import { Counter, Histogram, Gauge, metrics, trace } from '@opentelemetry/api';
+import { trace } from '@opentelemetry/api';
+import {
+ MetricsService,
+ MetricsServiceCounter,
+ MetricsServiceGauge,
+ MetricsServiceHistogram,
+} from '@backstage/backend-plugin-api/alpha';
import { Knex } from 'knex';
import { Duration } from 'luxon';
import express from 'express';
@@ -45,10 +51,10 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
private readonly allScheduledTasks: SchedulerServiceTaskDescriptor[] = [];
private readonly shutdownInitiated: Promise;
- private readonly counter: Counter;
- private readonly duration: Histogram;
- private readonly lastStarted: Gauge;
- private readonly lastCompleted: Gauge;
+ private readonly counter: MetricsServiceCounter;
+ private readonly duration: MetricsServiceHistogram;
+ private readonly lastStarted: MetricsServiceGauge;
+ private readonly lastCompleted: MetricsServiceGauge;
private readonly pluginId: string;
private readonly databaseFactory: () => Promise;
@@ -58,24 +64,27 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
pluginId: string,
databaseFactory: () => Promise,
logger: LoggerService,
+ metrics: MetricsService,
rootLifecycle: RootLifecycleService,
) {
this.pluginId = pluginId;
this.databaseFactory = databaseFactory;
this.logger = logger;
- const meter = metrics.getMeter('default');
- this.counter = meter.createCounter('backend_tasks.task.runs.count', {
+ this.counter = metrics.createCounter('backend_tasks.task.runs.count', {
description: 'Total number of times a task has been run',
});
- this.duration = meter.createHistogram('backend_tasks.task.runs.duration', {
- description: 'Histogram of task run durations',
- unit: 'seconds',
- });
- this.lastStarted = meter.createGauge('backend_tasks.task.runs.started', {
+ this.duration = metrics.createHistogram(
+ 'backend_tasks.task.runs.duration',
+ {
+ description: 'Histogram of task run durations',
+ unit: 'seconds',
+ },
+ );
+ this.lastStarted = metrics.createGauge('backend_tasks.task.runs.started', {
description: 'Epoch timestamp seconds when the task was last started',
unit: 'seconds',
});
- this.lastCompleted = meter.createGauge(
+ this.lastCompleted = metrics.createGauge(
'backend_tasks.task.runs.completed',
{
description: 'Epoch timestamp seconds when the task was last completed',
@@ -98,6 +107,17 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
await TaskWorker.trigger(knex, id);
}
+ async cancelTask(id: string): Promise {
+ const localTask = this.localWorkersById.get(id);
+ if (localTask) {
+ localTask.cancel();
+ return;
+ }
+
+ const knex = await this.databaseFactory();
+ await TaskWorker.cancel(knex, id);
+ }
+
async scheduleTask(
task: SchedulerServiceTaskScheduleDefinition &
SchedulerServiceTaskInvocationDefinition,
@@ -197,6 +217,15 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
},
);
+ router.post(
+ '/.backstage/scheduler/v1/tasks/:id/cancel',
+ async (req, res) => {
+ const { id } = req.params;
+ await this.cancelTask(id);
+ res.status(200).end();
+ },
+ );
+
return router;
}
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts
index 5bd3fb38cb..2f690ef499 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts
@@ -15,6 +15,7 @@
*/
import { TestDatabases, mockServices } from '@backstage/backend-test-utils';
+import { ConflictError, NotFoundError } from '@backstage/errors';
import { DateTime, Duration } from 'luxon';
import waitForExpect from 'wait-for-expect';
import { migrateBackendTasks } from '../database/migrateBackendTasks';
@@ -25,123 +26,154 @@ import { TaskSettingsV2 } from './types';
jest.setTimeout(60_000);
-describe('TaskWorker', () => {
+const databases = TestDatabases.create();
+
+describe.each(databases.eachSupportedId())('TaskWorker, %s', databaseId => {
const logger = mockServices.logger.mock();
- const databases = TestDatabases.create();
const testScopedSignal = createTestScopedSignal();
- beforeEach(() => {
+ let knex: Awaited>;
+
+ beforeEach(async () => {
+ knex = await databases.init(databaseId);
+ await migrateBackendTasks(knex);
jest.resetAllMocks();
});
- it.each(databases.eachSupportedId())(
- 'goes through the expected states, %p',
- async databaseId => {
- const knex = await databases.init(databaseId);
- await migrateBackendTasks(knex);
+ afterEach(async () => {
+ await knex?.destroy();
+ });
- const fn = jest.fn(
- async () => new Promise(resolve => setTimeout(resolve, 50)),
- );
- const settings: TaskSettingsV2 = {
- version: 2,
- cadence: '*/2 * * * * *',
- initialDelayDuration: Duration.fromObject({ seconds: 3 }).toISO()!, // over 1 second, to cover for 1-second granularity in some database timestamps
- timeoutAfterDuration: Duration.fromObject({ minutes: 1 }).toISO()!,
- };
+ it('goes through the expected states', async () => {
+ const fn = jest.fn(
+ async () => new Promise(resolve => setTimeout(resolve, 50)),
+ );
+ const settings: TaskSettingsV2 = {
+ version: 2,
+ cadence: '*/2 * * * * *',
+ initialDelayDuration: Duration.fromObject({ seconds: 3 }).toISO()!, // over 1 second, to cover for 1-second granularity in some database timestamps
+ timeoutAfterDuration: Duration.fromObject({ minutes: 1 }).toISO()!,
+ };
- const worker = new TaskWorker('task1', fn, knex, logger);
- await worker.persistTask(settings);
+ const worker = new TaskWorker('task1', fn, knex, logger);
+ await worker.persistTask(settings);
- let row = (await knex(DB_TASKS_TABLE))[0];
- expect(row).toEqual(
- expect.objectContaining({
- id: 'task1',
- current_run_ticket: null,
- current_run_started_at: null,
- current_run_expires_at: null,
- }),
- );
- expect(JSON.parse(row.settings_json)).toEqual({
- version: 2,
- cadence: '*/2 * * * * *',
- initialDelayDuration: 'PT3S',
- timeoutAfterDuration: 'PT1M',
- });
- await expect(TaskWorker.taskStates(knex)).resolves.toEqual(
- new Map([
- [
- 'task1',
- {
- status: 'idle',
- startsAt: expect.anything(),
- },
- ],
- ]),
- );
+ let row = (await knex(DB_TASKS_TABLE))[0];
+ expect(row).toEqual(
+ expect.objectContaining({
+ id: 'task1',
+ current_run_ticket: null,
+ current_run_started_at: null,
+ current_run_expires_at: null,
+ }),
+ );
+ expect(JSON.parse(row.settings_json)).toEqual({
+ version: 2,
+ cadence: '*/2 * * * * *',
+ initialDelayDuration: 'PT3S',
+ timeoutAfterDuration: 'PT1M',
+ });
+ await expect(TaskWorker.taskStates(knex)).resolves.toEqual(
+ new Map([
+ [
+ 'task1',
+ {
+ status: 'idle',
+ startsAt: expect.anything(),
+ },
+ ],
+ ]),
+ );
- // Note that this is timing sensitive - if things take too long between
- // persistTask and findReadyTask, this test will fail. We set some margin
- // of initialDelayDuration to cover for this, but if this turns out to be
- // flaky, we may have to revisit how this test is written.
+ // Note that this is timing sensitive - if things take too long between
+ // persistTask and findReadyTask, this test will fail. We set some margin
+ // of initialDelayDuration to cover for this, but if this turns out to be
+ // flaky, we may have to revisit how this test is written.
+ await expect(worker.findReadyTask()).resolves.toEqual({
+ result: 'not-ready-yet',
+ });
+
+ await waitForExpect(async () => {
await expect(worker.findReadyTask()).resolves.toEqual({
- result: 'not-ready-yet',
+ result: 'ready',
+ settings,
});
+ });
- await waitForExpect(async () => {
- await expect(worker.findReadyTask()).resolves.toEqual({
- result: 'ready',
- settings,
- });
- });
+ row = (await knex(DB_TASKS_TABLE))[0];
+ expect(row).toEqual(
+ expect.objectContaining({
+ id: 'task1',
+ current_run_ticket: null,
+ current_run_started_at: null,
+ current_run_expires_at: null,
+ }),
+ );
- row = (await knex(DB_TASKS_TABLE))[0];
- expect(row).toEqual(
- expect.objectContaining({
- id: 'task1',
- current_run_ticket: null,
- current_run_started_at: null,
- current_run_expires_at: null,
- }),
- );
+ await expect(worker.tryClaimTask('ticket', settings)).resolves.toBe(true);
- await expect(worker.tryClaimTask('ticket', settings)).resolves.toBe(true);
+ row = (await knex(DB_TASKS_TABLE))[0];
+ expect(row).toEqual(
+ expect.objectContaining({
+ id: 'task1',
+ current_run_ticket: 'ticket',
+ current_run_started_at: expect.anything(),
+ current_run_expires_at: expect.anything(),
+ }),
+ );
+ await expect(TaskWorker.taskStates(knex)).resolves.toEqual(
+ new Map([
+ [
+ 'task1',
+ {
+ status: 'running',
+ startedAt: expect.anything(),
+ timesOutAt: expect.anything(),
+ },
+ ],
+ ]),
+ );
- row = (await knex(DB_TASKS_TABLE))[0];
- expect(row).toEqual(
- expect.objectContaining({
- id: 'task1',
- current_run_ticket: 'ticket',
- current_run_started_at: expect.anything(),
- current_run_expires_at: expect.anything(),
- }),
- );
- await expect(TaskWorker.taskStates(knex)).resolves.toEqual(
- new Map([
- [
- 'task1',
- {
- status: 'running',
- startedAt: expect.anything(),
- timesOutAt: expect.anything(),
- },
- ],
- ]),
- );
+ await expect(worker.tryReleaseTask('ticket', settings)).resolves.toBe(true);
- await expect(worker.tryReleaseTask('ticket', settings)).resolves.toBe(
- true,
- );
+ row = (await knex(DB_TASKS_TABLE))[0];
+ expect(row).toEqual(
+ expect.objectContaining({
+ id: 'task1',
+ current_run_ticket: null,
+ current_run_started_at: null,
+ current_run_expires_at: null,
+ }),
+ );
+ await expect(TaskWorker.taskStates(knex)).resolves.toEqual(
+ new Map([
+ [
+ 'task1',
+ {
+ status: 'idle',
+ startsAt: expect.anything(),
+ lastRunEndedAt: expect.anything(),
+ },
+ ],
+ ]),
+ );
+ });
- row = (await knex(DB_TASKS_TABLE))[0];
- expect(row).toEqual(
- expect.objectContaining({
- id: 'task1',
- current_run_ticket: null,
- current_run_started_at: null,
- current_run_expires_at: null,
- }),
- );
+ it('logs error when the task throws', async () => {
+ jest.spyOn(logger, 'error');
+ const fn = jest.fn().mockRejectedValue(new Error('failed'));
+ const settings: TaskSettingsV2 = {
+ version: 2,
+ initialDelayDuration: undefined,
+ cadence: '* * * * * *',
+ timeoutAfterDuration: Duration.fromMillis(60000).toISO()!,
+ };
+ const checkFrequency = Duration.fromObject({ milliseconds: 100 });
+ const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency);
+ worker.start(settings, { signal: testScopedSignal() });
+
+ await waitForExpect(async () => {
+ expect(logger.error).toHaveBeenCalled();
await expect(TaskWorker.taskStates(knex)).resolves.toEqual(
new Map([
[
@@ -150,438 +182,386 @@ describe('TaskWorker', () => {
status: 'idle',
startsAt: expect.anything(),
lastRunEndedAt: expect.anything(),
+ lastRunError: expect.stringContaining('failed'),
},
],
]),
);
- },
- );
+ });
+ });
- it.each(databases.eachSupportedId())(
- 'logs error when the task throws, %p',
- async databaseId => {
- const knex = await databases.init(databaseId);
- await migrateBackendTasks(knex);
+ it('runs tasks more than once even when the task throws', async () => {
+ const fn = jest.fn().mockRejectedValue(new Error('failed'));
+ const settings: TaskSettingsV2 = {
+ version: 2,
+ initialDelayDuration: undefined,
+ cadence: '* * * * * *',
+ timeoutAfterDuration: Duration.fromMillis(60000).toISO()!,
+ };
+ const checkFrequency = Duration.fromObject({ milliseconds: 100 });
+ const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency);
+ worker.start(settings, { signal: testScopedSignal() });
- jest.spyOn(logger, 'error');
- const fn = jest.fn().mockRejectedValue(new Error('failed'));
- const settings: TaskSettingsV2 = {
- version: 2,
- initialDelayDuration: undefined,
- cadence: '* * * * * *',
- timeoutAfterDuration: Duration.fromMillis(60000).toISO()!,
- };
- const checkFrequency = Duration.fromObject({ milliseconds: 100 });
- const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency);
- worker.start(settings, { signal: testScopedSignal() });
+ await waitForExpect(() => {
+ expect(fn).toHaveBeenCalledTimes(3);
+ });
+ });
- await waitForExpect(async () => {
- expect(logger.error).toHaveBeenCalled();
- await expect(TaskWorker.taskStates(knex)).resolves.toEqual(
- new Map([
- [
- 'task1',
- {
- status: 'idle',
- startsAt: expect.anything(),
- lastRunEndedAt: expect.anything(),
- lastRunError: expect.stringContaining('failed'),
- },
- ],
- ]),
- );
+ it('does not clobber ticket lock when stolen', async () => {
+ const fn = jest.fn(
+ async () => new Promise(resolve => setTimeout(resolve, 50)),
+ );
+ const settings: TaskSettingsV2 = {
+ version: 2,
+ initialDelayDuration: undefined,
+ cadence: '* * * * * *',
+ timeoutAfterDuration: Duration.fromMillis(60000).toISO()!,
+ };
+
+ const worker = new TaskWorker('task1', fn, knex, logger);
+ await worker.persistTask(settings);
+
+ await waitForExpect(async () => {
+ await expect(worker.findReadyTask()).resolves.toEqual({
+ result: 'ready',
+ settings,
});
- },
- );
+ });
- it.each(databases.eachSupportedId())(
- 'runs tasks more than once even when the task throws, %p',
- async databaseId => {
- const knex = await databases.init(databaseId);
- await migrateBackendTasks(knex);
+ await expect(worker.tryClaimTask('ticket', settings)).resolves.toBe(true);
- const fn = jest.fn().mockRejectedValue(new Error('failed'));
- const settings: TaskSettingsV2 = {
- version: 2,
- initialDelayDuration: undefined,
- cadence: '* * * * * *',
- timeoutAfterDuration: Duration.fromMillis(60000).toISO()!,
- };
- const checkFrequency = Duration.fromObject({ milliseconds: 100 });
- const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency);
- worker.start(settings, { signal: testScopedSignal() });
+ let row = (await knex(DB_TASKS_TABLE))[0];
+ expect(row).toEqual(
+ expect.objectContaining({
+ id: 'task1',
+ current_run_ticket: 'ticket',
+ current_run_started_at: expect.anything(),
+ current_run_expires_at: expect.anything(),
+ }),
+ );
- await waitForExpect(() => {
- expect(fn).toHaveBeenCalledTimes(3);
+ await knex(DB_TASKS_TABLE)
+ .where('id', '=', 'task1')
+ .update({ current_run_ticket: 'stolen' });
+
+ await expect(worker.tryReleaseTask('ticket', settings)).resolves.toBe(
+ false,
+ );
+
+ row = (await knex(DB_TASKS_TABLE))[0];
+ expect(row).toEqual(
+ expect.objectContaining({
+ id: 'task1',
+ current_run_ticket: 'stolen',
+ current_run_started_at: expect.anything(),
+ current_run_expires_at: expect.anything(),
+ }),
+ );
+ });
+
+ it('gracefully handles a disappeared task row', async () => {
+ const fn = jest.fn(async () => {});
+ const settings: TaskSettingsV2 = {
+ version: 2,
+ initialDelayDuration: undefined,
+ cadence: '* * * * * *',
+ timeoutAfterDuration: Duration.fromMillis(60000).toISO()!,
+ };
+
+ const worker1 = new TaskWorker('task1', fn, knex, logger);
+ await worker1.persistTask(settings);
+ await knex(DB_TASKS_TABLE).where('id', '=', 'task1').delete();
+ await expect(worker1.findReadyTask()).resolves.toEqual({
+ result: 'abort',
+ });
+
+ const worker2 = new TaskWorker('task2', fn, knex, logger);
+ await worker2.persistTask(settings);
+
+ await waitForExpect(async () => {
+ await expect(worker2.findReadyTask()).resolves.toEqual({
+ result: 'ready',
+ settings,
});
- },
- );
+ });
- it.each(databases.eachSupportedId())(
- 'does not clobber ticket lock when stolen, %p',
- async databaseId => {
- const knex = await databases.init(databaseId);
- await migrateBackendTasks(knex);
+ await knex(DB_TASKS_TABLE).where('id', '=', 'task2').delete();
+ await expect(worker2.tryClaimTask('ticket', settings)).resolves.toBe(false);
- const fn = jest.fn(
- async () => new Promise(resolve => setTimeout(resolve, 50)),
- );
- const settings: TaskSettingsV2 = {
- version: 2,
- initialDelayDuration: undefined,
- cadence: '* * * * * *',
- timeoutAfterDuration: Duration.fromMillis(60000).toISO()!,
- };
+ const worker3 = new TaskWorker('task3', fn, knex, logger);
+ await worker3.persistTask(settings);
- const worker = new TaskWorker('task1', fn, knex, logger);
- await worker.persistTask(settings);
-
- await waitForExpect(async () => {
- await expect(worker.findReadyTask()).resolves.toEqual({
- result: 'ready',
- settings,
- });
+ await waitForExpect(async () => {
+ await expect(worker3.findReadyTask()).resolves.toEqual({
+ result: 'ready',
+ settings,
});
+ });
- await expect(worker.tryClaimTask('ticket', settings)).resolves.toBe(true);
+ await expect(worker3.tryClaimTask('ticket', settings)).resolves.toBe(true);
+ await knex(DB_TASKS_TABLE).where('id', '=', 'task3').delete();
+ await expect(worker3.tryReleaseTask('ticket', settings)).resolves.toBe(
+ false,
+ );
+ });
- let row = (await knex(DB_TASKS_TABLE))[0];
- expect(row).toEqual(
- expect.objectContaining({
- id: 'task1',
- current_run_ticket: 'ticket',
- current_run_started_at: expect.anything(),
- current_run_expires_at: expect.anything(),
- }),
- );
+ it('respects initialDelayDuration per worker', async () => {
+ const abortFirst = new AbortController();
+ const settings: TaskSettingsV2 = {
+ version: 2,
+ initialDelayDuration: 'PT0.3S',
+ cadence: 'PT0.1S',
+ timeoutAfterDuration: 'PT10S',
+ };
- await knex(DB_TASKS_TABLE)
- .where('id', '=', 'task1')
- .update({ current_run_ticket: 'stolen' });
+ // Start a single worker and make sure it waits and then goes to work
+ const fn1 = jest.fn(async () => {});
+ const worker1 = new TaskWorker(
+ 'task1',
+ fn1,
+ knex,
+ logger,
+ Duration.fromMillis(10),
+ );
+ await worker1.start(settings, { signal: abortFirst.signal });
- await expect(worker.tryReleaseTask('ticket', settings)).resolves.toBe(
- false,
- );
+ expect(fn1).toHaveBeenCalledTimes(0);
+ await new Promise(resolve => setTimeout(resolve, 250));
+ expect(fn1).toHaveBeenCalledTimes(0);
+ await new Promise(resolve => setTimeout(resolve, 100));
+ expect(fn1.mock.calls.length).toBeGreaterThan(0);
- row = (await knex(DB_TASKS_TABLE))[0];
- expect(row).toEqual(
- expect.objectContaining({
- id: 'task1',
- current_run_ticket: 'stolen',
- current_run_started_at: expect.anything(),
- current_run_expires_at: expect.anything(),
- }),
- );
- },
- );
+ // Start a second worker and make sure it waits but the first worker still works along
+ const fn2 = jest.fn();
+ const promise2 = new Promise(resolve => fn2.mockImplementation(resolve));
+ const worker2 = new TaskWorker(
+ 'task1',
+ fn2,
+ knex,
+ logger,
+ Duration.fromMillis(10),
+ );
+ await worker2.start(settings, { signal: testScopedSignal() });
- it.each(databases.eachSupportedId())(
- 'gracefully handles a disappeared task row, %p',
- async databaseId => {
- const knex = await databases.init(databaseId);
- await migrateBackendTasks(knex);
+ // We eventually abort the first worker just to make sure that the second
+ // one for sure will get a go at running the task
+ setTimeout(() => abortFirst.abort(), 1000);
- const fn = jest.fn(async () => {});
- const settings: TaskSettingsV2 = {
- version: 2,
- initialDelayDuration: undefined,
- cadence: '* * * * * *',
- timeoutAfterDuration: Duration.fromMillis(60000).toISO()!,
- };
+ const before = fn1.mock.calls.length;
+ await promise2;
+ expect(fn1.mock.calls.length).toBeGreaterThan(before);
+ });
- const worker1 = new TaskWorker('task1', fn, knex, logger);
- await worker1.persistTask(settings);
- await knex(DB_TASKS_TABLE).where('id', '=', 'task1').delete();
- await expect(worker1.findReadyTask()).resolves.toEqual({
- result: 'abort',
- });
+ it('next_run_start_at is always the min between schedule changes from cron frequency', async () => {
+ const fn = jest.fn(
+ async () => new Promise(resolve => setTimeout(resolve, 50)),
+ );
+ const settings: TaskSettingsV2 = {
+ version: 2,
+ cadence: '*/15 * * * *',
+ initialDelayDuration: 'PT2M',
+ timeoutAfterDuration: 'PT1M',
+ };
- const worker2 = new TaskWorker('task2', fn, knex, logger);
- await worker2.persistTask(settings);
+ const worker = new TaskWorker('task99', fn, knex, logger);
+ await worker.persistTask(settings);
+ const row1 = (await knex(DB_TASKS_TABLE))[0];
- await waitForExpect(async () => {
- await expect(worker2.findReadyTask()).resolves.toEqual({
- result: 'ready',
- settings,
- });
- });
+ const settings2 = {
+ ...settings,
+ cadence: '*/2 * * * *',
+ initialDelayDuration: 'PT1M',
+ };
+ await worker.persistTask(settings2);
+ const row2 = (await knex(DB_TASKS_TABLE))[0];
- await knex(DB_TASKS_TABLE).where('id', '=', 'task2').delete();
- await expect(worker2.tryClaimTask('ticket', settings)).resolves.toBe(
- false,
- );
+ expect(row2.next_run_start_at).not.toStrictEqual(row1.next_run_start_at);
- const worker3 = new TaskWorker('task3', fn, knex, logger);
- await worker3.persistTask(settings);
+ const settings3 = { ...settings };
+ await worker.persistTask(settings3);
+ const row3 = (await knex(DB_TASKS_TABLE))[0];
- await waitForExpect(async () => {
- await expect(worker3.findReadyTask()).resolves.toEqual({
- result: 'ready',
- settings,
- });
- });
+ // The new timestamp can basically be 0 or a minute depending on how the
+ // initialDelayDuration falls right on a cron boundary. This kinda
+ // contrived check removes a test flakiness based on wall clock time.
+ expect(
+ Math.abs(
+ +new Date(row3.next_run_start_at!) - +new Date(row2.next_run_start_at!),
+ ),
+ ).toBeLessThanOrEqual(60_000);
+ });
- await expect(worker3.tryClaimTask('ticket', settings)).resolves.toBe(
- true,
- );
- await knex(DB_TASKS_TABLE).where('id', '=', 'task3').delete();
- await expect(worker3.tryReleaseTask('ticket', settings)).resolves.toBe(
- false,
- );
- },
- );
+ it('next_run_start_at is always the min between schedule changes when using human duration frequency', async () => {
+ const fn = jest.fn(
+ async () => new Promise(resolve => setTimeout(resolve, 50)),
+ );
- it.each(databases.eachSupportedId())(
- 'respects initialDelayDuration per worker, %p',
- async databaseId => {
- const knex = await databases.init(databaseId);
- await migrateBackendTasks(knex);
+ const initialSettings: TaskSettingsV2 = {
+ version: 2,
+ cadence: 'PT120M',
+ timeoutAfterDuration: 'PT1M',
+ };
- const abortFirst = new AbortController();
- const settings: TaskSettingsV2 = {
- version: 2,
- initialDelayDuration: 'PT0.3S',
- cadence: 'PT0.1S',
- timeoutAfterDuration: 'PT10S',
- };
+ const worker = new TaskWorker('task99', fn, knex, logger);
+ await worker.persistTask(initialSettings);
+ // replicate task running, sets next_run_start_at based on cadence
+ await worker.tryClaimTask('ticket', initialSettings);
+ await worker.tryReleaseTask('ticket', initialSettings);
- // Start a single worker and make sure it waits and then goes to work
- const fn1 = jest.fn(async () => {});
- const worker1 = new TaskWorker(
- 'task1',
- fn1,
- knex,
- logger,
- Duration.fromMillis(10),
- );
- await worker1.start(settings, { signal: abortFirst.signal });
+ // grab initial row for comparisons later
+ const rowAfterClaimAndRelease = (await knex(DB_TASKS_TABLE))[0];
- expect(fn1).toHaveBeenCalledTimes(0);
- await new Promise(resolve => setTimeout(resolve, 250));
- expect(fn1).toHaveBeenCalledTimes(0);
- await new Promise(resolve => setTimeout(resolve, 100));
- expect(fn1.mock.calls.length).toBeGreaterThan(0);
+ const settings: TaskSettingsV2 = {
+ ...initialSettings,
+ cadence: 'PT60M',
+ };
+ await worker.persistTask(settings);
+ const row1 = (await knex(DB_TASKS_TABLE))[0];
- // Start a second worker and make sure it waits but the first worker still works along
- const fn2 = jest.fn();
- const promise2 = new Promise(resolve => fn2.mockImplementation(resolve));
- const worker2 = new TaskWorker(
- 'task1',
- fn2,
- knex,
- logger,
- Duration.fromMillis(10),
- );
- await worker2.start(settings, { signal: testScopedSignal() });
+ const rowAfterClaimAndReleaseNextStartAt = DateTime.fromJSDate(
+ new Date(rowAfterClaimAndRelease.next_run_start_at!),
+ );
+ const row1NextStartAt = DateTime.fromJSDate(
+ new Date(row1.next_run_start_at!),
+ );
+ const now = DateTime.now();
+ expect(
+ rowAfterClaimAndReleaseNextStartAt.diff(row1NextStartAt).as('minutes'),
+ ).toBeCloseTo(60, 1); // ensure that next start at is sooner than initial by one hour
+ expect(row1NextStartAt.diff(now).as('minutes')).toBeCloseTo(60, 1); // ensure that next start at is later than now by one hour
+ expect(
+ rowAfterClaimAndReleaseNextStartAt.diff(now).as('minutes'),
+ ).toBeCloseTo(120, 1);
- // We eventually abort the first worker just to make sure that the second
- // one for sure will get a go at running the task
- setTimeout(() => abortFirst.abort(), 1000);
+ const settings2 = {
+ ...settings,
+ };
+ await worker.persistTask(settings2);
+ const row2 = (await knex(DB_TASKS_TABLE))[0];
- const before = fn1.mock.calls.length;
- await promise2;
- expect(fn1.mock.calls.length).toBeGreaterThan(before);
+ expect(row2.next_run_start_at).toStrictEqual(row1.next_run_start_at);
+ });
- await knex.destroy();
- },
- );
+ it('next_run_start_at is always the min between schedule changes when using human duration frequency with initial start delay', async () => {
+ const fn = jest.fn(
+ async () => new Promise(resolve => setTimeout(resolve, 50)),
+ );
- it.each(databases.eachSupportedId())(
- 'next_run_start_at is always the min between schedule changes from cron frequency, %p',
- async databaseId => {
- const knex = await databases.init(databaseId);
- await migrateBackendTasks(knex);
+ const initialSettings: TaskSettingsV2 = {
+ version: 2,
+ cadence: 'PT120M',
+ initialDelayDuration: 'PT2M',
+ timeoutAfterDuration: 'PT1M',
+ };
- const fn = jest.fn(
- async () => new Promise(resolve => setTimeout(resolve, 50)),
- );
- const settings: TaskSettingsV2 = {
- version: 2,
- cadence: '*/15 * * * *',
- initialDelayDuration: 'PT2M',
- timeoutAfterDuration: 'PT1M',
- };
+ const worker = new TaskWorker('task99', fn, knex, logger);
+ await worker.persistTask(initialSettings);
+ // replicate task running, sets next_run_start_at based on cadence
+ await worker.tryClaimTask('ticket', initialSettings);
+ await worker.tryReleaseTask('ticket', initialSettings);
- const worker = new TaskWorker('task99', fn, knex, logger);
- await worker.persistTask(settings);
- const row1 = (await knex(DB_TASKS_TABLE))[0];
+ // grab initial row for comparisons later
+ const rowAfterClaimAndRelease = (await knex(DB_TASKS_TABLE))[0];
- const settings2 = {
- ...settings,
- cadence: '*/2 * * * *',
- initialDelayDuration: 'PT1M',
- };
- await worker.persistTask(settings2);
- const row2 = (await knex(DB_TASKS_TABLE))[0];
+ const settings: TaskSettingsV2 = {
+ ...initialSettings,
+ cadence: 'PT60M',
+ };
+ await worker.persistTask(settings);
+ const row1 = (await knex(DB_TASKS_TABLE))[0];
- expect(row2.next_run_start_at).not.toStrictEqual(row1.next_run_start_at);
+ const rowAfterClaimAndReleaseNextStartAt = DateTime.fromJSDate(
+ new Date(rowAfterClaimAndRelease.next_run_start_at!),
+ );
+ const row1NextStartAt = DateTime.fromJSDate(
+ new Date(row1.next_run_start_at!),
+ );
+ const now = DateTime.now();
+ expect(
+ rowAfterClaimAndReleaseNextStartAt.diff(row1NextStartAt).as('minutes'),
+ ).toBeCloseTo(62, 1); // ensure that next start at is sooner than initial by one hour, plus the 2 minute delay (set my tryReleaseTask)
+ expect(row1NextStartAt.diff(now).as('minutes')).toBeCloseTo(60, 1); // ensure that next start at is later than now by one hour (2 minute delay doesn't take effect here)
+ expect(
+ rowAfterClaimAndReleaseNextStartAt.diff(now).as('minutes'),
+ ).toBeCloseTo(122, 1); // includes 2 minute start delay (which is persisted from tryReleaseTask)
- const settings3 = { ...settings };
- await worker.persistTask(settings3);
- const row3 = (await knex(DB_TASKS_TABLE))[0];
+ const settings2 = {
+ ...settings,
+ };
+ await worker.persistTask(settings2);
+ const row2 = (await knex(DB_TASKS_TABLE))[0];
- // The new timestamp can basically be 0 or a minute depending on how the
- // initialDelayDuration falls right on a cron boundary. This kinda
- // contrived check removes a test flakiness based on wall clock time.
- expect(
- Math.abs(
- +new Date(row3.next_run_start_at!) -
- +new Date(row2.next_run_start_at!),
- ),
- ).toBeLessThanOrEqual(60_000);
+ expect(row2.next_run_start_at).toStrictEqual(row1.next_run_start_at);
+ });
- await knex.destroy();
- },
- );
+ it('next_run_start_at is not set for manually-triggered tasks', async () => {
+ const fn = jest.fn(
+ async () => new Promise(resolve => setTimeout(resolve, 50)),
+ );
- it.each(databases.eachSupportedId())(
- 'next_run_start_at is always the min between schedule changes when using human duration frequency, %p',
- async databaseId => {
- const knex = await databases.init(databaseId);
- await migrateBackendTasks(knex);
+ const initialSettings: TaskSettingsV2 = {
+ version: 2,
+ cadence: 'manual',
+ timeoutAfterDuration: 'PT1M',
+ };
- const fn = jest.fn(
- async () => new Promise(resolve => setTimeout(resolve, 50)),
- );
+ const worker = new TaskWorker('task99', fn, knex, logger);
+ await worker.persistTask(initialSettings);
+ await worker.tryClaimTask('ticket', initialSettings);
+ await worker.tryReleaseTask('ticket', initialSettings);
- const initialSettings: TaskSettingsV2 = {
- version: 2,
- cadence: 'PT120M',
- timeoutAfterDuration: 'PT1M',
- };
+ const row = (await knex(DB_TASKS_TABLE))[0];
+ expect(row.next_run_start_at).toBeNull();
+ });
- const worker = new TaskWorker('task99', fn, knex, logger);
- await worker.persistTask(initialSettings);
- // replicate task running, sets next_run_start_at based on cadence
- await worker.tryClaimTask('ticket', initialSettings);
- await worker.tryReleaseTask('ticket', initialSettings);
+ it('can cancel a running task', async () => {
+ const fn = jest.fn(async () => {});
+ const settings: TaskSettingsV2 = {
+ version: 2,
+ cadence: '* * * * * *',
+ initialDelayDuration: undefined,
+ timeoutAfterDuration: Duration.fromObject({ minutes: 1 }).toISO()!,
+ };
- // grab initial row for comparisons later
- const rowAfterClaimAndRelease = (
- await knex(DB_TASKS_TABLE)
- )[0];
+ const worker = new TaskWorker('task1', fn, knex, logger);
+ await worker.persistTask(settings);
+ await worker.tryClaimTask('ticket', settings);
- const settings: TaskSettingsV2 = {
- ...initialSettings,
- cadence: 'PT60M',
- };
- await worker.persistTask(settings);
- const row1 = (await knex(DB_TASKS_TABLE))[0];
+ // Verify the task is running
+ let row = (await knex(DB_TASKS_TABLE))[0];
+ expect(row.current_run_ticket).toBe('ticket');
- const rowAfterClaimAndReleaseNextStartAt = DateTime.fromJSDate(
- new Date(rowAfterClaimAndRelease.next_run_start_at!),
- );
- const row1NextStartAt = DateTime.fromJSDate(
- new Date(row1.next_run_start_at!),
- );
- const now = DateTime.now();
- expect(
- rowAfterClaimAndReleaseNextStartAt.diff(row1NextStartAt).as('minutes'),
- ).toBeCloseTo(60, 1); // ensure that next start at is sooner than initial by one hour
- expect(row1NextStartAt.diff(now).as('minutes')).toBeCloseTo(60, 1); // ensure that next start at is later than now by one hour
- expect(
- rowAfterClaimAndReleaseNextStartAt.diff(now).as('minutes'),
- ).toBeCloseTo(120, 1);
+ await TaskWorker.cancel(knex, 'task1');
- const settings2 = {
- ...settings,
- };
- await worker.persistTask(settings2);
- const row2 = (await knex(DB_TASKS_TABLE))[0];
+ // Verify the task is now idle with a cancellation error recorded
+ row = (await knex(DB_TASKS_TABLE))[0];
+ expect(row.current_run_ticket).toBeNull();
+ expect(row.current_run_started_at).toBeNull();
+ expect(row.current_run_expires_at).toBeNull();
+ expect(row.last_run_ended_at).not.toBeNull();
+ expect(row.last_run_error_json).toContain('Task was cancelled');
+ });
- expect(row2.next_run_start_at).toStrictEqual(row1.next_run_start_at);
+ it('cannot cancel a non-existent task', async () => {
+ await expect(TaskWorker.cancel(knex, 'nonexistent')).rejects.toThrow(
+ NotFoundError,
+ );
+ });
- await knex.destroy();
- },
- );
+ it('cannot cancel a task that is not running', async () => {
+ const fn = jest.fn(async () => {});
+ const settings: TaskSettingsV2 = {
+ version: 2,
+ cadence: '* * * * * *',
+ initialDelayDuration: undefined,
+ timeoutAfterDuration: Duration.fromObject({ minutes: 1 }).toISO()!,
+ };
- it.each(databases.eachSupportedId())(
- 'next_run_start_at is always the min between schedule changes when using human duration frequency with initial start delay, %p',
- async databaseId => {
- const knex = await databases.init(databaseId);
- await migrateBackendTasks(knex);
+ const worker = new TaskWorker('task1', fn, knex, logger);
+ await worker.persistTask(settings);
- const fn = jest.fn(
- async () => new Promise(resolve => setTimeout(resolve, 50)),
- );
-
- const initialSettings: TaskSettingsV2 = {
- version: 2,
- cadence: 'PT120M',
- initialDelayDuration: 'PT2M',
- timeoutAfterDuration: 'PT1M',
- };
-
- const worker = new TaskWorker('task99', fn, knex, logger);
- await worker.persistTask(initialSettings);
- // replicate task running, sets next_run_start_at based on cadence
- await worker.tryClaimTask('ticket', initialSettings);
- await worker.tryReleaseTask('ticket', initialSettings);
-
- // grab initial row for comparisons later
- const rowAfterClaimAndRelease = (
- await knex(DB_TASKS_TABLE)
- )[0];
-
- const settings: TaskSettingsV2 = {
- ...initialSettings,
- cadence: 'PT60M',
- };
- await worker.persistTask(settings);
- const row1 = (await knex(DB_TASKS_TABLE))[0];
-
- const rowAfterClaimAndReleaseNextStartAt = DateTime.fromJSDate(
- new Date(rowAfterClaimAndRelease.next_run_start_at!),
- );
- const row1NextStartAt = DateTime.fromJSDate(
- new Date(row1.next_run_start_at!),
- );
- const now = DateTime.now();
- expect(
- rowAfterClaimAndReleaseNextStartAt.diff(row1NextStartAt).as('minutes'),
- ).toBeCloseTo(62, 1); // ensure that next start at is sooner than initial by one hour, plus the 2 minute delay (set my tryReleaseTask)
- expect(row1NextStartAt.diff(now).as('minutes')).toBeCloseTo(60, 1); // ensure that next start at is later than now by one hour (2 minute delay doesn't take effect here)
- expect(
- rowAfterClaimAndReleaseNextStartAt.diff(now).as('minutes'),
- ).toBeCloseTo(122, 1); // includes 2 minute start delay (which is persisted from tryReleaseTask)
-
- const settings2 = {
- ...settings,
- };
- await worker.persistTask(settings2);
- const row2 = (await knex(DB_TASKS_TABLE))[0];
-
- expect(row2.next_run_start_at).toStrictEqual(row1.next_run_start_at);
-
- await knex.destroy();
- },
- );
-
- it.each(databases.eachSupportedId())(
- 'next_run_start_at is not set for manually-triggered tasks, %p',
- async databaseId => {
- const knex = await databases.init(databaseId);
- await migrateBackendTasks(knex);
-
- const fn = jest.fn(
- async () => new Promise(resolve => setTimeout(resolve, 50)),
- );
-
- const initialSettings: TaskSettingsV2 = {
- version: 2,
- cadence: 'manual',
- timeoutAfterDuration: 'PT1M',
- };
-
- const worker = new TaskWorker('task99', fn, knex, logger);
- await worker.persistTask(initialSettings);
- await worker.tryClaimTask('ticket', initialSettings);
- await worker.tryReleaseTask('ticket', initialSettings);
-
- const row = (await knex(DB_TASKS_TABLE))[0];
- expect(row.next_run_start_at).toBeNull();
-
- await knex.destroy();
- },
- );
+ await expect(TaskWorker.cancel(knex, 'task1')).rejects.toThrow(
+ ConflictError,
+ );
+ });
});
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts
index e47967de90..4f32dc6863 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts
@@ -152,6 +152,36 @@ export class TaskWorker {
}
}
+ static async cancel(knex: Knex, taskId: string): Promise {
+ const [row] = await knex(DB_TASKS_TABLE)
+ .where('id', '=', taskId)
+ .select('settings_json', 'current_run_ticket');
+ if (!row) {
+ throw new NotFoundError(`Task ${taskId} does not exist`);
+ }
+ if (!row.current_run_ticket) {
+ throw new ConflictError(`Task ${taskId} is not running`);
+ }
+
+ const settings = taskSettingsV2Schema.parse(JSON.parse(row.settings_json));
+ const nextRun = TaskWorker.computeNextRunStartAt(knex, settings);
+
+ const updatedRows = await knex(DB_TASKS_TABLE)
+ .where('id', '=', taskId)
+ .where('current_run_ticket', '=', row.current_run_ticket)
+ .update({
+ next_run_start_at: nextRun,
+ current_run_ticket: knex.raw('null'),
+ current_run_started_at: knex.raw('null'),
+ current_run_expires_at: knex.raw('null'),
+ last_run_ended_at: knex.fn.now(),
+ last_run_error_json: serializeError(new Error('Task was cancelled')),
+ });
+ if (updatedRows < 1) {
+ throw new ConflictError(`Task ${taskId} is not running`);
+ }
+ }
+
static async taskStates(
knex: Knex,
): Promise> {
@@ -227,11 +257,22 @@ export class TaskWorker {
}
// Abort the task execution either if the worker is stopped, or if the
- // task timeout is hit
+ // task timeout is hit, or if the task ticket was lost (e.g. due to
+ // cancellation from another host)
const taskAbortController = delegateAbortController(signal);
const timeoutHandle = setTimeout(() => {
taskAbortController.abort();
}, Duration.fromISO(taskSettings.timeoutAfterDuration).as('milliseconds'));
+ let livenessHandle: ReturnType | undefined;
+ const scheduleLivenessCheck = () => {
+ livenessHandle = setTimeout(async () => {
+ await this.checkLiveness(ticket, taskAbortController);
+ if (!taskAbortController.signal.aborted) {
+ scheduleLivenessCheck();
+ }
+ }, this.workCheckFrequency.as('milliseconds'));
+ };
+ scheduleLivenessCheck();
try {
this.#workerState = {
@@ -248,6 +289,7 @@ export class TaskWorker {
status: 'idle',
};
clearTimeout(timeoutHandle);
+ clearTimeout(livenessHandle);
}
await this.tryReleaseTask(ticket, taskSettings);
@@ -283,7 +325,7 @@ export class TaskWorker {
// We make a conversion here to make typescript happy, because the luxon versions of the cron library and here may not be the same
const timeConverted = DateTime.fromJSDate(time.toJSDate());
- nextStartAt = this.nextRunAtRaw(timeConverted);
+ nextStartAt = TaskWorker.nextRunAtRaw(this.knex, timeConverted);
startAt ||= nextStartAt;
} else if (isManual) {
nextStartAt = this.knex.raw('null');
@@ -334,6 +376,33 @@ export class TaskWorker {
);
}
+ /**
+ * Checks whether the current task ticket is still valid in the database.
+ * If the ticket has been cleared (e.g. by cancellation or janitor cleanup),
+ * aborts the task execution.
+ */
+ private async checkLiveness(
+ ticket: string,
+ taskAbortController: AbortController,
+ ): Promise {
+ try {
+ const [row] = await this.knex(DB_TASKS_TABLE)
+ .where('id', '=', this.taskId)
+ .select('current_run_ticket');
+
+ if (!row || row.current_run_ticket !== ticket) {
+ this.logger.info(
+ `Task ticket for "${this.taskId}" is no longer valid; aborting execution`,
+ );
+ taskAbortController.abort();
+ }
+ } catch (e) {
+ this.logger.warn(
+ `Failed to check liveness for task "${this.taskId}", ${e}`,
+ );
+ }
+ }
+
/**
* Check if the task is ready to run
*/
@@ -407,48 +476,49 @@ export class TaskWorker {
return rows === 1;
}
+ private static computeNextRunStartAt(
+ knex: Knex,
+ settings: TaskSettingsV2,
+ ): Knex.Raw {
+ const isManual = settings?.cadence === 'manual';
+ const isDuration = settings?.cadence.startsWith('P');
+ const isCron = !isManual && !isDuration;
+
+ if (isCron) {
+ const time = new CronTime(settings.cadence).sendAt().toUTC();
+ const timeConverted = DateTime.fromJSDate(time.toJSDate());
+ return TaskWorker.nextRunAtRaw(knex, timeConverted);
+ }
+
+ if (isManual) {
+ return knex.raw('null');
+ }
+
+ const dt = Duration.fromISO(settings.cadence).as('seconds');
+
+ if (knex.client.config.client.includes('sqlite3')) {
+ return knex.raw(`max(datetime(next_run_start_at, ?), datetime('now'))`, [
+ `+${dt} seconds`,
+ ]);
+ }
+
+ if (knex.client.config.client.includes('mysql')) {
+ return knex.raw(
+ `greatest(next_run_start_at + interval ${dt} second, now())`,
+ );
+ }
+
+ return knex.raw(
+ `greatest(next_run_start_at + interval '${dt} seconds', now())`,
+ );
+ }
+
async tryReleaseTask(
ticket: string,
settings: TaskSettingsV2,
error?: Error,
): Promise {
- const isManual = settings?.cadence === 'manual';
- const isDuration = settings?.cadence.startsWith('P');
- const isCron = !isManual && !isDuration;
-
- let nextRun: Knex.Raw;
- if (isCron) {
- const time = new CronTime(settings.cadence).sendAt().toUTC();
- this.logger.debug(`task: ${this.taskId} will next occur around ${time}`);
- // We make a conversion here to make typescript happy, because the luxon versions of the cron library and here may not be the same
- const timeConverted = DateTime.fromJSDate(time.toJSDate());
-
- nextRun = this.nextRunAtRaw(timeConverted);
- } else if (isManual) {
- nextRun = this.knex.raw('null');
- } else {
- const dt = Duration.fromISO(settings.cadence).as('seconds');
- this.logger.debug(
- `task: ${this.taskId} will next occur around ${DateTime.now().plus({
- seconds: dt,
- })}`,
- );
-
- if (this.knex.client.config.client.includes('sqlite3')) {
- nextRun = this.knex.raw(
- `max(datetime(next_run_start_at, ?), datetime('now'))`,
- [`+${dt} seconds`],
- );
- } else if (this.knex.client.config.client.includes('mysql')) {
- nextRun = this.knex.raw(
- `greatest(next_run_start_at + interval ${dt} second, now())`,
- );
- } else {
- nextRun = this.knex.raw(
- `greatest(next_run_start_at + interval '${dt} seconds', now())`,
- );
- }
- }
+ const nextRun = TaskWorker.computeNextRunStartAt(this.knex, settings);
const rows = await this.knex(DB_TASKS_TABLE)
.where('id', '=', this.taskId)
@@ -467,12 +537,13 @@ export class TaskWorker {
return rows === 1;
}
- private nextRunAtRaw(time: DateTime): Knex.Raw {
- if (this.knex.client.config.client.includes('sqlite3')) {
- return this.knex.raw('datetime(?)', [time.toISO()]);
- } else if (this.knex.client.config.client.includes('mysql')) {
- return this.knex.raw(`?`, [time.toSQL({ includeOffset: false })]);
+ private static nextRunAtRaw(knex: Knex, time: DateTime): Knex.Raw {
+ if (knex.client.config.client.includes('sqlite3')) {
+ return knex.raw('datetime(?)', [time.toISO()]);
}
- return this.knex.raw(`?`, [time.toISO()]);
+ if (knex.client.config.client.includes('mysql')) {
+ return knex.raw(`?`, [time.toSQL({ includeOffset: false })]);
+ }
+ return knex.raw(`?`, [time.toISO()]);
}
}
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts
index f691ef78a0..64f7d00899 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts
@@ -17,7 +17,7 @@
import { JsonObject } from '@backstage/types';
import { CronTime } from 'cron';
import { Duration } from 'luxon';
-import { z } from 'zod';
+import { z } from 'zod/v4';
function isValidOptionalDurationString(d: string | undefined): boolean {
try {
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts
index 186e5f6940..8aacdc0005 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts
@@ -18,6 +18,7 @@ import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
+import { metricsServiceRef } from '@backstage/backend-plugin-api/alpha';
import { DefaultSchedulerService } from './lib/DefaultSchedulerService';
/**
@@ -37,6 +38,7 @@ export const schedulerServiceFactory = createServiceFactory({
rootLifecycle: coreServices.rootLifecycle,
httpRouter: coreServices.httpRouter,
pluginMetadata: coreServices.pluginMetadata,
+ metrics: metricsServiceRef,
},
async factory({
database,
@@ -44,6 +46,7 @@ export const schedulerServiceFactory = createServiceFactory({
rootLifecycle,
httpRouter,
pluginMetadata,
+ metrics,
}) {
return DefaultSchedulerService.create({
database,
@@ -51,6 +54,7 @@ export const schedulerServiceFactory = createServiceFactory({
rootLifecycle,
httpRouter,
pluginMetadata,
+ metrics,
});
},
});
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.test.ts
index dc3d1e5b8e..3592b3d72b 100644
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.test.ts
+++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.test.ts
@@ -128,6 +128,55 @@ describe('parseUrl', () => {
});
});
+ it('supports aws formats with custom endpoint hosts', () => {
+ expect(
+ parseUrl(
+ 'https://bucket-1.s3.eu-central-1.amazonaws.com/path/to/file.yaml',
+ {
+ host: 'bucket-1.s3.eu-central-1.amazonaws.com',
+ },
+ ),
+ ).toEqual({
+ path: 'path/to/file.yaml',
+ bucket: 'bucket-1',
+ region: 'eu-central-1',
+ });
+ expect(
+ parseUrl('https://my-bucket.s3.cn-north-1.amazonaws.com.cn/data.json', {
+ host: 'my-bucket.s3.cn-north-1.amazonaws.com.cn',
+ }),
+ ).toEqual({
+ path: 'data.json',
+ bucket: 'my-bucket',
+ region: 'cn-north-1',
+ });
+ expect(
+ parseUrl(
+ 'https://s3.eu-central-1.amazonaws.com/my-bucket/path/to/file.yaml',
+ {
+ host: 's3.eu-central-1.amazonaws.com',
+ },
+ ),
+ ).toEqual({
+ path: 'path/to/file.yaml',
+ bucket: 'my-bucket',
+ region: 'eu-central-1',
+ });
+ expect(
+ parseUrl(
+ 'https://s3.eu-central-1.amazonaws.com/my-bucket/path/to/file.yaml',
+ {
+ host: 's3.eu-central-1.amazonaws.com',
+ s3ForcePathStyle: true,
+ },
+ ),
+ ).toEqual({
+ path: 'path/to/file.yaml',
+ bucket: 'my-bucket',
+ region: 'eu-central-1',
+ });
+ });
+
it('supports all non-aws formats', () => {
expect(
parseUrl('https://my-host.com/my.bucket-3/a/puppy.jpg', {
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts
index cb776fc977..0b17ffe326 100644
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts
+++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts
@@ -74,7 +74,12 @@ export function parseUrl(
const host = parsedUrl.host;
// Treat Amazon hosted separately because it has special region logic
- if (config.host === 'amazonaws.com' || config.host === 'amazonaws.com.cn') {
+ if (
+ config.host === 'amazonaws.com' ||
+ config.host === 'amazonaws.com.cn' ||
+ config.host.endsWith('.amazonaws.com') ||
+ config.host.endsWith('.amazonaws.com.cn')
+ ) {
const match = host.match(
/^(?:([a-z0-9.-]+)\.)?s3(?:[.-]([a-z0-9-]+))?\.amazonaws\.com(\.cn)?$/,
);
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader.test.ts
deleted file mode 100644
index 1a72d2af73..0000000000
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader.test.ts
+++ /dev/null
@@ -1,656 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { ConfigReader } from '@backstage/config';
-import {
- BitbucketIntegration,
- readBitbucketIntegrationConfig,
-} from '@backstage/integration';
-import {
- createMockDirectory,
- mockServices,
- registerMswTestHooks,
-} from '@backstage/backend-test-utils';
-import fs from 'fs-extra';
-import { rest } from 'msw';
-import { setupServer } from 'msw/node';
-import path from 'node:path';
-import { NotModifiedError } from '@backstage/errors';
-import { BitbucketUrlReader } from './BitbucketUrlReader';
-import { DefaultReadTreeResponseFactory } from './tree';
-import getRawBody from 'raw-body';
-import { UrlReaderServiceReadUrlResponse } from '@backstage/backend-plugin-api';
-
-const logger = mockServices.logger.mock();
-
-describe('BitbucketUrlReader.factory', () => {
- it('only apply integration configs not inherited from bitbucketCloud or bitbucketServer', () => {
- const config = new ConfigReader({
- integrations: {
- bitbucket: [],
- bitbucketCloud: [
- {
- username: 'username',
- appPassword: 'password',
- },
- ],
- bitbucketServer: [
- {
- host: 'bitbucket-server.local',
- token: 'test-token',
- },
- ],
- },
- });
- const treeResponseFactory = DefaultReadTreeResponseFactory.create({
- config: config,
- });
-
- const tuples = BitbucketUrlReader.factory({
- config,
- logger,
- treeResponseFactory,
- });
-
- expect(tuples).toHaveLength(0);
- });
-});
-
-describe('BitbucketUrlReader', () => {
- const mockDir = createMockDirectory({ mockOsTmpDir: true });
-
- beforeEach(mockDir.clear);
-
- const treeResponseFactory = DefaultReadTreeResponseFactory.create({
- config: new ConfigReader({}),
- });
-
- const bitbucketProcessor = new BitbucketUrlReader(
- new BitbucketIntegration(
- readBitbucketIntegrationConfig(
- new ConfigReader({
- host: 'bitbucket.org',
- apiBaseUrl: 'https://api.bitbucket.org/2.0',
- }),
- ),
- ),
- logger,
- { treeResponseFactory },
- );
-
- const hostedBitbucketProcessor = new BitbucketUrlReader(
- new BitbucketIntegration(
- readBitbucketIntegrationConfig(
- new ConfigReader({
- host: 'bitbucket.mycompany.net',
- apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0',
- }),
- ),
- ),
- logger,
- { treeResponseFactory },
- );
-
- const worker = setupServer();
- registerMswTestHooks(worker);
-
- describe('readUrl', () => {
- it('should be able to readUrl via buffer without ETag', async () => {
- worker.use(
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
- (req, res, ctx) => {
- expect(req.headers.get('If-None-Match')).toBeNull();
- return res(
- ctx.status(200),
- ctx.body('foo'),
- ctx.set('ETag', 'etag-value'),
- );
- },
- ),
- );
-
- const result = await bitbucketProcessor.readUrl(
- 'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
- );
- const buffer = await result.buffer();
- expect(buffer.toString()).toBe('foo');
- });
-
- it('should be able to readUrl using provided token', async () => {
- worker.use(
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
- (req, res, ctx) => {
- expect(req.headers.get('authorization')).toBe(
- 'Bearer manual-token',
- );
- return res(ctx.status(200), ctx.body('foo'));
- },
- ),
- );
-
- const result = await bitbucketProcessor.readUrl(
- 'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
- { token: 'manual-token' },
- );
- const buffer = await result.buffer();
- expect(buffer.toString()).toBe('foo');
- });
-
- it('should be able to readUrl via stream without ETag', async () => {
- worker.use(
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
- (req, res, ctx) => {
- expect(req.headers.get('If-None-Match')).toBeNull();
- return res(
- ctx.status(200),
- ctx.body('foo'),
- ctx.set('ETag', 'etag-value'),
- );
- },
- ),
- );
-
- const result = await bitbucketProcessor.readUrl(
- 'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
- );
- const fromStream = await getRawBody(result.stream!());
- expect(fromStream.toString()).toBe('foo');
- });
-
- it('should be able to readUrl with matching ETag', async () => {
- worker.use(
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
- (req, res, ctx) => {
- expect(req.headers.get('If-None-Match')).toBe(
- 'matching-etag-value',
- );
- return res(ctx.status(304));
- },
- ),
- );
-
- await expect(
- bitbucketProcessor.readUrl(
- 'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
- { etag: 'matching-etag-value' },
- ),
- ).rejects.toThrow(NotModifiedError);
- });
-
- it('should be able to readUrl without matching ETag', async () => {
- worker.use(
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
- (req, res, ctx) => {
- expect(req.headers.get('If-None-Match')).toBe(
- 'previous-etag-value',
- );
- return res(
- ctx.status(200),
- ctx.body('foo'),
- ctx.set('ETag', 'new-etag-value'),
- );
- },
- ),
- );
-
- const result = await bitbucketProcessor.readUrl(
- 'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
- { etag: 'previous-etag-value' },
- );
- const buffer = await result.buffer();
- expect(buffer.toString()).toBe('foo');
- expect(result.etag).toBe('new-etag-value');
- });
-
- it('should be able to readUrl via buffer without If-Modified-Since', async () => {
- worker.use(
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
- (req, res, ctx) => {
- expect(req.headers.get('If-None-Match')).toBeNull();
- return res(
- ctx.status(200),
- ctx.body('foo'),
- ctx.set('ETag', 'etag-value'),
- ctx.set(
- 'Last-Modified',
- new Date('2020-01-01T00:00:00Z').toUTCString(),
- ),
- );
- },
- ),
- );
-
- const result = await bitbucketProcessor.readUrl(
- 'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
- );
- const buffer = await result.buffer();
- expect(result.lastModifiedAt).toEqual(new Date('2020-01-01T00:00:00Z'));
- expect(buffer.toString()).toBe('foo');
- });
-
- it('should be throw not modified when If-Modified-Since returns a 304', async () => {
- worker.use(
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
- (req, res, ctx) => {
- expect(req.headers.get('If-Modified-Since')).toBe(
- new Date('1999 12 31 23:59:59 GMT').toUTCString(),
- );
- return res(ctx.status(304));
- },
- ),
- );
-
- await expect(
- bitbucketProcessor.readUrl(
- 'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
- { lastModifiedAfter: new Date('1999 12 31 23:59:59 GMT') },
- ),
- ).rejects.toThrow(NotModifiedError);
- });
-
- it('should be able to readUrl when If-Modified-Since is before Last-Modified', async () => {
- worker.use(
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
- (req, res, ctx) => {
- expect(req.headers.get('If-Modified-Since')).toBe(
- new Date('1999 12 31 23:59:59 GMT').toUTCString(),
- );
- return res(
- ctx.status(200),
- ctx.set(
- 'Last-Modified',
- new Date('2020-01-01T00:00:00Z').toUTCString(),
- ),
- ctx.body('foo'),
- );
- },
- ),
- );
-
- const result = await bitbucketProcessor.readUrl(
- 'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
- { lastModifiedAfter: new Date('1999 12 31 23:59:59 GMT') },
- );
- const buffer = await result.buffer();
- expect(buffer.toString()).toBe('foo');
- expect(result.lastModifiedAt).toEqual(new Date('2020-01-01T00:00:00Z'));
- });
- });
-
- describe('read', () => {
- it('rejects unknown targets', async () => {
- await expect(
- bitbucketProcessor.read('https://not.bitbucket.com/apa'),
- ).rejects.toThrow(
- 'Incorrect URL: https://not.bitbucket.com/apa, Error: Invalid Bitbucket URL or file path',
- );
- });
- });
-
- describe('readTree', () => {
- const repoBuffer = fs.readFileSync(
- path.resolve(
- __dirname,
- '__fixtures__/bitbucket-repo-with-commit-hash.tar.gz',
- ),
- );
-
- const privateBitbucketRepoBuffer = fs.readFileSync(
- path.resolve(__dirname, '__fixtures__/bitbucket-server-repo.tar.gz'),
- );
-
- beforeEach(() => {
- worker.use(
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage/mock',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.json({
- mainbranch: {
- type: 'branch',
- name: 'master',
- },
- }),
- ),
- ),
- rest.get(
- 'https://bitbucket.org/backstage/mock/get/master.tar.gz',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.set('Content-Type', 'application/zip'),
- ctx.set(
- 'content-disposition',
- 'attachment; filename=backstage-mock-12ab34cd56ef.tar.gz',
- ),
- ctx.body(new Uint8Array(repoBuffer)),
- ),
- ),
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage/mock/commits/master',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.json({
- values: [{ hash: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }],
- }),
- ),
- ),
- rest.get(
- 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.set('Content-Type', 'application/zip'),
- ctx.set(
- 'content-disposition',
- 'attachment; filename=backstage-mock.tgz',
- ),
- ctx.body(new Uint8Array(privateBitbucketRepoBuffer)),
- ),
- ),
- rest.get(
- 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.json({
- values: [{ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }],
- }),
- ),
- ),
- );
- });
-
- it('returns the wanted files from an archive', async () => {
- const response = await bitbucketProcessor.readTree(
- 'https://bitbucket.org/backstage/mock/src/master',
- );
-
- expect(response.etag).toBe('12ab34cd56ef');
-
- const files = await response.files();
-
- expect(files.length).toBe(2);
- const mkDocsFile = await files[0].content();
- const indexMarkdownFile = await files[1].content();
-
- expect(indexMarkdownFile.toString()).toBe('# Test\n');
- expect(mkDocsFile.toString()).toBe('site_name: Test\n');
- });
-
- it('creates a directory with the wanted files', async () => {
- const response = await bitbucketProcessor.readTree(
- 'https://bitbucket.org/backstage/mock',
- );
-
- const dir = await response.dir({ targetDir: mockDir.path });
-
- await expect(
- fs.readFile(path.join(dir, 'mkdocs.yml'), 'utf8'),
- ).resolves.toBe('site_name: Test\n');
- await expect(
- fs.readFile(path.join(dir, 'docs', 'index.md'), 'utf8'),
- ).resolves.toBe('# Test\n');
- });
-
- it('uses private bitbucket host', async () => {
- const response = await hostedBitbucketProcessor.readTree(
- 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs?at=some-branch',
- );
-
- expect(response.etag).toBe('12ab34cd56ef');
-
- const files = await response.files();
-
- expect(files.length).toBe(1);
- const indexMarkdownFile = await files[0].content();
-
- expect(indexMarkdownFile.toString()).toBe('# Test\n');
- });
-
- it('returns the wanted files from an archive with a subpath', async () => {
- const response = await bitbucketProcessor.readTree(
- 'https://bitbucket.org/backstage/mock/src/master/docs',
- );
-
- expect(response.etag).toBe('12ab34cd56ef');
-
- const files = await response.files();
-
- expect(files.length).toBe(1);
- const indexMarkdownFile = await files[0].content();
-
- expect(indexMarkdownFile.toString()).toBe('# Test\n');
- });
-
- it('creates a directory with the wanted files with a subpath', async () => {
- const response = await bitbucketProcessor.readTree(
- 'https://bitbucket.org/backstage/mock/src/master/docs',
- );
-
- const dir = await response.dir({ targetDir: mockDir.path });
-
- await expect(
- fs.readFile(path.join(dir, 'index.md'), 'utf8'),
- ).resolves.toBe('# Test\n');
- });
-
- it('throws a NotModifiedError when given a etag in options', async () => {
- const fnBitbucket = async () => {
- await bitbucketProcessor.readTree(
- 'https://bitbucket.org/backstage/mock',
- { etag: '12ab34cd56ef' },
- );
- };
-
- await expect(fnBitbucket).rejects.toThrow(NotModifiedError);
- });
-
- it('should not throw a NotModifiedError when given an outdated etag in options', async () => {
- const response = await bitbucketProcessor.readTree(
- 'https://bitbucket.org/backstage/mock',
- { etag: 'outdatedetag123abc' },
- );
-
- expect(response.etag).toBe('12ab34cd56ef');
- });
- });
-
- describe('search hosted', () => {
- const repoBuffer = fs.readFileSync(
- path.resolve(
- __dirname,
- '__fixtures__/bitbucket-repo-with-commit-hash.tar.gz',
- ),
- );
-
- beforeEach(() => {
- worker.use(
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage/mock',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.json({
- mainbranch: {
- type: 'branch',
- name: 'master',
- },
- }),
- ),
- ),
- rest.get(
- 'https://bitbucket.org/backstage/mock/get/master.tar.gz',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.set('Content-Type', 'application/zip'),
- ctx.set(
- 'content-disposition',
- 'attachment; filename=backstage-mock-12ab34cd56ef.tar.gz',
- ),
- ctx.body(new Uint8Array(repoBuffer)),
- ),
- ),
- rest.get(
- 'https://api.bitbucket.org/2.0/repositories/backstage/mock/commits/master',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.json({
- values: [{ hash: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }],
- }),
- ),
- ),
- );
- });
-
- it('works for the naive case', async () => {
- const result = await bitbucketProcessor.search(
- 'https://bitbucket.org/backstage/mock/src/master/**/index.*',
- );
- expect(result.etag).toBe('12ab34cd56ef');
- expect(result.files.length).toBe(1);
- expect(result.files[0].url).toBe(
- 'https://bitbucket.org/backstage/mock/src/master/docs/index.md',
- );
- await expect(result.files[0].content()).resolves.toEqual(
- Buffer.from('# Test\n'),
- );
- });
-
- it('works in nested folders', async () => {
- const result = await bitbucketProcessor.search(
- 'https://bitbucket.org/backstage/mock/src/master/docs/index.*',
- );
- expect(result.etag).toBe('12ab34cd56ef');
- expect(result.files.length).toBe(1);
- expect(result.files[0].url).toBe(
- 'https://bitbucket.org/backstage/mock/src/master/docs/index.md',
- );
- await expect(result.files[0].content()).resolves.toEqual(
- Buffer.from('# Test\n'),
- );
- });
-
- it('throws NotModifiedError when same etag', async () => {
- await expect(
- bitbucketProcessor.search(
- 'https://bitbucket.org/backstage/mock/src/master/**/index.*',
- { etag: '12ab34cd56ef' },
- ),
- ).rejects.toThrow(NotModifiedError);
- });
- });
-
- describe('search private', () => {
- const privateBitbucketRepoBuffer = fs.readFileSync(
- path.resolve(__dirname, '__fixtures__/bitbucket-server-repo.tar.gz'),
- );
-
- beforeEach(() => {
- worker.use(
- rest.get(
- 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.set('Content-Type', 'application/zip'),
- ctx.set(
- 'content-disposition',
- 'attachment; filename=backstage-mock.tgz',
- ),
- ctx.body(new Uint8Array(privateBitbucketRepoBuffer)),
- ),
- ),
- rest.get(
- 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.json({
- values: [{ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }],
- }),
- ),
- ),
- );
- });
-
- it('works for the naive case', async () => {
- const result = await hostedBitbucketProcessor.search(
- 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/**/index.*?at=master',
- );
- expect(result.etag).toBe('12ab34cd56ef');
- expect(result.files.length).toBe(1);
- expect(result.files[0].url).toBe(
- 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs/index.md?at=master',
- );
- await expect(result.files[0].content()).resolves.toEqual(
- Buffer.from('# Test\n'),
- );
- });
-
- it('works in nested folders', async () => {
- const result = await hostedBitbucketProcessor.search(
- 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs/index.*?at=master',
- );
- expect(result.etag).toBe('12ab34cd56ef');
- expect(result.files.length).toBe(1);
- expect(result.files[0].url).toBe(
- 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs/index.md?at=master',
- );
- await expect(result.files[0].content()).resolves.toEqual(
- Buffer.from('# Test\n'),
- );
- });
-
- it('throws NotModifiedError when same etag', async () => {
- await expect(
- hostedBitbucketProcessor.search(
- 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/**/index.*?at=master',
- { etag: '12ab34cd56ef' },
- ),
- ).rejects.toThrow(NotModifiedError);
- });
-
- it('should work for exact URLs', async () => {
- hostedBitbucketProcessor.readUrl = jest.fn().mockResolvedValue({
- buffer: async () => Buffer.from('content'),
- etag: 'etag',
- } as UrlReaderServiceReadUrlResponse);
-
- const result = await hostedBitbucketProcessor.search(
- 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs/index.md?at=master',
- );
- expect(result.etag).toBe('etag');
- expect(result.files.length).toBe(1);
- expect(result.files[0].url).toBe(
- 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs/index.md?at=master',
- );
- expect((await result.files[0].content()).toString()).toEqual('content');
- });
- });
-});
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader.ts
deleted file mode 100644
index 8c8843d7e9..0000000000
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader.ts
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {
- UrlReaderService,
- UrlReaderServiceReadTreeOptions,
- UrlReaderServiceReadTreeResponse,
- UrlReaderServiceReadUrlOptions,
- UrlReaderServiceReadUrlResponse,
- UrlReaderServiceSearchOptions,
- UrlReaderServiceSearchResponse,
-} from '@backstage/backend-plugin-api';
-import {
- assertError,
- NotFoundError,
- NotModifiedError,
-} from '@backstage/errors';
-import {
- BitbucketIntegration,
- getBitbucketDefaultBranch,
- getBitbucketDownloadUrl,
- getBitbucketFileFetchUrl,
- getBitbucketRequestOptions,
- ScmIntegrations,
-} from '@backstage/integration';
-import parseGitUrl from 'git-url-parse';
-import { trimEnd } from 'lodash';
-import { Minimatch } from 'minimatch';
-import { LoggerService } from '@backstage/backend-plugin-api';
-import { ReaderFactory, ReadTreeResponseFactory } from './types';
-import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
-
-/**
- * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket v1 and v2 APIs, such
- * as the one exposed by Bitbucket Cloud itself.
- *
- * @public
- * @deprecated in favor of BitbucketCloudUrlReader and BitbucketServerUrlReader
- */
-export class BitbucketUrlReader implements UrlReaderService {
- static factory: ReaderFactory = ({ config, logger, treeResponseFactory }) => {
- const integrations = ScmIntegrations.fromConfig(config);
- return integrations.bitbucket
- .list()
- .filter(
- item =>
- !integrations.bitbucketCloud.byHost(item.config.host) &&
- !integrations.bitbucketServer.byHost(item.config.host),
- )
- .map(integration => {
- const reader = new BitbucketUrlReader(integration, logger, {
- treeResponseFactory,
- });
- const predicate = (url: URL) => url.host === integration.config.host;
- return { reader, predicate };
- });
- };
-
- private readonly integration: BitbucketIntegration;
- private readonly deps: { treeResponseFactory: ReadTreeResponseFactory };
-
- constructor(
- integration: BitbucketIntegration,
- logger: LoggerService,
- deps: { treeResponseFactory: ReadTreeResponseFactory },
- ) {
- this.integration = integration;
- this.deps = deps;
- const { host, token, username, appPassword } = integration.config;
- const replacement =
- host === 'bitbucket.org' ? 'bitbucketCloud' : 'bitbucketServer';
- logger.warn(
- `[Deprecated] Please migrate from "integrations.bitbucket" to "integrations.${replacement}".`,
- );
-
- if (!token && username && !appPassword) {
- throw new Error(
- `Bitbucket integration for '${host}' has configured a username but is missing a required appPassword.`,
- );
- }
- }
-
- async read(url: string): Promise {
- const response = await this.readUrl(url);
- return response.buffer();
- }
-
- private getCredentials = async (options?: {
- token?: string;
- }): Promise<{ headers: Record }> => {
- if (options?.token) {
- return {
- headers: {
- Authorization: `Bearer ${options.token}`,
- },
- };
- }
-
- return await getBitbucketRequestOptions(this.integration.config);
- };
-
- async readUrl(
- url: string,
- options?: UrlReaderServiceReadUrlOptions,
- ): Promise {
- const { etag, lastModifiedAfter, signal } = options ?? {};
- const bitbucketUrl = getBitbucketFileFetchUrl(url, this.integration.config);
- const requestOptions = await this.getCredentials(options);
-
- let response: Response;
- try {
- response = await fetch(bitbucketUrl.toString(), {
- headers: {
- ...requestOptions.headers,
- ...(etag && { 'If-None-Match': etag }),
- ...(lastModifiedAfter && {
- 'If-Modified-Since': lastModifiedAfter.toUTCString(),
- }),
- },
- // TODO(freben): The signal cast is there because pre-3.x versions of
- // node-fetch have a very slightly deviating AbortSignal type signature.
- // The difference does not affect us in practice however. The cast can be
- // removed after we support ESM for CLI dependencies and migrate to
- // version 3 of node-fetch.
- // https://github.com/backstage/backstage/issues/8242
- ...(signal && { signal: signal as any }),
- });
- } catch (e) {
- throw new Error(`Unable to read ${url}, ${e}`);
- }
-
- if (response.status === 304) {
- throw new NotModifiedError();
- }
-
- if (response.ok) {
- return ReadUrlResponseFactory.fromResponse(response);
- }
-
- const message = `${url} could not be read as ${bitbucketUrl}, ${response.status} ${response.statusText}`;
- if (response.status === 404) {
- throw new NotFoundError(message);
- }
- throw new Error(message);
- }
-
- async readTree(
- url: string,
- options?: UrlReaderServiceReadTreeOptions,
- ): Promise {
- const { filepath } = parseGitUrl(url);
-
- const lastCommitShortHash = await this.getLastCommitShortHash(url);
- if (options?.etag && options.etag === lastCommitShortHash) {
- throw new NotModifiedError();
- }
-
- const downloadUrl = await getBitbucketDownloadUrl(
- url,
- this.integration.config,
- );
- const archiveBitbucketResponse = await fetch(
- downloadUrl,
- getBitbucketRequestOptions(this.integration.config),
- );
- if (!archiveBitbucketResponse.ok) {
- const message = `Failed to read tree from ${url}, ${archiveBitbucketResponse.status} ${archiveBitbucketResponse.statusText}`;
- if (archiveBitbucketResponse.status === 404) {
- throw new NotFoundError(message);
- }
- throw new Error(message);
- }
-
- return await this.deps.treeResponseFactory.fromTarArchive({
- response: archiveBitbucketResponse,
- subpath: filepath,
- etag: lastCommitShortHash,
- filter: options?.filter,
- });
- }
-
- async search(
- url: string,
- options?: UrlReaderServiceSearchOptions,
- ): Promise {
- const { filepath } = parseGitUrl(url);
-
- // If it's a direct URL we use readUrl instead
- if (!filepath?.match(/[*?]/)) {
- try {
- const data = await this.readUrl(url, options);
-
- return {
- files: [
- {
- url: url,
- content: data.buffer,
- lastModifiedAt: data.lastModifiedAt,
- },
- ],
- etag: data.etag ?? '',
- };
- } catch (error) {
- assertError(error);
- if (error.name === 'NotFoundError') {
- return {
- files: [],
- etag: '',
- };
- }
- throw error;
- }
- }
-
- const matcher = new Minimatch(filepath);
-
- // TODO(freben): For now, read the entire repo and filter through that. In
- // a future improvement, we could be smart and try to deduce that non-glob
- // prefixes (like for filepaths such as some-prefix/**/a.yaml) can be used
- // to get just that part of the repo.
- const treeUrl = trimEnd(url.replace(filepath, ''), '/');
-
- const tree = await this.readTree(treeUrl, {
- etag: options?.etag,
- filter: path => matcher.match(path),
- });
- const files = await tree.files();
-
- return {
- etag: tree.etag,
- files: files.map(file => ({
- url: this.integration.resolveUrl({
- url: `/${file.path}`,
- base: url,
- }),
- content: file.content,
- lastModifiedAt: file.lastModifiedAt,
- })),
- };
- }
-
- toString() {
- const { host, token, username, appPassword } = this.integration.config;
- let authed = Boolean(token);
- if (!authed) {
- authed = Boolean(username && appPassword);
- }
- return `bitbucket{host=${host},authed=${authed}}`;
- }
-
- private async getLastCommitShortHash(url: string): Promise {
- const { resource, name: repoName, owner: project, ref } = parseGitUrl(url);
-
- let branch = ref;
- if (!branch) {
- branch = await getBitbucketDefaultBranch(url, this.integration.config);
- }
-
- const isHosted = resource === 'bitbucket.org';
- // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp222
- const commitsApiUrl = isHosted
- ? `${this.integration.config.apiBaseUrl}/repositories/${project}/${repoName}/commits/${branch}`
- : `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/commits`;
-
- const commitsResponse = await fetch(
- commitsApiUrl,
- getBitbucketRequestOptions(this.integration.config),
- );
- if (!commitsResponse.ok) {
- const message = `Failed to retrieve commits from ${commitsApiUrl}, ${commitsResponse.status} ${commitsResponse.statusText}`;
- if (commitsResponse.status === 404) {
- throw new NotFoundError(message);
- }
- throw new Error(message);
- }
-
- const commits = await commitsResponse.json();
- if (isHosted) {
- if (
- commits &&
- commits.values &&
- commits.values.length > 0 &&
- commits.values[0].hash
- ) {
- return commits.values[0].hash.substring(0, 12);
- }
- } else {
- if (
- commits &&
- commits.values &&
- commits.values.length > 0 &&
- commits.values[0].id
- ) {
- return commits.values[0].id.substring(0, 12);
- }
- }
-
- throw new Error(`Failed to read response from ${commitsApiUrl}`);
- }
-}
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts
index 229bdd4903..f04a668b0a 100644
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts
+++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts
@@ -24,7 +24,6 @@ import { UrlReaderPredicateMux } from './UrlReaderPredicateMux';
import { AzureUrlReader } from './AzureUrlReader';
import { BitbucketCloudUrlReader } from './BitbucketCloudUrlReader';
import { BitbucketServerUrlReader } from './BitbucketServerUrlReader';
-import { BitbucketUrlReader } from './BitbucketUrlReader';
import { GerritUrlReader } from './GerritUrlReader';
import { GithubUrlReader } from './GithubUrlReader';
import { GitlabUrlReader } from './GitlabUrlReader';
@@ -92,7 +91,6 @@ export class UrlReaders {
AzureUrlReader.factory,
BitbucketCloudUrlReader.factory,
BitbucketServerUrlReader.factory,
- BitbucketUrlReader.factory,
GerritUrlReader.factory,
GithubUrlReader.factory,
GiteaUrlReader.factory,
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/index.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/index.ts
index fc33efbc3a..3f5d7a7ea5 100644
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/index.ts
+++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/index.ts
@@ -16,7 +16,6 @@
export { AzureUrlReader } from './AzureUrlReader';
export { BitbucketCloudUrlReader } from './BitbucketCloudUrlReader';
-export { BitbucketUrlReader } from './BitbucketUrlReader';
export { BitbucketServerUrlReader } from './BitbucketServerUrlReader';
export { GerritUrlReader } from './GerritUrlReader';
export { GithubUrlReader } from './GithubUrlReader';
diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md
index a9be5cd0ba..3ea462e952 100644
--- a/packages/backend-dynamic-feature-service/CHANGELOG.md
+++ b/packages/backend-dynamic-feature-service/CHANGELOG.md
@@ -1,5 +1,148 @@
# @backstage/backend-dynamic-feature-service
+## 0.8.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-defaults@0.16.1-next.1
+ - @backstage/plugin-catalog-backend@3.5.1-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/backend-openapi-utils@0.6.8-next.1
+ - @backstage/plugin-app-node@0.1.44-next.1
+ - @backstage/plugin-events-backend@0.6.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+ - @backstage/plugin-scaffolder-node@0.13.1-next.1
+ - @backstage/plugin-search-backend-node@1.4.3-next.1
+
+## 0.8.1-next.0
+
+### Patch Changes
+
+- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`.
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/plugin-catalog-backend@3.5.1-next.0
+ - @backstage/backend-defaults@0.16.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/config-loader@1.10.10-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-events-backend@0.6.1-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/plugin-search-backend-node@1.4.3-next.0
+ - @backstage/backend-openapi-utils@0.6.8-next.0
+ - @backstage/plugin-app-node@0.1.44-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/plugin-scaffolder-node@0.13.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-search-common@1.2.22
+
+## 0.8.0
+
+### Minor Changes
+
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+
+### Patch Changes
+
+- 4074a22: Fixed `resolvePackagePath` resolution for bundled dynamic plugins. When a plugin bundles its own copy of `@backstage/backend-plugin-api` inside `node_modules`, the `CommonJSModuleLoader` fallback now correctly resolves the plugin's `package.json` by name. Previously the fallback only applied when the resolution originated from the host application; it now also applies when originating from a bundled dependency, which is the case for plugins produced by the `backstage-cli package bundle` command.
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-defaults@0.16.0
+ - @backstage/cli-node@0.3.0
+ - @backstage/plugin-catalog-backend@3.5.0
+ - @backstage/cli-common@0.2.0
+ - @backstage/plugin-scaffolder-node@0.13.0
+ - @backstage/plugin-events-backend@0.6.0
+ - @backstage/config-loader@1.10.9
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/plugin-auth-node@0.6.14
+ - @backstage/backend-openapi-utils@0.6.7
+ - @backstage/plugin-app-node@0.1.43
+ - @backstage/plugin-events-node@0.4.20
+ - @backstage/plugin-search-backend-node@1.4.2
+
+## 0.8.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-defaults@0.16.0-next.2
+ - @backstage/cli-common@0.2.0-next.2
+ - @backstage/plugin-catalog-backend@3.5.0-next.2
+ - @backstage/backend-openapi-utils@0.6.7-next.1
+ - @backstage/plugin-app-node@0.1.43-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-events-backend@0.6.0-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+ - @backstage/plugin-permission-node@0.10.11-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.2
+ - @backstage/plugin-search-backend-node@1.4.2-next.1
+
+## 0.8.0-next.1
+
+### Minor Changes
+
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.5.0-next.1
+ - @backstage/cli-common@0.2.0-next.1
+ - @backstage/cli-node@0.2.19-next.1
+ - @backstage/backend-defaults@0.16.0-next.1
+ - @backstage/plugin-events-backend@0.6.0-next.1
+ - @backstage/plugin-scaffolder-node@0.13.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.1
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-app-node@0.1.43-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
+## 0.7.10-next.0
+
+### Patch Changes
+
+- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/cli-node@0.2.19-next.0
+ - @backstage/backend-defaults@0.15.3-next.0
+ - @backstage/plugin-catalog-backend@3.5.0-next.0
+ - @backstage/config-loader@1.10.9-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/backend-openapi-utils@0.6.7-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-app-node@0.1.43-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-events-backend@0.5.12-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+ - @backstage/plugin-scaffolder-node@0.12.6-next.0
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-search-common@1.2.22
+
## 0.7.9
### Patch Changes
diff --git a/packages/backend-dynamic-feature-service/README.md b/packages/backend-dynamic-feature-service/README.md
index 8122810f96..914df15f65 100644
--- a/packages/backend-dynamic-feature-service/README.md
+++ b/packages/backend-dynamic-feature-service/README.md
@@ -97,7 +97,7 @@ Since this service only handles loading, you would choose a packaging approach b
**When to use:** Plugin only uses dependencies that are already provided by the main Backstage application.
-**How to apply:**
+**How to use:**
```bash
cd my-backstage-plugin
@@ -117,7 +117,7 @@ tar -xzf package.tgz -C /path/to/dynamic-plugins-root/my-backstage-plugin --stri
**When to use:** Plugin has private dependencies not available in the main Backstage application.
-**How to apply:**
+**How to use:**
```bash
# Package the plugin
@@ -136,35 +136,47 @@ yarn install # Installs all the plugin's dependencies
**Example scenario:** Plugin needs `axios@1.4.0` which isn't available in the main application.
-### 3. Custom packaging CLI tool
+### 3. Dedicated bundling CLI command
-**When to use:** When you want to produce self-contained dynamic plugin packages that can be directly extracted without any post-action, and systematically use the core `@backstage` dependencies provided by the Backstage application.
+**When to use:**
-**What a packaging CLI needs to do:**
+- When you want to produce self-contained dynamic plugin packages that can be directly extracted and loaded without any post-action,
+- especially when your plugin depends on other packages in the same monorepo.
-1. **Analyze plugin dependencies** - Identify which are Backstage core vs private dependencies
-2. **Create distribution package** - Generate a new directory with modified structure:
- - Move `@backstage/*` packages from `dependencies` to `peerDependencies` in package.json
- - Keep only private dependencies in the `dependencies` section
- - Keep the built JavaScript code unchanged
- - Include only the filtered private dependencies in `node_modules`
-3. **Result** - A self-contained package that uses the main app's `@backstage/*` packages but includes its own private dependencies
+**How to use:**
-**Benefits:**
-
-- Systematic use of main application's `@backstage/*` packages (no version conflicts), enabling the future implementation of `@backstage` dependency version checking at start time
-- Self-contained packages with only necessary private dependencies
-- No post-installation steps required (extract and run)
-- Consistent dependency structure across all dynamic plugins
-- Production-ready distribution format
-
-**Example implementation:** The [`@red-hat-developer-hub/cli`](https://github.com/redhat-developer/rhdh-cli) tool implements this approach:
+The [`backstage-cli package bundle`](../cli/cli-report.md) command automates the required steps. Run it from within a plugin directory:
```bash
cd my-backstage-plugin
-npx @red-hat-developer-hub/cli@latest plugin export
-# Creates a self-contained package with embedded dependencies in the `/dist-dynamic` sub-folder
-
-# Deploy the generated package
-cp -r dist-dynamic /path/to/dynamic-plugins-root/my-backstage-plugin
+yarn backstage-cli package bundle --output-destination /path/to/dynamic-plugins-root
+# Creates a self-contained bundle in the /path/to/dynamic-plugins-root/my-backstage-plugin/ sub-folder
```
+
+**Batch bundling:** When bundling many plugins from the same monorepo, use `--pre-packed-dir` to avoid redundant work:
+
+```bash
+# First, build a shared dist workspace
+backstage-cli build-workspace dist-workspace --alwaysPack ...plugin-packages
+
+# Then bundle each plugin using the pre-packed output
+cd plugins/my-backstage-plugin
+backstage-cli package bundle --pre-packed-dir ../../dist-workspace
+```
+
+See the full list of options in the [CLI reference](../cli/cli-report.md).
+
+**What the command does:**
+
+1. **Builds the plugin** — Produces CJS output for the backend plugin and its transitively-required monorepo packages (`*-node` or `*-common`)
+2. **Packs local packages** — Resolves `workspace:^` and `backstage:^` dependencies on both the main plugin package and its transitively-required monorepo packages (`*-node` or `*-common`)
+3. **Installs private dependencies** — Seeds a lockfile from the plugin source monorepo and prunes it, then installs a private `node_modules` containing all required dependencies
+4. **Collects configuration schemas** — Gathers plugin config schemas and writes them to `dist/.config-schema.json` so they are available for validation at load time
+
+**Benefits:**
+
+- Self-contained packages with all necessary dependencies
+- No post-installation steps required (extract and run)
+- Consistent dependency structure across all dynamic plugins
+- Automatic configuration schema collection
+- Production-ready distribution format
diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json
index c197ad041c..fb1e5dd803 100644
--- a/packages/backend-dynamic-feature-service/package.json
+++ b/packages/backend-dynamic-feature-service/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-dynamic-feature-service",
- "version": "0.7.9",
+ "version": "0.8.1-next.1",
"description": "Backstage dynamic feature service",
"backstage": {
"role": "node-library"
@@ -84,7 +84,7 @@
"@backstage/repo-tools": "workspace:^",
"@types/express": "^4.17.6",
"triple-beam": "^1.4.1",
- "wait-for-expect": "^3.0.2"
+ "wait-for-expect": "^4.0.0"
},
"configSchema": "config.d.ts"
}
diff --git a/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/dist/index.cjs.js b/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/dist/index.cjs.js
new file mode 100644
index 0000000000..f3671bc0a7
--- /dev/null
+++ b/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/dist/index.cjs.js
@@ -0,0 +1,28 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+// This require resolves to the bundled proxy copy inside this plugin's
+// own node_modules/@backstage/backend-plugin-api, NOT the host's copy.
+var backendPluginApi = require('@backstage/backend-plugin-api');
+
+// Triggers the _resolveFilename fallback: require.resolve runs from
+// the bundled @backstage/backend-plugin-api whose mod.path is inside
+// this plugin's node_modules.
+var pkgDir = backendPluginApi.resolvePackagePath('plugin-test-backend-bundled');
+
+const testBundledPlugin = backendPluginApi.createBackendPlugin({
+ pluginId: "test-bundled",
+ register(env) {
+ env.registerInit({
+ deps: {
+ logger: backendPluginApi.coreServices.rootLogger,
+ },
+ async init({ logger }) {
+ logger.info("Bundled backend plugin loaded successfully");
+ }
+ });
+ }
+});
+
+exports.default = testBundledPlugin;
diff --git a/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/node_modules/@backstage/backend-plugin-api/index.js b/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/node_modules/@backstage/backend-plugin-api/index.js
new file mode 100644
index 0000000000..70e57950be
--- /dev/null
+++ b/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/node_modules/@backstage/backend-plugin-api/index.js
@@ -0,0 +1,22 @@
+'use strict';
+
+// Proxy that simulates a bundled copy of @backstage/backend-plugin-api.
+// Re-exports everything from the real workspace package, but provides a
+// local resolvePackagePath so that require.resolve() runs from THIS
+// file's context (mod.path inside node_modules/@backstage/backend-plugin-api).
+
+const nodePath = require('node:path');
+const real = require('../../../../../../../../../backend-plugin-api/src');
+
+function resolvePackagePath(name) {
+ const args = Array.prototype.slice.call(arguments, 1);
+ const pkgJson = require.resolve(name + '/package.json');
+ return nodePath.resolve.apply(
+ nodePath,
+ [nodePath.dirname(pkgJson)].concat(args),
+ );
+}
+
+module.exports = Object.assign({}, real, {
+ resolvePackagePath: resolvePackagePath,
+});
diff --git a/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/node_modules/@backstage/backend-plugin-api/package.json b/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/node_modules/@backstage/backend-plugin-api/package.json
new file mode 100644
index 0000000000..f487169afe
--- /dev/null
+++ b/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/node_modules/@backstage/backend-plugin-api/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "@backstage/backend-plugin-api",
+ "version": "0.0.0",
+ "description": "Proxy that re-exports the real @backstage/backend-plugin-api with a local resolvePackagePath, simulating a bundled copy.",
+ "main": "index.js"
+}
diff --git a/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/package.json b/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/package.json
new file mode 100644
index 0000000000..bb2dbf15a6
--- /dev/null
+++ b/packages/backend-dynamic-feature-service/src/features/__fixtures__/dynamic-plugins-root-for-bundled/test-backend-bundled-dynamic/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "plugin-test-backend-bundled-dynamic",
+ "version": "0.0.0",
+ "description": "A test dynamic backend plugin that bundles its own @backstage/backend-plugin-api.",
+ "backstage": {
+ "role": "backend-plugin",
+ "pluginId": "test-bundled",
+ "pluginPackages": [
+ "plugin-test-backend-bundled"
+ ]
+ },
+ "keywords": [
+ "backstage",
+ "dynamic"
+ ],
+ "exports": {
+ ".": {
+ "require": "./dist/index.cjs.js",
+ "default": "./dist/index.cjs.js"
+ },
+ "./package.json": "./package.json"
+ },
+ "main": "./dist/index.cjs.js",
+ "files": [
+ "dist"
+ ],
+ "dependencies": {
+ "@backstage/backend-plugin-api": "0.0.0"
+ },
+ "bundleDependencies": true
+}
diff --git a/packages/backend-dynamic-feature-service/src/features/features.test.ts b/packages/backend-dynamic-feature-service/src/features/features.test.ts
index ce15964c77..ac6c6ea510 100644
--- a/packages/backend-dynamic-feature-service/src/features/features.test.ts
+++ b/packages/backend-dynamic-feature-service/src/features/features.test.ts
@@ -465,6 +465,45 @@ Require stack:
});
});
+ it('should load a backend plugin that bundles its own @backstage/backend-plugin-api', async () => {
+ const dynamicPluginsLister = new DynamicPluginLister();
+ const dynamicPluginsRootForBundled = resolvePath(
+ __dirname,
+ '__fixtures__/dynamic-plugins-root-for-bundled',
+ );
+ await startTestBackend({
+ features: [
+ mockServices.rootConfig.factory({
+ data: {
+ dynamicPlugins: {
+ rootDirectory: dynamicPluginsRootForBundled,
+ },
+ backend: {
+ baseUrl: `http://localhost:0`,
+ },
+ },
+ }),
+ dynamicPluginsFeatureLoader({
+ moduleLoader: logger =>
+ jestFreeTypescriptAwareModuleLoader({ logger }),
+ }),
+ dynamicPluginsLister.feature(),
+ ],
+ });
+
+ expect(dynamicPluginsLister.loadedPlugins).toMatchObject([
+ {
+ installer: {
+ kind: 'new',
+ },
+ name: 'plugin-test-backend-bundled-dynamic',
+ platform: 'node',
+ role: 'backend-plugin',
+ version: '0.0.0',
+ },
+ ]);
+ });
+
describe('module federation support', () => {
const createRemoteProviderPlugin = (
provider: FrontendRemoteResolverProvider,
diff --git a/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts b/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts
index 3cc640086f..8902decdfa 100644
--- a/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts
+++ b/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts
@@ -99,15 +99,21 @@ export class CommonJSModuleLoader implements ModuleLoader {
);
}
- // Are we trying to resolve a `package.json` from an originating module of the core backstage application
- // (this is mostly done by calling `@backstage/backend-plugin-api/resolvePackagePath`).
- const resolvingPackageJsonFromBackstageApplication =
+ // Is this a `resolvePackagePath` call from `@backstage/backend-plugin-api`?
+ // This covers both the host application's copy and a bundled copy living
+ // inside a dynamic plugin's own node_modules.
+ // The regex matches mod.path against the various ways the package can be resolved on disk
+ // (with optional subdirectory such as /src or /dist after the package name):
+ // - .../node_modules/@backstage/backend-plugin-api[/...] (npm-installed)
+ // - ...//node_modules/@backstage/backend-plugin-api[/...] (bundled)
+ // - .../packages/backend-plugin-api[/...] (symlinked workspace in monorepo)
+ const resolvingPackageJsonViaResolvePackagePath =
request?.endsWith('/package.json') &&
- mod?.path &&
- !dynamicPluginsPaths.some(p => mod.path.startsWith(p));
+ /[/\\](?:@backstage|packages)[/\\]backend-plugin-api(?:[/\\]|$)/.test(
+ mod?.path ?? '',
+ );
- // If not, we don't need the dedicated specific case below.
- if (!resolvingPackageJsonFromBackstageApplication) {
+ if (!resolvingPackageJsonViaResolvePackagePath) {
throw errorToThrow;
}
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi.yaml b/packages/backend-dynamic-feature-service/src/schema/openapi.yaml
index 9b1871c366..987b59f4b5 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi.yaml
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi.yaml
@@ -1,4 +1,4 @@
-openapi: 3.0.3
+openapi: 3.1.0
info:
title: .backstage/dynamic-features
version: '1'
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/apis/Api.server.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/apis/Api.server.ts
index 285345d44c..7d241760dc 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/apis/Api.server.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/apis/Api.server.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/apis/index.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/apis/index.ts
index 8d81cbaf39..9a0ffed740 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/apis/index.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/apis/index.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/index.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/index.ts
index dec4b8804e..58fc52487a 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/index.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/index.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorError.model.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorError.model.ts
index e0265e95d7..853f88cc14 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorError.model.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorError.model.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorRequest.model.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorRequest.model.ts
index 3eb5e15740..1591911bc9 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorRequest.model.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorRequest.model.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorResponse.model.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorResponse.model.ts
index edbcc32df7..1eff0557ed 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorResponse.model.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ErrorResponse.model.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ModelError.model.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ModelError.model.ts
index 958fde7d0b..9d5c36325e 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ModelError.model.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/ModelError.model.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@ import { ErrorResponse } from '../models/ErrorResponse.model';
*/
export interface ModelError {
[key: string]: any;
-
error: ErrorError;
request?: ErrorRequest;
response: ErrorResponse;
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/Remote.model.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/Remote.model.ts
index 7440110ad8..ff5cae705f 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/Remote.model.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/Remote.model.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/RemoteInfo.model.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/RemoteInfo.model.ts
index 72edba90d4..6e8bdeff0a 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/RemoteInfo.model.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/RemoteInfo.model.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/index.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/index.ts
index 0c54a6585b..4cf296a0b9 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/index.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/models/index.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts
index cf48bcfc95..b228198c3c 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage
import { EndpointMap } from './apis';
export const spec = {
- openapi: '3.0.3',
+ openapi: '3.1.0',
info: {
title: '.backstage/dynamic-features',
version: '1',
diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/index.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/index.ts
index 196aad553a..fb49a2b9c0 100644
--- a/packages/backend-dynamic-feature-service/src/schema/openapi/index.ts
+++ b/packages/backend-dynamic-feature-service/src/schema/openapi/index.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md
index 58436b3235..8b382a3f84 100644
--- a/packages/backend-openapi-utils/CHANGELOG.md
+++ b/packages/backend-openapi-utils/CHANGELOG.md
@@ -1,5 +1,44 @@
# @backstage/backend-openapi-utils
+## 0.6.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+
+## 0.6.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## 0.6.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+
+## 0.6.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+
+## 0.6.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
## 0.6.6
### Patch Changes
diff --git a/packages/backend-openapi-utils/README.md b/packages/backend-openapi-utils/README.md
index 33f2200f81..bb420f49fa 100644
--- a/packages/backend-openapi-utils/README.md
+++ b/packages/backend-openapi-utils/README.md
@@ -4,6 +4,8 @@
This package is meant to provide a typed Express router for an OpenAPI spec. Based on the [`oatx`](https://github.com/varanauskas/oatx) library and adapted to override Express values.
+Only supports OpenAPI 3.1 specifications.
+
## Getting Started
### Configuration
diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json
index 5c6a9c5773..135af02922 100644
--- a/packages/backend-openapi-utils/package.json
+++ b/packages/backend-openapi-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-openapi-utils",
- "version": "0.6.6",
+ "version": "0.6.8-next.1",
"description": "OpenAPI typescript support.",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-openapi-utils/src/stub.ts b/packages/backend-openapi-utils/src/stub.ts
index 717e2ae19b..b595f357bc 100644
--- a/packages/backend-openapi-utils/src/stub.ts
+++ b/packages/backend-openapi-utils/src/stub.ts
@@ -54,6 +54,7 @@ export function getOpenApiSpecRoute(baseUrl: string) {
/**
* Create a router with validation middleware. This is used by typing methods to create an
* "OpenAPI router" with all of the expected validation + metadata.
+ * Only supports OpenAPI 3.1 specifications.
* @param spec - Your OpenAPI spec imported as a JSON object.
* @param validatorOptions - `openapi-express-validator` options to override the defaults.
* @returns A new express router with validation middleware.
@@ -115,6 +116,7 @@ function createRouterWithValidation(
/**
* Create a new OpenAPI router with some default middleware.
+ * Only supports OpenAPI 3.1 specifications.
* @param spec - Your OpenAPI spec imported as a JSON object.
* @param validatorOptions - `openapi-express-validator` options to override the defaults.
* @returns A new express router with validation middleware.
@@ -132,6 +134,7 @@ export function createValidatedOpenApiRouter(
/**
* Create a new OpenAPI router with some default middleware.
+ * Only supports OpenAPI 3.1 specifications.
* @param spec - Your OpenAPI spec imported as a JSON object.
* @param validatorOptions - `openapi-express-validator` options to override the defaults.
* @returns A new express router with validation middleware.
diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md
index 94768add54..bbcf28b17f 100644
--- a/packages/backend-plugin-api/CHANGELOG.md
+++ b/packages/backend-plugin-api/CHANGELOG.md
@@ -1,5 +1,101 @@
# @backstage/backend-plugin-api
+## 1.9.0-next.1
+
+### Minor Changes
+
+- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+
+## 1.8.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+
+## 1.8.0
+
+### Minor Changes
+
+- cc8348e: Added optional `visibilityPermission` field to `ActionsRegistryActionOptions`, allowing actions to declare a `BasicPermission` that controls visibility and access.
+
+ ```typescript
+ import { createPermission } from '@backstage/plugin-permission-common';
+
+ const myPermission = createPermission({
+ name: 'myPlugin.myAction.use',
+ attributes: {},
+ });
+
+ actionsRegistry.register({
+ name: 'my-action',
+ title: 'My Action',
+ description: 'An action that requires permission',
+ visibilityPermission: myPermission,
+ schema: {
+ input: z => z.object({ name: z.string() }),
+ output: z => z.object({ ok: z.boolean() }),
+ },
+ action: async ({ input }) => {
+ return { output: { ok: true } };
+ },
+ });
+ ```
+
+ Actions without a `visibilityPermission` field continue to work as before.
+
+- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available.
+
+### Patch Changes
+
+- dee4283: Added `pluginId` field to `ActionsServiceAction` type, populated from the registering plugin's metadata.
+- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/plugin-auth-node@0.6.14
+
+## 1.8.0-next.1
+
+### Minor Changes
+
+- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.2
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-permission-node@0.10.11-next.1
+
+## 1.7.1-next.0
+
+### Patch Changes
+
+- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
+- Updated dependencies
+ - @backstage/cli-common@0.2.0-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+
## 1.7.0
### Minor Changes
diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json
index 131f24bafb..5f836592c0 100644
--- a/packages/backend-plugin-api/package.json
+++ b/packages/backend-plugin-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-plugin-api",
- "version": "1.7.0",
+ "version": "1.9.0-next.1",
"description": "Core API used by Backstage backend plugins",
"backstage": {
"role": "node-library"
@@ -66,7 +66,7 @@
"json-schema": "^0.4.0",
"knex": "^3.0.0",
"luxon": "^3.0.0",
- "zod": "^3.25.76"
+ "zod": "^3.25.76 || ^4.0.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
diff --git a/packages/backend-plugin-api/report-alpha.api.md b/packages/backend-plugin-api/report-alpha.api.md
index 7d6786b97e..d1db9c7eeb 100644
--- a/packages/backend-plugin-api/report-alpha.api.md
+++ b/packages/backend-plugin-api/report-alpha.api.md
@@ -3,14 +3,15 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
-import { AnyZodObject } from 'zod';
+import { AnyZodObject } from 'zod/v3';
import { BackstageCredentials } from '@backstage/backend-plugin-api';
+import { BasicPermission } from '@backstage/plugin-permission-common';
import { JsonObject } from '@backstage/types';
import { JSONSchema7 } from 'json-schema';
import { JsonValue } from '@backstage/types';
import { LoggerService } from '@backstage/backend-plugin-api';
import { ServiceRef } from '@backstage/backend-plugin-api';
-import { z } from 'zod';
+import { z } from 'zod/v3';
// @alpha (undocumented)
export type ActionsRegistryActionContext = {
@@ -19,6 +20,17 @@ export type ActionsRegistryActionContext = {
credentials: BackstageCredentials;
};
+// @alpha
+export type ActionsRegistryActionExample<
+ TInputSchema extends AnyZodObject,
+ TOutputSchema extends AnyZodObject,
+> = {
+ title: string;
+ description?: string;
+ input: z.infer;
+ output?: z.infer;
+};
+
// @alpha (undocumented)
export type ActionsRegistryActionOptions<
TInputSchema extends AnyZodObject,
@@ -31,6 +43,8 @@ export type ActionsRegistryActionOptions<
input: (zod: typeof z) => TInputSchema;
output: (zod: typeof z) => TOutputSchema;
};
+ examples?: Array>;
+ visibilityPermission?: BasicPermission;
attributes?: {
destructive?: boolean;
idempotent?: boolean;
@@ -82,6 +96,7 @@ export interface ActionsService {
// @alpha (undocumented)
export type ActionsServiceAction = {
id: string;
+ pluginId: string;
name: string;
title: string;
description: string;
@@ -89,6 +104,12 @@ export type ActionsServiceAction = {
input: JSONSchema7;
output: JSONSchema7;
};
+ examples?: Array<{
+ title: string;
+ description?: string;
+ input: JsonObject;
+ output?: JsonObject;
+ }>;
attributes: {
readOnly: boolean;
destructive: boolean;
diff --git a/packages/backend-plugin-api/report.api.md b/packages/backend-plugin-api/report.api.md
index 504be97ec6..96d1a02c0a 100644
--- a/packages/backend-plugin-api/report.api.md
+++ b/packages/backend-plugin-api/report.api.md
@@ -643,6 +643,7 @@ export interface RootServiceFactoryOptions<
// @public
export interface SchedulerService {
+ cancelTask(id: string): Promise;
createScheduledTaskRunner(
schedule: SchedulerServiceTaskScheduleDefinition,
): SchedulerServiceTaskRunner;
diff --git a/packages/backend-plugin-api/src/alpha/ActionsRegistryService.ts b/packages/backend-plugin-api/src/alpha/ActionsRegistryService.ts
index 9a7820e354..65f44be051 100644
--- a/packages/backend-plugin-api/src/alpha/ActionsRegistryService.ts
+++ b/packages/backend-plugin-api/src/alpha/ActionsRegistryService.ts
@@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { z, AnyZodObject } from 'zod';
+import { z, AnyZodObject } from 'zod/v3';
+import { BasicPermission } from '@backstage/plugin-permission-common';
import {
LoggerService,
BackstageCredentials,
@@ -28,6 +29,21 @@ export type ActionsRegistryActionContext = {
credentials: BackstageCredentials;
};
+/**
+ * An example of how to use an action registered in the actions registry.
+ *
+ * @alpha
+ */
+export type ActionsRegistryActionExample<
+ TInputSchema extends AnyZodObject,
+ TOutputSchema extends AnyZodObject,
+> = {
+ title: string;
+ description?: string;
+ input: z.infer;
+ output?: z.infer;
+};
+
/**
* @alpha
*/
@@ -42,6 +58,8 @@ export type ActionsRegistryActionOptions<
input: (zod: typeof z) => TInputSchema;
output: (zod: typeof z) => TOutputSchema;
};
+ examples?: Array>;
+ visibilityPermission?: BasicPermission;
attributes?: {
destructive?: boolean;
idempotent?: boolean;
diff --git a/packages/backend-plugin-api/src/alpha/ActionsService.ts b/packages/backend-plugin-api/src/alpha/ActionsService.ts
index 528b6cea67..7062de9c77 100644
--- a/packages/backend-plugin-api/src/alpha/ActionsService.ts
+++ b/packages/backend-plugin-api/src/alpha/ActionsService.ts
@@ -22,6 +22,7 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api';
*/
export type ActionsServiceAction = {
id: string;
+ pluginId: string;
name: string;
title: string;
description: string;
@@ -29,6 +30,12 @@ export type ActionsServiceAction = {
input: JSONSchema7;
output: JSONSchema7;
};
+ examples?: Array<{
+ title: string;
+ description?: string;
+ input: JsonObject;
+ output?: JsonObject;
+ }>;
attributes: {
readOnly: boolean;
destructive: boolean;
diff --git a/packages/backend-plugin-api/src/alpha/index.ts b/packages/backend-plugin-api/src/alpha/index.ts
index c487686338..9ec7ca5b09 100644
--- a/packages/backend-plugin-api/src/alpha/index.ts
+++ b/packages/backend-plugin-api/src/alpha/index.ts
@@ -23,6 +23,7 @@ export type {
ActionsRegistryService,
ActionsRegistryActionOptions,
ActionsRegistryActionContext,
+ ActionsRegistryActionExample,
} from './ActionsRegistryService';
export type { ActionsService, ActionsServiceAction } from './ActionsService';
diff --git a/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts b/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts
index 6c0936f85c..39c62ce731 100644
--- a/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts
+++ b/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts
@@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { z, AnyZodObject } from 'zod';
+import { z, AnyZodObject } from 'zod/v3';
+import { BasicPermission } from '@backstage/plugin-permission-common';
import { LoggerService } from './LoggerService';
import { BackstageCredentials } from './AuthService';
@@ -26,6 +27,21 @@ export type ActionsRegistryActionContext = {
credentials: BackstageCredentials;
};
+/**
+ * An example of how to use an action registered in the actions registry.
+ *
+ * @public
+ */
+export type ActionsRegistryActionExample<
+ TInputSchema extends AnyZodObject,
+ TOutputSchema extends AnyZodObject,
+> = {
+ title: string;
+ description?: string;
+ input: z.infer;
+ output?: z.infer;
+};
+
/**
* @public
*/
@@ -40,6 +56,8 @@ export type ActionsRegistryActionOptions<
input: (zod: typeof z) => TInputSchema;
output: (zod: typeof z) => TOutputSchema;
};
+ examples?: Array>;
+ visibilityPermission?: BasicPermission;
attributes?: {
destructive?: boolean;
idempotent?: boolean;
diff --git a/packages/backend-plugin-api/src/services/definitions/ActionsService.ts b/packages/backend-plugin-api/src/services/definitions/ActionsService.ts
index d595008a42..3afa131915 100644
--- a/packages/backend-plugin-api/src/services/definitions/ActionsService.ts
+++ b/packages/backend-plugin-api/src/services/definitions/ActionsService.ts
@@ -29,6 +29,12 @@ export type ActionsServiceAction = {
input: JSONSchema7;
output: JSONSchema7;
};
+ examples?: Array<{
+ title: string;
+ description?: string;
+ input: JsonObject;
+ output?: JsonObject;
+ }>;
attributes: {
readOnly: boolean;
destructive: boolean;
diff --git a/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts b/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts
index b1a0e17df8..2748b8fac7 100644
--- a/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts
+++ b/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts
@@ -304,6 +304,16 @@ export interface SchedulerService {
*/
triggerTask(id: string): Promise;
+ /**
+ * Cancels a currently running task by ID, marking it as idle.
+ *
+ * If the task doesn't exist, a NotFoundError is thrown. If the task is
+ * not currently running, a ConflictError is thrown.
+ *
+ * @param id - The task ID
+ */
+ cancelTask(id: string): Promise;
+
/**
* Schedules a task function for recurring runs.
*
diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md
index 0b144df1da..5f783d9029 100644
--- a/packages/backend-test-utils/CHANGELOG.md
+++ b/packages/backend-test-utils/CHANGELOG.md
@@ -1,5 +1,93 @@
# @backstage/backend-test-utils
+## 1.11.2-next.1
+
+### Patch Changes
+
+- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-defaults@0.16.1-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/backend-app-api@1.6.1-next.1
+ - @backstage/plugin-events-node@0.4.21-next.1
+
+## 1.11.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.16.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/backend-app-api@1.6.1-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-events-node@0.4.21-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.7
+
+## 1.11.1
+
+### Patch Changes
+
+- dee4283: Added `pluginId` field to `ActionsServiceAction` type, populated from the registering plugin's metadata.
+- 164711a: Added `cancelTask` to `MockSchedulerService` and mock scheduler service factory.
+- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500.
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+- 1ee5b28: Adds a new metrics service mock to be leveraged in tests
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-defaults@0.16.0
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-auth-node@0.6.14
+ - @backstage/backend-app-api@1.6.0
+ - @backstage/plugin-events-node@0.4.20
+
+## 1.11.1-next.2
+
+### Patch Changes
+
+- 164711a: Added `cancelTask` to `MockSchedulerService` and mock scheduler service factory.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-defaults@0.16.0-next.2
+ - @backstage/backend-app-api@1.6.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-events-node@0.4.20-next.1
+
+## 1.11.1-next.1
+
+### Patch Changes
+
+- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500.
+- Updated dependencies
+ - @backstage/backend-defaults@0.16.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.1
+ - @backstage/backend-app-api@1.5.1-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+
+## 1.11.1-next.0
+
+### Patch Changes
+
+- 1ee5b28: Adds a new metrics service mock to be leveraged in tests
+- Updated dependencies
+ - @backstage/backend-defaults@0.15.3-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/backend-app-api@1.5.1-next.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-events-node@0.4.20-next.0
+ - @backstage/plugin-permission-common@0.9.6
+
## 1.11.0
### Minor Changes
diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json
index ee65ddbc78..4e5bcb2074 100644
--- a/packages/backend-test-utils/package.json
+++ b/packages/backend-test-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-test-utils",
- "version": "1.11.0",
+ "version": "1.11.2-next.1",
"description": "Test helpers library for Backstage backends",
"backstage": {
"role": "node-library"
@@ -79,7 +79,7 @@
"text-extensions": "^2.4.0",
"uuid": "^11.0.0",
"yn": "^4.0.0",
- "zod": "^3.25.76",
+ "zod": "^3.25.76 || ^4.0.0",
"zod-to-json-schema": "^3.25.1"
},
"devDependencies": {
diff --git a/packages/backend-test-utils/report-alpha.api.md b/packages/backend-test-utils/report-alpha.api.md
index b6bf9e883d..4813f49c35 100644
--- a/packages/backend-test-utils/report-alpha.api.md
+++ b/packages/backend-test-utils/report-alpha.api.md
@@ -7,7 +7,7 @@ import { ActionsRegistryActionOptions } from '@backstage/backend-plugin-api/alph
import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha';
import { ActionsService } from '@backstage/backend-plugin-api/alpha';
import { ActionsServiceAction } from '@backstage/backend-plugin-api/alpha';
-import { AnyZodObject } from 'zod';
+import { AnyZodObject } from 'zod/v3';
import { BackstageCredentials } from '@backstage/backend-plugin-api';
import { JsonObject } from '@backstage/types';
import { JsonValue } from '@backstage/types';
diff --git a/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.ts b/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.ts
index 94e5987afd..d3d94d8d9c 100644
--- a/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.ts
+++ b/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.ts
@@ -17,9 +17,9 @@ import {
BackstageCredentials,
LoggerService,
} from '@backstage/backend-plugin-api';
-import { ForwardedError, InputError, NotFoundError } from '@backstage/errors';
+import { InputError, NotFoundError } from '@backstage/errors';
import { JsonObject, JsonValue } from '@backstage/types';
-import { z, AnyZodObject } from 'zod';
+import { z, AnyZodObject } from 'zod/v3';
import zodToJsonSchema from 'zod-to-json-schema';
import { mockCredentials } from '../../services';
import {
@@ -82,6 +82,7 @@ export class MockActionsRegistry
return {
actions: Array.from(this.actions.entries()).map(([id, action]) => ({
id,
+ pluginId: 'test',
name: action.name,
title: action.title,
description: action.description,
@@ -90,6 +91,7 @@ export class MockActionsRegistry
idempotent: action.attributes?.idempotent ?? false,
readOnly: action.attributes?.readOnly ?? false,
},
+ examples: action.examples,
schema: {
input: action.schema?.input
? zodToJsonSchema(action.schema.input(z))
@@ -126,31 +128,24 @@ export class MockActionsRegistry
throw new InputError(`Invalid input to action "${opts.id}"`, input.error);
}
- try {
- const result = await action.action({
- input: input.data,
- credentials: opts.credentials ?? mockCredentials.none(),
- logger: this.logger,
- });
+ const result = await action.action({
+ input: input.data,
+ credentials: opts.credentials ?? mockCredentials.none(),
+ logger: this.logger,
+ });
- const output = action.schema?.output
- ? action.schema.output(z).safeParse(result?.output)
- : ({ success: true, data: result?.output } as const);
+ const output = action.schema?.output
+ ? action.schema.output(z).safeParse(result?.output)
+ : ({ success: true, data: result?.output } as const);
- if (!output.success) {
- throw new InputError(
- `Invalid output from action "${opts.id}"`,
- output.error,
- );
- }
-
- return { output: output.data };
- } catch (error) {
- throw new ForwardedError(
- `Failed execution of action "${opts.id}"`,
- error,
+ if (!output.success) {
+ throw new InputError(
+ `Invalid output from action "${opts.id}"`,
+ output.error,
);
}
+
+ return { output: output.data };
}
register<
diff --git a/packages/backend-test-utils/src/services/MockSchedulerService.test.ts b/packages/backend-test-utils/src/services/MockSchedulerService.test.ts
index faf4e3e26d..2668fa7205 100644
--- a/packages/backend-test-utils/src/services/MockSchedulerService.test.ts
+++ b/packages/backend-test-utils/src/services/MockSchedulerService.test.ts
@@ -206,6 +206,68 @@ describe('MockSchedulerService', () => {
await expect(isDone()).resolves.toBe(true);
});
+ it('should cancel a running task and allow re-triggering with a fresh signal', async () => {
+ const scheduler = new MockSchedulerService();
+ const signals: AbortSignal[] = [];
+
+ scheduler.scheduleTask({
+ ...baseOpts,
+ id: 'test',
+ fn: async signal => {
+ signals.push(signal);
+ // Simulate long-running work that respects cancellation
+ await new Promise((resolve, reject) => {
+ if (signal.aborted) {
+ reject(new Error('aborted'));
+ return;
+ }
+ signal.addEventListener('abort', () => reject(new Error('aborted')));
+ setTimeout(1).then(resolve);
+ });
+ },
+ });
+
+ // First run completes normally
+ await scheduler.triggerTask('test');
+ expect(signals).toHaveLength(1);
+ expect(signals[0].aborted).toBe(false);
+
+ // Start a task that will block until cancelled
+ const blockingScheduler = new MockSchedulerService();
+ let resolveBlock: (() => void) | undefined;
+ blockingScheduler.scheduleTask({
+ ...baseOpts,
+ id: 'blocking',
+ fn: async signal => {
+ signals.push(signal);
+ await new Promise((resolve, reject) => {
+ signal.addEventListener('abort', () => reject(new Error('aborted')));
+ resolveBlock = resolve;
+ });
+ },
+ });
+
+ const triggerPromise = blockingScheduler.triggerTask('blocking');
+ // Give the task fn time to start
+ await setTimeout(1);
+
+ await blockingScheduler.cancelTask('blocking');
+ await triggerPromise.catch(() => {});
+
+ expect(signals).toHaveLength(2);
+ expect(signals[1].aborted).toBe(true);
+
+ // Re-trigger should get a fresh non-aborted signal
+ resolveBlock = undefined;
+ const triggerPromise2 = blockingScheduler.triggerTask('blocking');
+ await setTimeout(1);
+ resolveBlock!();
+ await triggerPromise2;
+
+ expect(signals).toHaveLength(3);
+ expect(signals[2].aborted).toBe(false);
+ });
+
it('should abort tasks when shutting down', async () => {
let taskSignal: AbortSignal | undefined;
diff --git a/packages/backend-test-utils/src/services/MockSchedulerService.ts b/packages/backend-test-utils/src/services/MockSchedulerService.ts
index aafd6d98ec..3309ebc954 100644
--- a/packages/backend-test-utils/src/services/MockSchedulerService.ts
+++ b/packages/backend-test-utils/src/services/MockSchedulerService.ts
@@ -23,6 +23,7 @@ import {
SchedulerServiceTaskRunner,
SchedulerServiceTaskScheduleDefinition,
} from '@backstage/backend-plugin-api';
+import { ConflictError, NotFoundError } from '@backstage/errors';
import { createDeferred, DeferredPromise } from '@backstage/types';
export class MockSchedulerService implements SchedulerService {
@@ -95,10 +96,22 @@ export class MockSchedulerService implements SchedulerService {
});
}
+ async cancelTask(id: string): Promise {
+ const task = this.#tasks.get(id);
+ if (!task) {
+ throw new NotFoundError(`Task ${id} not found`);
+ }
+ if (!this.#runningTasks.has(id)) {
+ throw new ConflictError(`Task ${id} is not running`);
+ }
+ task.abortControllers.abort();
+ task.abortControllers = new AbortController();
+ }
+
async triggerTask(id: string): Promise {
const task = this.#tasks.get(id);
if (!task) {
- throw new Error(`Task ${id} not found`);
+ throw new NotFoundError(`Task ${id} not found`);
}
if (this.#runningTasks.has(id)) {
return;
diff --git a/packages/backend-test-utils/src/services/mockServices.ts b/packages/backend-test-utils/src/services/mockServices.ts
index 84783f04d7..4d54269b98 100644
--- a/packages/backend-test-utils/src/services/mockServices.ts
+++ b/packages/backend-test-utils/src/services/mockServices.ts
@@ -526,6 +526,7 @@ export namespace mockServices {
getScheduledTasks: jest.fn(),
scheduleTask: jest.fn(),
triggerTask: jest.fn(),
+ cancelTask: jest.fn(),
}));
}
diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md
index 27ef95a0f3..9e3353c8be 100644
--- a/packages/backend/CHANGELOG.md
+++ b/packages/backend/CHANGELOG.md
@@ -1,5 +1,255 @@
# example-backend
+## 0.0.49-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.9.0-next.1
+ - @backstage/backend-defaults@0.16.1-next.1
+ - @backstage/plugin-catalog-backend@3.5.1-next.1
+ - @backstage/plugin-scaffolder-backend@3.3.0-next.1
+ - @backstage/plugin-auth-node@0.7.0-next.1
+ - @backstage/plugin-app-backend@0.5.13-next.1
+ - @backstage/plugin-auth-backend@0.28.0-next.1
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.2-next.1
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.18-next.1
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.6-next.1
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.13-next.1
+ - @backstage/plugin-catalog-backend-module-logs@0.1.21-next.1
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.21-next.1
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.19-next.1
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.10-next.1
+ - @backstage/plugin-devtools-backend@0.5.16-next.1
+ - @backstage/plugin-events-backend@0.6.1-next.1
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.2-next.1
+ - @backstage/plugin-kubernetes-backend@0.21.3-next.1
+ - @backstage/plugin-mcp-actions-backend@0.1.11-next.1
+ - @backstage/plugin-notifications-backend@0.6.4-next.1
+ - @backstage/plugin-permission-backend@0.7.11-next.1
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.18-next.1
+ - @backstage/plugin-permission-node@0.10.12-next.1
+ - @backstage/plugin-proxy-backend@0.6.12-next.1
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.8-next.1
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.21-next.1
+ - @backstage/plugin-search-backend@2.1.1-next.1
+ - @backstage/plugin-search-backend-module-catalog@0.3.14-next.1
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.2-next.1
+ - @backstage/plugin-search-backend-module-explore@0.3.13-next.1
+ - @backstage/plugin-search-backend-module-techdocs@0.4.13-next.1
+ - @backstage/plugin-search-backend-node@1.4.3-next.1
+ - @backstage/plugin-signals-backend@0.3.14-next.1
+ - @backstage/plugin-techdocs-backend@2.1.7-next.1
+
+## 0.0.49-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.5.1-next.0
+ - @backstage/plugin-auth-backend@0.28.0-next.0
+ - @backstage/backend-defaults@0.16.1-next.0
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.2-next.0
+ - @backstage/plugin-scaffolder-backend@3.2.1-next.0
+ - @backstage/backend-plugin-api@1.8.1-next.0
+ - @backstage/plugin-devtools-backend@0.5.16-next.0
+ - @backstage/plugin-catalog-backend-module-logs@0.1.21-next.0
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.2-next.0
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.6-next.0
+ - @backstage/plugin-notifications-backend@0.6.4-next.0
+ - @backstage/plugin-signals-backend@0.3.14-next.0
+ - @backstage/plugin-app-backend@0.5.13-next.0
+ - @backstage/plugin-auth-node@0.6.15-next.0
+ - @backstage/plugin-events-backend@0.6.1-next.0
+ - @backstage/plugin-kubernetes-backend@0.21.3-next.0
+ - @backstage/plugin-mcp-actions-backend@0.1.11-next.0
+ - @backstage/plugin-permission-backend@0.7.11-next.0
+ - @backstage/plugin-permission-node@0.10.12-next.0
+ - @backstage/plugin-proxy-backend@0.6.12-next.0
+ - @backstage/plugin-search-backend@2.1.1-next.0
+ - @backstage/plugin-search-backend-node@1.4.3-next.0
+ - @backstage/plugin-techdocs-backend@2.1.7-next.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.18-next.0
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.13-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.21-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.19-next.0
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.10-next.0
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.18-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.8-next.0
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.21-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.3.14-next.0
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.2-next.0
+ - @backstage/plugin-search-backend-module-explore@0.3.13-next.0
+ - @backstage/plugin-search-backend-module-techdocs@0.4.13-next.0
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-permission-common@0.9.7
+
+## 0.0.48
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0
+ - @backstage/backend-defaults@0.16.0
+ - @backstage/plugin-catalog-backend@3.5.0
+ - @backstage/plugin-auth-backend@0.27.2
+ - @backstage/plugin-techdocs-backend@2.1.6
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.7
+ - @backstage/plugin-scaffolder-backend@3.2.0
+ - @backstage/plugin-mcp-actions-backend@0.1.10
+ - @backstage/plugin-kubernetes-backend@0.21.2
+ - @backstage/plugin-events-backend@0.6.0
+ - @backstage/plugin-search-backend@2.1.0
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.1
+ - @backstage/plugin-permission-backend@0.7.10
+ - @backstage/plugin-permission-common@0.9.7
+ - @backstage/plugin-permission-node@0.10.11
+ - @backstage/catalog-model@1.7.7
+ - @backstage/plugin-auth-node@0.6.14
+ - @backstage/plugin-app-backend@0.5.12
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.17
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.20
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9
+ - @backstage/plugin-devtools-backend@0.5.15
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.1
+ - @backstage/plugin-notifications-backend@0.6.3
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17
+ - @backstage/plugin-proxy-backend@0.6.11
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20
+ - @backstage/plugin-search-backend-module-catalog@0.3.13
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.1
+ - @backstage/plugin-search-backend-module-explore@0.3.12
+ - @backstage/plugin-search-backend-module-techdocs@0.4.12
+ - @backstage/plugin-search-backend-node@1.4.2
+ - @backstage/plugin-signals-backend@0.3.13
+
+## 0.0.48-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.8.0-next.1
+ - @backstage/backend-defaults@0.16.0-next.2
+ - @backstage/plugin-auth-backend@0.27.1-next.2
+ - @backstage/plugin-catalog-backend@3.5.0-next.2
+ - @backstage/plugin-scaffolder-backend@3.2.0-next.2
+ - @backstage/plugin-mcp-actions-backend@0.1.10-next.2
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.2
+ - @backstage/plugin-app-backend@0.5.12-next.1
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.1
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.1
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.2
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.1
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.2
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.2
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.1
+ - @backstage/plugin-devtools-backend@0.5.15-next.1
+ - @backstage/plugin-events-backend@0.6.0-next.2
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.1
+ - @backstage/plugin-kubernetes-backend@0.21.2-next.2
+ - @backstage/plugin-notifications-backend@0.6.3-next.1
+ - @backstage/plugin-permission-backend@0.7.10-next.1
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.1
+ - @backstage/plugin-permission-node@0.10.11-next.1
+ - @backstage/plugin-proxy-backend@0.6.11-next.1
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.2
+ - @backstage/plugin-search-backend@2.1.0-next.2
+ - @backstage/plugin-search-backend-module-catalog@0.3.13-next.2
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.1
+ - @backstage/plugin-search-backend-module-explore@0.3.12-next.1
+ - @backstage/plugin-search-backend-module-techdocs@0.4.12-next.2
+ - @backstage/plugin-search-backend-node@1.4.2-next.1
+ - @backstage/plugin-signals-backend@0.3.13-next.1
+ - @backstage/plugin-techdocs-backend@2.1.6-next.2
+
+## 0.0.48-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.5.0-next.1
+ - @backstage/plugin-auth-backend@0.27.1-next.1
+ - @backstage/plugin-techdocs-backend@2.1.6-next.1
+ - @backstage/backend-defaults@0.16.0-next.1
+ - @backstage/plugin-scaffolder-backend@3.2.0-next.1
+ - @backstage/plugin-mcp-actions-backend@0.1.10-next.1
+ - @backstage/plugin-events-backend@0.6.0-next.1
+ - @backstage/plugin-search-backend@2.1.0-next.1
+ - @backstage/plugin-auth-node@0.6.14-next.1
+ - @backstage/plugin-kubernetes-backend@0.21.2-next.1
+ - @backstage/plugin-search-backend-module-catalog@0.3.13-next.1
+ - @backstage/plugin-search-backend-module-techdocs@0.4.12-next.1
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-app-backend@0.5.12-next.0
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.0
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.0
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.1
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.1
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.0
+ - @backstage/plugin-devtools-backend@0.5.15-next.0
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.0
+ - @backstage/plugin-notifications-backend@0.6.3-next.0
+ - @backstage/plugin-permission-backend@0.7.10-next.0
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+ - @backstage/plugin-proxy-backend@0.6.11-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.1
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.1
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.0
+ - @backstage/plugin-search-backend-module-explore@0.3.12-next.0
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-signals-backend@0.3.13-next.0
+
+## 0.0.48-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.15.3-next.0
+ - @backstage/plugin-auth-backend@0.27.1-next.0
+ - @backstage/plugin-catalog-backend@3.5.0-next.0
+ - @backstage/plugin-scaffolder-backend@3.1.4-next.0
+ - @backstage/backend-plugin-api@1.7.1-next.0
+ - @backstage/plugin-mcp-actions-backend@0.1.10-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-app-backend@0.5.12-next.0
+ - @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.0
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.0
+ - @backstage/plugin-auth-node@0.6.14-next.0
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.0
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.0
+ - @backstage/plugin-devtools-backend@0.5.15-next.0
+ - @backstage/plugin-events-backend@0.5.12-next.0
+ - @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.0
+ - @backstage/plugin-kubernetes-backend@0.21.2-next.0
+ - @backstage/plugin-notifications-backend@0.6.3-next.0
+ - @backstage/plugin-permission-backend@0.7.10-next.0
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.0
+ - @backstage/plugin-permission-common@0.9.6
+ - @backstage/plugin-permission-node@0.10.11-next.0
+ - @backstage/plugin-proxy-backend@0.6.11-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.0
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.0
+ - @backstage/plugin-search-backend@2.0.13-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.3.13-next.0
+ - @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.0
+ - @backstage/plugin-search-backend-module-explore@0.3.12-next.0
+ - @backstage/plugin-search-backend-module-techdocs@0.4.12-next.0
+ - @backstage/plugin-search-backend-node@1.4.2-next.0
+ - @backstage/plugin-signals-backend@0.3.13-next.0
+ - @backstage/plugin-techdocs-backend@2.1.6-next.0
+
## 0.0.47
### Patch Changes
diff --git a/packages/backend/package.json b/packages/backend/package.json
index b6b97f214e..fd2c166773 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "example-backend",
- "version": "0.0.47",
+ "version": "0.0.49-next.1",
"backstage": {
"role": "backend"
},
@@ -41,6 +41,7 @@
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/plugin-catalog-backend": "workspace:^",
"@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^",
+ "@backstage/plugin-catalog-backend-module-logs": "workspace:^",
"@backstage/plugin-catalog-backend-module-openapi": "workspace:^",
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^",
"@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^",
@@ -66,9 +67,9 @@
"@backstage/plugin-search-backend-node": "workspace:^",
"@backstage/plugin-signals-backend": "workspace:^",
"@backstage/plugin-techdocs-backend": "workspace:^",
- "@opentelemetry/auto-instrumentations-node": "^0.67.0",
- "@opentelemetry/exporter-prometheus": "^0.211.0",
- "@opentelemetry/sdk-node": "^0.211.0",
+ "@opentelemetry/auto-instrumentations-node": "^0.71.0",
+ "@opentelemetry/exporter-prometheus": "^0.213.0",
+ "@opentelemetry/sdk-node": "^0.213.0",
"example-app": "link:../app"
},
"devDependencies": {
diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts
index 14eb36a3a6..e77c83baac 100644
--- a/packages/backend/src/index.ts
+++ b/packages/backend/src/index.ts
@@ -50,6 +50,7 @@ backend.add(
import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
);
backend.add(import('@backstage/plugin-catalog-backend'));
+backend.add(import('@backstage/plugin-catalog-backend-module-logs'));
backend.add(import('@backstage/plugin-events-backend'));
backend.add(import('@backstage/plugin-devtools-backend'));
backend.add(import('@backstage/plugin-kubernetes-backend'));
diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md
index 563a0ef828..874212077b 100644
--- a/packages/catalog-client/CHANGELOG.md
+++ b/packages/catalog-client/CHANGELOG.md
@@ -1,5 +1,62 @@
# @backstage/catalog-client
+## 1.14.0
+
+### Minor Changes
+
+- 972f686: Added support for the `query` field in `getEntitiesByRefs` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+- 5d95e8e: Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error.
+- 56c908e: Added support for the `query` field in `getEntityFacets` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+- 51e23eb: Added predicate-based entity filtering via POST /entities/by-query endpoint.
+
+ Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and (partially) `$contains` operators for expressive entity queries. Integrated into the existing `queryEntities` flow with full cursor-based pagination, permission enforcement, and `totalItems` support.
+
+ The catalog client's `queryEntities()` method automatically routes to the POST endpoint when a `query` predicate is provided.
+
+### Patch Changes
+
+- d2494d6: Minor update to catalog client docs
+- Updated dependencies
+ - @backstage/filter-predicates@0.1.1
+ - @backstage/catalog-model@1.7.7
+
+## 1.14.0-next.2
+
+### Minor Changes
+
+- 5d95e8e: Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error.
+
+## 1.14.0-next.1
+
+### Minor Changes
+
+- 972f686: Added support for the `query` field in `getEntitiesByRefs` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+- 56c908e: Added support for the `query` field in `getEntityFacets` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators.
+- 0fbcf23: Migrated OpenAPI schemas to 3.1.
+- 51e23eb: Added predicate-based entity filtering via POST /entities/by-query endpoint.
+
+ Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and (partially) `$contains` operators for expressive entity queries. Integrated into the existing `queryEntities` flow with full cursor-based pagination, permission enforcement, and `totalItems` support.
+
+ The catalog client's `queryEntities()` method automatically routes to the POST endpoint when a `query` predicate is provided.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.0
+
+## 1.13.1-next.0
+
+### Patch Changes
+
+- d2494d6: Minor update to catalog client docs
+- Updated dependencies
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/filter-predicates@0.1.0
+
## 1.13.0
### Minor Changes
diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json
index 1a299182e8..1235f4d5ca 100644
--- a/packages/catalog-client/package.json
+++ b/packages/catalog-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-client",
- "version": "1.13.0",
+ "version": "1.14.0",
"description": "An isomorphic client for the catalog backend",
"backstage": {
"role": "common-library"
diff --git a/packages/catalog-client/report.api.md b/packages/catalog-client/report.api.md
index 58fc4bc8a1..73eb280d87 100644
--- a/packages/catalog-client/report.api.md
+++ b/packages/catalog-client/report.api.md
@@ -7,14 +7,15 @@ import type { AnalyzeLocationRequest } from '@backstage/plugin-catalog-common';
import type { AnalyzeLocationResponse } from '@backstage/plugin-catalog-common';
import { CompoundEntityRef } from '@backstage/catalog-model';
import { Entity } from '@backstage/catalog-model';
-import { FilterPredicate } from '@backstage/filter-predicates';
-import { SerializedError } from '@backstage/errors';
+import type { FilterPredicate } from '@backstage/filter-predicates';
+import type { SerializedError } from '@backstage/errors';
// @public
export type AddLocationRequest = {
type?: string;
target: string;
dryRun?: boolean;
+ onConflict?: 'refresh' | 'reject';
};
// @public
@@ -236,6 +237,7 @@ export interface GetEntitiesByRefsRequest {
entityRefs: string[];
fields?: EntityFieldsQuery | undefined;
filter?: EntityFilterQuery;
+ query?: FilterPredicate;
}
// @public
@@ -280,6 +282,7 @@ export interface GetEntityAncestorsResponse {
export interface GetEntityFacetsRequest {
facets: string[];
filter?: EntityFilterQuery;
+ query?: FilterPredicate;
}
// @public
@@ -320,6 +323,7 @@ export type QueryEntitiesInitialRequest = {
limit?: number;
offset?: number;
filter?: EntityFilterQuery;
+ query?: FilterPredicate;
orderFields?: EntityOrderQuery;
fullTextFilter?: {
term: string;
diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts
index d62323852e..70265bd6cb 100644
--- a/packages/catalog-client/src/CatalogClient.test.ts
+++ b/packages/catalog-client/src/CatalogClient.test.ts
@@ -302,6 +302,103 @@ describe('CatalogClient', () => {
expect(response).toEqual({ items: [entity, undefined] });
});
+
+ it('sends only query predicate in the body when query is provided without filter', async () => {
+ expect.assertions(3);
+ const entity = {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ name: 'Test2',
+ namespace: 'test1',
+ },
+ };
+ server.use(
+ rest.post(`${mockBaseUrl}/entities/by-refs`, async (req, res, ctx) => {
+ expect(req.url.search).toBe('');
+ await expect(req.json()).resolves.toEqual({
+ entityRefs: ['k:n/a'],
+ query: { kind: 'Component' },
+ });
+ return res(ctx.json({ items: [entity] }));
+ }),
+ );
+
+ const response = await client.getEntitiesByRefs(
+ {
+ entityRefs: ['k:n/a'],
+ query: { kind: 'Component' },
+ },
+ { token },
+ );
+
+ expect(response).toEqual({ items: [entity] });
+ });
+
+ it('merges filter and query into $all predicate when both are provided', async () => {
+ expect.assertions(4);
+ const entity = {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ name: 'Test2',
+ namespace: 'test1',
+ },
+ };
+ server.use(
+ rest.post(`${mockBaseUrl}/entities/by-refs`, async (req, res, ctx) => {
+ expect(req.url.search).toBe('');
+ const body = await req.json();
+ expect(body.entityRefs).toEqual(['k:n/a']);
+ expect(body.query).toEqual({
+ $all: [{ kind: 'Component' }, { kind: 'API' }],
+ });
+ return res(ctx.json({ items: [entity] }));
+ }),
+ );
+
+ const response = await client.getEntitiesByRefs(
+ {
+ entityRefs: ['k:n/a'],
+ query: { kind: 'Component' },
+ filter: { kind: ['API'] },
+ },
+ { token },
+ );
+
+ expect(response).toEqual({ items: [entity] });
+ });
+
+ it('sends filter as query parameter when only filter is provided (backward compat)', async () => {
+ expect.assertions(4);
+ const entity = {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ name: 'Test2',
+ namespace: 'test1',
+ },
+ };
+ server.use(
+ rest.post(`${mockBaseUrl}/entities/by-refs`, async (req, res, ctx) => {
+ expect(req.url.search).toBe('?filter=kind%3DAPI');
+ const body = await req.json();
+ expect(body).toEqual({ entityRefs: ['k:n/a'] });
+ expect(body.query).toBeUndefined();
+ return res(ctx.json({ items: [entity] }));
+ }),
+ );
+
+ const response = await client.getEntitiesByRefs(
+ {
+ entityRefs: ['k:n/a'],
+ filter: { kind: ['API'] },
+ },
+ { token },
+ );
+
+ expect(response).toEqual({ items: [entity] });
+ });
});
describe('queryEntities', () => {
@@ -540,6 +637,350 @@ describe('CatalogClient', () => {
});
});
+ describe('queryEntities with predicate-based queries (POST endpoint)', () => {
+ const defaultResponse = {
+ items: [
+ {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ name: 'service-1',
+ namespace: 'default',
+ },
+ spec: {
+ type: 'service',
+ owner: 'team-a',
+ },
+ },
+ {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ name: 'service-2',
+ namespace: 'default',
+ },
+ spec: {
+ type: 'service',
+ owner: 'team-b',
+ },
+ },
+ ],
+ totalItems: 2,
+ pageInfo: {},
+ };
+
+ it('should use POST endpoint when query is provided', async () => {
+ const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
+ expect(req.method).toBe('POST');
+ expect(req.body).toMatchObject({
+ query: { kind: 'component' },
+ limit: 20,
+ });
+ return res(ctx.json(defaultResponse));
+ });
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ const response = await client.queryEntities({
+ query: { kind: 'component' },
+ limit: 20,
+ });
+
+ expect(mockedEndpoint).toHaveBeenCalledTimes(1);
+ expect(response.items).toEqual(defaultResponse.items);
+ expect(response.totalItems).toBe(2);
+ });
+
+ it('should support $all operator', async () => {
+ const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
+ expect(req.body).toMatchObject({
+ query: {
+ $all: [{ kind: 'component' }, { 'spec.type': 'service' }],
+ },
+ });
+ return res(ctx.json(defaultResponse));
+ });
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ await client.queryEntities({
+ query: {
+ $all: [{ kind: 'component' }, { 'spec.type': 'service' }],
+ },
+ });
+
+ expect(mockedEndpoint).toHaveBeenCalledTimes(1);
+ });
+
+ it('should support $any operator', async () => {
+ const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
+ expect(req.body).toMatchObject({
+ query: {
+ $any: [{ 'spec.type': 'service' }, { 'spec.type': 'website' }],
+ },
+ });
+ return res(ctx.json(defaultResponse));
+ });
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ await client.queryEntities({
+ query: {
+ $any: [{ 'spec.type': 'service' }, { 'spec.type': 'website' }],
+ },
+ });
+
+ expect(mockedEndpoint).toHaveBeenCalledTimes(1);
+ });
+
+ it('should support $not operator', async () => {
+ const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
+ expect(req.body).toMatchObject({
+ query: {
+ $not: { 'spec.lifecycle': 'experimental' },
+ },
+ });
+ return res(ctx.json(defaultResponse));
+ });
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ await client.queryEntities({
+ query: {
+ $not: { 'spec.lifecycle': 'experimental' },
+ },
+ });
+
+ expect(mockedEndpoint).toHaveBeenCalledTimes(1);
+ });
+
+ it('should support $exists operator', async () => {
+ const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
+ expect(req.body).toMatchObject({
+ query: {
+ 'spec.owner': { $exists: true },
+ },
+ });
+ return res(ctx.json(defaultResponse));
+ });
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ await client.queryEntities({
+ query: {
+ 'spec.owner': { $exists: true },
+ },
+ });
+
+ expect(mockedEndpoint).toHaveBeenCalledTimes(1);
+ });
+
+ it('should support $in operator', async () => {
+ const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
+ expect(req.body).toMatchObject({
+ query: {
+ 'spec.owner': { $in: ['team-a', 'team-b', 'team-c'] },
+ },
+ });
+ return res(ctx.json(defaultResponse));
+ });
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ await client.queryEntities({
+ query: {
+ 'spec.owner': { $in: ['team-a', 'team-b', 'team-c'] },
+ },
+ });
+
+ expect(mockedEndpoint).toHaveBeenCalledTimes(1);
+ });
+
+ it('should support complex nested predicates', async () => {
+ const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
+ expect(req.body).toMatchObject({
+ query: {
+ $all: [
+ { kind: 'component' },
+ {
+ $any: [{ 'spec.type': 'service' }, { 'spec.type': 'website' }],
+ },
+ {
+ $not: {
+ 'spec.lifecycle': 'experimental',
+ },
+ },
+ ],
+ },
+ });
+ return res(ctx.json(defaultResponse));
+ });
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ await client.queryEntities({
+ query: {
+ $all: [
+ { kind: 'component' },
+ {
+ $any: [{ 'spec.type': 'service' }, { 'spec.type': 'website' }],
+ },
+ {
+ $not: {
+ 'spec.lifecycle': 'experimental',
+ },
+ },
+ ],
+ },
+ });
+
+ expect(mockedEndpoint).toHaveBeenCalledTimes(1);
+ });
+
+ it('should send orderFields with correct format', async () => {
+ const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
+ expect(req.body.orderBy).toEqual([
+ { field: 'metadata.name', order: 'asc' },
+ ]);
+ return res(ctx.json(defaultResponse));
+ });
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ await client.queryEntities({
+ query: { kind: 'component' },
+ orderFields: { field: 'metadata.name', order: 'asc' },
+ });
+
+ expect(mockedEndpoint).toHaveBeenCalledTimes(1);
+ });
+
+ it('should send multiple orderFields with correct format', async () => {
+ const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
+ expect(req.body.orderBy).toEqual([
+ { field: 'metadata.name', order: 'asc' },
+ { field: 'spec.type', order: 'desc' },
+ ]);
+ return res(ctx.json(defaultResponse));
+ });
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ await client.queryEntities({
+ query: { kind: 'component' },
+ orderFields: [
+ { field: 'metadata.name', order: 'asc' },
+ { field: 'spec.type', order: 'desc' },
+ ],
+ });
+
+ expect(mockedEndpoint).toHaveBeenCalledTimes(1);
+ });
+
+ it('should send limit and offset parameters in the body', async () => {
+ const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
+ expect(req.body.limit).toBe(50);
+ return res(ctx.json(defaultResponse));
+ });
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ await client.queryEntities({
+ query: { kind: 'component' },
+ limit: 50,
+ });
+
+ expect(mockedEndpoint).toHaveBeenCalledTimes(1);
+ });
+
+ it('should paginate using POST when cursor contains a query', async () => {
+ // Simulate a cursor that contains a query predicate (as the server would encode it)
+ const cursorPayload = Buffer.from(
+ JSON.stringify({
+ orderFields: [],
+ orderFieldValues: [],
+ isPrevious: false,
+ query: { kind: 'component' },
+ totalItems: 100,
+ }),
+ ).toString('base64');
+
+ const page2Response = {
+ items: [
+ {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: { name: 'service-3', namespace: 'default' },
+ },
+ ],
+ totalItems: 100,
+ pageInfo: {},
+ };
+
+ const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
+ expect(req.method).toBe('POST');
+ expect(req.body).toMatchObject({ cursor: cursorPayload });
+ return res(ctx.json(page2Response));
+ });
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ const response = await client.queryEntities({
+ cursor: cursorPayload,
+ });
+
+ expect(mockedEndpoint).toHaveBeenCalledTimes(1);
+ expect(response.items).toEqual(page2Response.items);
+ expect(response.totalItems).toBe(100);
+ });
+
+ it('should use GET endpoint for cursor without query', async () => {
+ // A cursor that does NOT contain a query field should go to GET
+ const cursorPayload = Buffer.from(
+ JSON.stringify({
+ orderFields: [],
+ orderFieldValues: [],
+ isPrevious: false,
+ totalItems: 50,
+ }),
+ ).toString('base64');
+
+ const mockedGetEndpoint = jest.fn().mockImplementation((_req, res, ctx) =>
+ res(
+ ctx.json({
+ items: [],
+ totalItems: 50,
+ pageInfo: {},
+ }),
+ ),
+ );
+
+ const mockedPostEndpoint = jest.fn();
+
+ server.use(
+ rest.get(`${mockBaseUrl}/entities/by-query`, mockedGetEndpoint),
+ rest.post(`${mockBaseUrl}/entities/by-query`, mockedPostEndpoint),
+ );
+
+ await client.queryEntities({ cursor: cursorPayload });
+
+ expect(mockedGetEndpoint).toHaveBeenCalledTimes(1);
+ expect(mockedPostEndpoint).not.toHaveBeenCalled();
+ });
+
+ it('should handle errors from POST endpoint', async () => {
+ const mockedEndpoint = jest
+ .fn()
+ .mockImplementation((_req, res, ctx) => res(ctx.status(400)));
+
+ server.use(rest.post(`${mockBaseUrl}/entities/by-query`, mockedEndpoint));
+
+ await expect(() =>
+ client.queryEntities({ query: { kind: 'component' } }),
+ ).rejects.toThrow(/Request failed with 400/);
+ });
+ });
+
describe('streamEntities', () => {
const defaultResponse: QueryEntitiesResponse = {
items: [
diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts
index b45bef8bbd..cf363e1823 100644
--- a/packages/catalog-client/src/CatalogClient.ts
+++ b/packages/catalog-client/src/CatalogClient.ts
@@ -20,7 +20,8 @@ import {
parseEntityRef,
stringifyLocationRef,
} from '@backstage/catalog-model';
-import { ResponseError } from '@backstage/errors';
+import { InputError, ResponseError } from '@backstage/errors';
+import { FilterPredicate } from '@backstage/filter-predicates';
import {
AddLocationRequest,
AddLocationResponse,
@@ -46,10 +47,17 @@ import {
StreamEntitiesRequest,
ValidateEntityResponse,
} from './types/api';
-import { isQueryEntitiesInitialRequest, splitRefsIntoChunks } from './utils';
+import {
+ convertFilterToPredicate,
+ isQueryEntitiesInitialRequest,
+ splitRefsIntoChunks,
+ cursorContainsQuery,
+} from './utils';
import {
DefaultApiClient,
+ GetEntitiesByQuery,
GetLocationsByQueryRequest,
+ QueryEntitiesByPredicateRequest,
TypedResponse,
} from './schema/openapi';
import type {
@@ -229,11 +237,36 @@ export class CatalogClient implements CatalogApi {
request: GetEntitiesByRefsRequest,
options?: CatalogRequestOptions,
): Promise {
+ const { filter, query } = request;
+
+ // Only convert and merge if both filter and query are provided, or if
+ // query alone is provided. When only filter is given, preserve the old
+ // query-parameter behavior for backward compatibility.
+ let filterPredicate: FilterPredicate | undefined;
+ if (query !== undefined) {
+ if (typeof query !== 'object' || query === null || Array.isArray(query)) {
+ throw new InputError('Query must be an object');
+ }
+ filterPredicate = query;
+ if (filter !== undefined) {
+ const converted = convertFilterToPredicate(filter);
+ filterPredicate = { $all: [filterPredicate, converted] };
+ }
+ }
+
const getOneChunk = async (refs: string[]) => {
const response = await this.apiClient.getEntitiesByRefs(
{
- body: { entityRefs: refs, fields: request.fields },
- query: { filter: this.getFilterValue(request.filter) },
+ body: {
+ entityRefs: refs,
+ fields: request.fields,
+ ...(filterPredicate && {
+ query: filterPredicate as unknown as { [key: string]: any },
+ }),
+ },
+ query: filterPredicate
+ ? {}
+ : { filter: this.getFilterValue(request.filter) },
},
options,
);
@@ -266,11 +299,26 @@ export class CatalogClient implements CatalogApi {
request: QueryEntitiesRequest = {},
options?: CatalogRequestOptions,
): Promise {
- const params: Partial<
- Parameters[0]['query']
- > = {};
+ const isInitialRequest = isQueryEntitiesInitialRequest(request);
- if (isQueryEntitiesInitialRequest(request)) {
+ // Route to POST endpoint if query predicate is provided (initial request)
+ if (isInitialRequest && request.query) {
+ return this.queryEntitiesByPredicate(request, options);
+ }
+
+ // Route to POST endpoint if cursor contains a query predicate (pagination)
+ // TODO(freben): It's costly and non-opaque to have to introspect the cursor
+ // like this. It should be refactored in the future to not need this.
+ // Suggestion: make the GET and POST endpoints understand the same cursor
+ // format, and pick which one to call ONLY based on whether the cursor size
+ // risks hitting url length limits
+ if (!isInitialRequest && cursorContainsQuery(request.cursor)) {
+ return this.queryEntitiesByPredicate(request, options);
+ }
+
+ const params: Partial = {};
+
+ if (isInitialRequest) {
const {
fields = [],
filter,
@@ -320,6 +368,84 @@ export class CatalogClient implements CatalogApi {
);
}
+ /**
+ * Query entities using predicate-based filters (POST endpoint).
+ * @internal
+ */
+ private async queryEntitiesByPredicate(
+ request: QueryEntitiesRequest,
+ options?: CatalogRequestOptions,
+ ): Promise {
+ const body: QueryEntitiesByPredicateRequest = {};
+
+ if (isQueryEntitiesInitialRequest(request)) {
+ const {
+ filter,
+ query,
+ limit,
+ offset,
+ orderFields,
+ fullTextFilter,
+ fields,
+ } = request;
+
+ let filterPredicate: FilterPredicate | undefined;
+ if (query !== undefined) {
+ if (
+ typeof query !== 'object' ||
+ query === null ||
+ Array.isArray(query)
+ ) {
+ throw new InputError('Query must be an object');
+ }
+ filterPredicate = query;
+ }
+ if (filter !== undefined) {
+ const converted = convertFilterToPredicate(filter);
+ filterPredicate = filterPredicate
+ ? { $all: [filterPredicate, converted] }
+ : converted;
+ }
+ if (filterPredicate !== undefined) {
+ body.query = filterPredicate as unknown as { [key: string]: any };
+ }
+
+ if (limit !== undefined) {
+ body.limit = limit;
+ }
+ if (offset !== undefined) {
+ body.offset = offset;
+ }
+ if (orderFields !== undefined) {
+ body.orderBy = [orderFields].flat();
+ }
+ if (fullTextFilter) {
+ body.fullTextFilter = fullTextFilter;
+ }
+ if (fields?.length) {
+ body.fields = fields;
+ }
+ } else {
+ body.cursor = request.cursor;
+ if (request.limit !== undefined) {
+ body.limit = request.limit;
+ }
+ if (request.fields?.length) {
+ body.fields = request.fields;
+ }
+ }
+
+ const res = await this.requestRequired(
+ await this.apiClient.queryEntitiesByPredicate({ body }, options),
+ );
+
+ return {
+ items: res.items,
+ totalItems: res.totalItems,
+ pageInfo: res.pageInfo,
+ };
+ }
+
/**
* {@inheritdoc CatalogApi.getEntityByRef}
*/
@@ -378,7 +504,13 @@ export class CatalogClient implements CatalogApi {
request: GetEntityFacetsRequest,
options?: CatalogRequestOptions,
): Promise {
- const { filter = [], facets } = request;
+ const { filter = [], query, facets } = request;
+
+ // Route to POST endpoint if query predicate is provided
+ if (query) {
+ return this.getEntityFacetsByPredicate(request, options);
+ }
+
return await this.requestOptional(
await this.apiClient.getEntityFacets(
{
@@ -389,6 +521,45 @@ export class CatalogClient implements CatalogApi {
);
}
+ /**
+ * Get entity facets using predicate-based filters (POST endpoint).
+ * @internal
+ */
+ private async getEntityFacetsByPredicate(
+ request: GetEntityFacetsRequest,
+ options?: CatalogRequestOptions,
+ ): Promise {
+ const { filter, query, facets } = request;
+
+ let filterPredicate: FilterPredicate | undefined;
+ if (query !== undefined) {
+ if (typeof query !== 'object' || query === null || Array.isArray(query)) {
+ throw new InputError('Query must be an object');
+ }
+ filterPredicate = query;
+ }
+ if (filter !== undefined) {
+ const converted = convertFilterToPredicate(filter);
+ filterPredicate = filterPredicate
+ ? { $all: [filterPredicate, converted] }
+ : converted;
+ }
+
+ return await this.requestOptional(
+ await this.apiClient.queryEntityFacetsByPredicate(
+ {
+ body: {
+ facets,
+ ...(filterPredicate && {
+ query: filterPredicate as unknown as { [key: string]: any },
+ }),
+ },
+ },
+ options,
+ ),
+ );
+ }
+
/**
* {@inheritdoc CatalogApi.addLocation}
*/
@@ -396,12 +567,15 @@ export class CatalogClient implements CatalogApi {
request: AddLocationRequest,
options?: CatalogRequestOptions,
): Promise {
- const { type = 'url', target, dryRun } = request;
+ const { type = 'url', target, dryRun, onConflict } = request;
const response = await this.apiClient.createLocation(
{
body: { type, target },
- query: { dryRun: dryRun ? 'true' : undefined },
+ query: {
+ dryRun: dryRun ? 'true' : undefined,
+ onConflict,
+ },
},
options,
);
diff --git a/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts b/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts
index 4530cd795c..4b3e088fe2 100644
--- a/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts
@@ -29,6 +29,8 @@ import { Entity } from '../models/Entity.model';
import { EntityAncestryResponse } from '../models/EntityAncestryResponse.model';
import { EntityFacetsResponse } from '../models/EntityFacetsResponse.model';
import { GetEntitiesByRefsRequest } from '../models/GetEntitiesByRefsRequest.model';
+import { QueryEntitiesByPredicateRequest } from '../models/QueryEntitiesByPredicateRequest.model';
+import { QueryEntityFacetsByPredicateRequest } from '../models/QueryEntityFacetsByPredicateRequest.model';
import { RefreshEntityRequest } from '../models/RefreshEntityRequest.model';
import { ValidateEntityRequest } from '../models/ValidateEntityRequest.model';
import { AnalyzeLocationRequest } from '../models/AnalyzeLocationRequest.model';
@@ -139,6 +141,18 @@ export type GetEntityFacets = {
filter?: Array;
};
};
+/**
+ * @public
+ */
+export type QueryEntitiesByPredicate = {
+ body: QueryEntitiesByPredicateRequest;
+};
+/**
+ * @public
+ */
+export type QueryEntityFacetsByPredicate = {
+ body: QueryEntityFacetsByPredicateRequest;
+};
/**
* @public
*/
@@ -164,6 +178,7 @@ export type CreateLocation = {
body: CreateLocationRequest;
query: {
dryRun?: string;
+ onConflict?: 'refresh' | 'reject';
};
};
/**
@@ -246,9 +261,9 @@ export class DefaultApiClient {
/**
* Get all entities matching a given filter.
- * @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it'll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name`
+ * @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it\'ll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name`
* @param limit - Number of records to return in the response.
- * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
+ * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let\'s look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
* @param offset - Number of records to skip in the query page.
* @param after - Pointer to the previous page of results.
* @param order -
@@ -277,12 +292,12 @@ export class DefaultApiClient {
/**
* Search for entities by a given query.
- * @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it'll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name`
+ * @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it\'ll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name`
* @param limit - Number of records to return in the response.
* @param offset - Number of records to skip in the query page.
* @param orderField - By default the entities are returned ordered by their internal uid. You can customize the `orderField` query parameters to affect that ordering. For example, to return entities by their name: `/entities/by-query?orderField=metadata.name,asc` Each parameter can be followed by `asc` for ascending lexicographical order or `desc` for descending (reverse) lexicographical order.
- * @param cursor - You may pass the `cursor` query parameters to perform cursor based pagination through the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property: ```json \"pageInfo\": { \"nextCursor\": \"a-cursor\", \"prevCursor\": \"another-cursor\" } ``` If `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach, if `prevCursor` exists, it can be used to retrieve the previous batch of entities. - [`filter`](#filtering), for selecting only a subset of all entities - [`fields`](#field-selection), for selecting only parts of the full data structure of each entity - `limit` for limiting the number of entities returned (20 is the default) - [`orderField`](#ordering), for deciding the order of the entities - `fullTextFilter` **NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that, it isn't possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters, as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration.
- * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
+ * @param cursor - You may pass the `cursor` query parameters to perform cursor based pagination through the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property: ```json \"pageInfo\": { \"nextCursor\": \"a-cursor\", \"prevCursor\": \"another-cursor\" } ``` If `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach, if `prevCursor` exists, it can be used to retrieve the previous batch of entities. - [`filter`](#filtering), for selecting only a subset of all entities - [`fields`](#field-selection), for selecting only parts of the full data structure of each entity - `limit` for limiting the number of entities returned (20 is the default) - [`orderField`](#ordering), for deciding the order of the entities - `fullTextFilter` **NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that, it isn\'t possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters, as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration.
+ * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let\'s look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
* @param fullTextFilterTerm - Text search term.
* @param fullTextFilterFields - A comma separated list of fields to sort returned results by.
*/
@@ -310,7 +325,7 @@ export class DefaultApiClient {
/**
* Get a batch set of entities given an array of entityRefs.
- * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
+ * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let\'s look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
* @param getEntitiesByRefsRequest -
*/
public async getEntitiesByRefs(
@@ -337,7 +352,7 @@ export class DefaultApiClient {
}
/**
- * Get an entity's ancestry by entity ref.
+ * Get an entity\'s ancestry by entity ref.
* @param kind -
* @param namespace -
* @param name -
@@ -425,7 +440,7 @@ export class DefaultApiClient {
/**
* Get all entity facets that match the given filters.
* @param facet -
- * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
+ * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let\'s look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
*/
public async getEntityFacets(
// @ts-ignore
@@ -449,6 +464,56 @@ export class DefaultApiClient {
});
}
+ /**
+ * Query entities using predicate-based filters.
+ * @param queryEntitiesByPredicateRequest -
+ */
+ public async queryEntitiesByPredicate(
+ // @ts-ignore
+ request: QueryEntitiesByPredicate,
+ options?: RequestOptions,
+ ): Promise> {
+ const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
+
+ const uriTemplate = `/entities/by-query`;
+
+ const uri = parser.parse(uriTemplate).expand({});
+
+ return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(options?.token && { Authorization: `Bearer ${options?.token}` }),
+ },
+ method: 'POST',
+ body: JSON.stringify(request.body),
+ });
+ }
+
+ /**
+ * Get entity facets using predicate-based filters.
+ * @param queryEntityFacetsByPredicateRequest -
+ */
+ public async queryEntityFacetsByPredicate(
+ // @ts-ignore
+ request: QueryEntityFacetsByPredicate,
+ options?: RequestOptions,
+ ): Promise> {
+ const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
+
+ const uriTemplate = `/entity-facets`;
+
+ const uri = parser.parse(uriTemplate).expand({});
+
+ return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(options?.token && { Authorization: `Bearer ${options?.token}` }),
+ },
+ method: 'POST',
+ body: JSON.stringify(request.body),
+ });
+ }
+
/**
* Refresh the entity related to entityRef.
* @param refreshEntityRequest -
@@ -528,6 +593,7 @@ export class DefaultApiClient {
* Create a location for a given target.
* @param createLocationRequest -
* @param dryRun -
+ * @param onConflict - Behavior when the location already exists. \'reject\' (default) returns a 409 error, \'refresh\' triggers a refresh of the existing location entity and returns 201.
*/
public async createLocation(
// @ts-ignore
@@ -536,7 +602,7 @@ export class DefaultApiClient {
): Promise> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
- const uriTemplate = `/locations{?dryRun}`;
+ const uriTemplate = `/locations{?dryRun,onConflict}`;
const uri = parser.parse(uriTemplate).expand({
...request.query,
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts
index ea3d097ec5..06998f5422 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts
@@ -17,12 +17,11 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { Entity } from '../models/Entity.model';
import { LocationSpec } from '../models/LocationSpec.model';
/**
- * If the folder pointed to already contained catalog info yaml files, they are read and emitted like this so that the frontend can inform the user that it located them and can make sure to register them as well if they weren't already
+ * If the folder pointed to already contained catalog info yaml files, they are read and emitted like this so that the frontend can inform the user that it located them and can make sure to register them as well if they weren\'t already
* @public
*/
export interface AnalyzeLocationExistingEntity {
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts
index 2999da3ddf..7b2ba72114 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts
@@ -17,12 +17,11 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { AnalyzeLocationEntityField } from '../models/AnalyzeLocationEntityField.model';
import { RecursivePartialEntity } from '../models/RecursivePartialEntity.model';
/**
- * This is some form of representation of what the analyzer could deduce. We should probably have a chat about how this can best be conveyed to the frontend. It'll probably contain a (possibly incomplete) entity, plus enough info for the frontend to know what form data to show to the user for overriding/completing the info.
+ * This is some form of representation of what the analyzer could deduce. We should probably have a chat about how this can best be conveyed to the frontend. It\'ll probably contain a (possibly incomplete) entity, plus enough info for the frontend to know what form data to show to the user for overriding/completing the info.
* @public
*/
export interface AnalyzeLocationGenerateEntity {
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts
index 5099a5c8aa..a0a5615f25 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { LocationInput } from '../models/LocationInput.model';
/**
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts
index a5eac33f7c..d4d8a0fdd9 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { AnalyzeLocationExistingEntity } from '../models/AnalyzeLocationExistingEntity.model';
import { AnalyzeLocationGenerateEntity } from '../models/AnalyzeLocationGenerateEntity.model';
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/CreateLocation201Response.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/CreateLocation201Response.model.ts
index 610e565b3e..e224386de4 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/CreateLocation201Response.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/CreateLocation201Response.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { Entity } from '../models/Entity.model';
import { Location } from '../models/Location.model';
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts
index e8bf79a501..0434965888 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { NullableEntity } from '../models/NullableEntity.model';
/**
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts
index d201466db6..5165faec58 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { EntitiesQueryResponsePageInfo } from '../models/EntitiesQueryResponsePageInfo.model';
import { Entity } from '../models/Entity.model';
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/Entity.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/Entity.model.ts
index 108a7b15b8..00cc91e89b 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/Entity.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/Entity.model.ts
@@ -17,12 +17,11 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { EntityMeta } from '../models/EntityMeta.model';
import { EntityRelation } from '../models/EntityRelation.model';
/**
- * The parts of the format that's common to all versions/kinds of entity.
+ * The parts of the format that\'s common to all versions/kinds of entity.
* @public
*/
export interface Entity {
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts
index f2c1116f35..9d1e4d9052 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { EntityAncestryResponseItemsInner } from '../models/EntityAncestryResponseItemsInner.model';
/**
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts
index 9c816e5517..0936545ec7 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { Entity } from '../models/Entity.model';
/**
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts
index 5cc67294e5..540650bcaf 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { EntityFacet } from '../models/EntityFacet.model';
/**
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityMeta.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityMeta.model.ts
index c1e1471045..a35ee87b61 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/EntityMeta.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityMeta.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { EntityLink } from '../models/EntityLink.model';
/**
@@ -26,7 +25,6 @@ import { EntityLink } from '../models/EntityLink.model';
*/
export interface EntityMeta {
[key: string]: any;
-
/**
* A list of external hyperlinks related to the entity.
*/
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts
index 0e7255f9c6..607c992ace 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts
@@ -24,4 +24,8 @@
export interface GetEntitiesByRefsRequest {
entityRefs: Array;
fields?: Array;
+ /**
+ * A type representing all allowed JSON object values.
+ */
+ query?: { [key: string]: any };
}
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts
index b77b78971f..846b58ba97 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { Location } from '../models/Location.model';
/**
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/LocationsQueryResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/LocationsQueryResponse.model.ts
index c231fccc57..05259caf32 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/LocationsQueryResponse.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/LocationsQueryResponse.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { Location } from '../models/Location.model';
import { LocationsQueryResponsePageInfo } from '../models/LocationsQueryResponsePageInfo.model';
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/ModelError.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/ModelError.model.ts
index 207f16f828..9d5c36325e 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/ModelError.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/ModelError.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { ErrorError } from '../models/ErrorError.model';
import { ErrorRequest } from '../models/ErrorRequest.model';
import { ErrorResponse } from '../models/ErrorResponse.model';
@@ -27,7 +26,6 @@ import { ErrorResponse } from '../models/ErrorResponse.model';
*/
export interface ModelError {
[key: string]: any;
-
error: ErrorError;
request?: ErrorRequest;
response: ErrorResponse;
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/NullableEntity.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/NullableEntity.model.ts
index 5e59af3327..288b6febb6 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/NullableEntity.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/NullableEntity.model.ts
@@ -17,12 +17,11 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { EntityMeta } from '../models/EntityMeta.model';
import { EntityRelation } from '../models/EntityRelation.model';
/**
- * The parts of the format that's common to all versions/kinds of entity.
+ * The parts of the format that\'s common to all versions/kinds of entity.
* @public
*/
export type NullableEntity = {
diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOf.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequest.model.ts
similarity index 54%
rename from plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOf.model.ts
rename to packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequest.model.ts
index 5b6c2ff9c7..7196156f55 100644
--- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOf.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequest.model.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,13 +17,21 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-import { DryRun200ResponseAllOfDirectoryContentsInner } from '../models/DryRun200ResponseAllOfDirectoryContentsInner.model';
-import { DryRun200ResponseAllOfStepsInner } from '../models/DryRun200ResponseAllOfStepsInner.model';
+import { QueryEntitiesByPredicateRequestFullTextFilter } from '../models/QueryEntitiesByPredicateRequestFullTextFilter.model';
+import { QueryEntitiesByPredicateRequestOrderByInner } from '../models/QueryEntitiesByPredicateRequestOrderByInner.model';
/**
* @public
*/
-export interface DryRun200ResponseAllOf {
- steps: Array;
- directoryContents?: Array;
+export interface QueryEntitiesByPredicateRequest {
+ cursor?: string;
+ limit?: number;
+ offset?: number;
+ orderBy?: Array;
+ fullTextFilter?: QueryEntitiesByPredicateRequestFullTextFilter;
+ fields?: Array;
+ /**
+ * A type representing all allowed JSON object values.
+ */
+ query?: { [key: string]: any };
}
diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecretsAllOf.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequestFullTextFilter.model.ts
similarity index 84%
rename from plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecretsAllOf.model.ts
rename to packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequestFullTextFilter.model.ts
index 0b33852cc9..e7e971e1ea 100644
--- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecretsAllOf.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequestFullTextFilter.model.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
/**
* @public
*/
-export interface TaskSecretsAllOf {
- backstageToken?: string;
+export interface QueryEntitiesByPredicateRequestFullTextFilter {
+ term?: string;
+ fields?: Array;
}
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequestOrderByInner.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequestOrderByInner.model.ts
new file mode 100644
index 0000000000..373ca9715f
--- /dev/null
+++ b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequestOrderByInner.model.ts
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2026 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// ******************************************************************
+// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
+// ******************************************************************
+
+/**
+ * @public
+ */
+export interface QueryEntitiesByPredicateRequestOrderByInner {
+ field: string;
+ order: QueryEntitiesByPredicateRequestOrderByInnerOrderEnum;
+}
+
+/**
+ * @public
+ */
+export type QueryEntitiesByPredicateRequestOrderByInnerOrderEnum =
+ | 'asc'
+ | 'desc';
diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBodyAllOf.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntityFacetsByPredicateRequest.model.ts
similarity index 78%
rename from plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBodyAllOf.model.ts
rename to packages/catalog-client/src/schema/openapi/generated/models/QueryEntityFacetsByPredicateRequest.model.ts
index e67240ee54..14fd3a8bc0 100644
--- a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBodyAllOf.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntityFacetsByPredicateRequest.model.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,13 +17,14 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-import { TaskStatus } from '../models/TaskStatus.model';
/**
* @public
*/
-export interface DryRunResultLogInnerBodyAllOf {
- message: string;
- status?: TaskStatus;
- stepId?: string;
+export interface QueryEntityFacetsByPredicateRequest {
+ facets: Array;
+ /**
+ * A type representing all allowed JSON object values.
+ */
+ query?: { [key: string]: any };
}
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts
index 3f09c69672..3a607dd878 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { RecursivePartialEntityMeta } from '../models/RecursivePartialEntityMeta.model';
import { RecursivePartialEntityRelation } from '../models/RecursivePartialEntityRelation.model';
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts
index 2db84dd085..7c2d3bed48 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { EntityLink } from '../models/EntityLink.model';
/**
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMetaAllOf.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMetaAllOf.model.ts
deleted file mode 100644
index caca69a622..0000000000
--- a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMetaAllOf.model.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2026 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// ******************************************************************
-// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
-// ******************************************************************
-
-import { EntityLink } from '../models/EntityLink.model';
-
-/**
- * Metadata fields common to all versions/kinds of entity.
- * @public
- */
-export interface RecursivePartialEntityMetaAllOf {
- /**
- * A list of external hyperlinks related to the entity.
- */
- links?: Array;
- /**
- * A list of single-valued strings, to for example classify catalog entities in various ways.
- */
- tags?: Array;
- /**
- * Construct a type with a set of properties K of type T
- */
- annotations?: { [key: string]: string };
- /**
- * Construct a type with a set of properties K of type T
- */
- labels?: { [key: string]: string };
- /**
- * A short (typically relatively few words, on one line) description of the entity.
- */
- description?: string;
- /**
- * A display name of the entity, to be presented in user interfaces instead of the `name` property above, when available. This field is sometimes useful when the `name` is cumbersome or ends up being perceived as overly technical. The title generally does not have as stringent format requirements on it, so it may contain special characters and be more explanatory. Do keep it very short though, and avoid situations where a title can be confused with the name of another entity, or where two entities share a title. Note that this is only for display purposes, and may be ignored by some parts of the code. Entity references still always make use of the `name` property, not the title.
- */
- title?: string;
- /**
- * The namespace that the entity belongs to.
- */
- namespace?: string;
- /**
- * The name of the entity. Must be unique within the catalog at any given point in time, for any given namespace + kind pair. This value is part of the technical identifier of the entity, and as such it will appear in URLs, database tables, entity references, and similar. It is subject to restrictions regarding what characters are allowed. If you want to use a different, more human readable string with fewer restrictions on it in user interfaces, see the `title` field below.
- */
- name?: string;
- /**
- * An opaque string that changes for each update operation to any part of the entity, including metadata. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, and the server will then reject the operation if it does not match the current stored value.
- */
- etag?: string;
- /**
- * A globally unique ID for the entity. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, but the server is free to reject requests that do so in such a way that it breaks semantics.
- */
- uid?: string;
-}
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts
index aabf825a57..2a41f2df5c 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts
@@ -17,7 +17,6 @@
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
-
import { ValidateEntity400ResponseErrorsInner } from '../models/ValidateEntity400ResponseErrorsInner.model';
/**
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts
index 4582bf2046..f8a7ae4386 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts
@@ -23,7 +23,6 @@
*/
export interface ValidateEntity400ResponseErrorsInner {
[key: string]: any;
-
name: string;
message: string;
}
diff --git a/packages/catalog-client/src/schema/openapi/generated/models/index.ts b/packages/catalog-client/src/schema/openapi/generated/models/index.ts
index abdb58378c..aaafc8e60e 100644
--- a/packages/catalog-client/src/schema/openapi/generated/models/index.ts
+++ b/packages/catalog-client/src/schema/openapi/generated/models/index.ts
@@ -45,9 +45,12 @@ export * from '../models/LocationsQueryResponse.model';
export * from '../models/LocationsQueryResponsePageInfo.model';
export * from '../models/ModelError.model';
export * from '../models/NullableEntity.model';
+export * from '../models/QueryEntitiesByPredicateRequest.model';
+export * from '../models/QueryEntitiesByPredicateRequestFullTextFilter.model';
+export * from '../models/QueryEntitiesByPredicateRequestOrderByInner.model';
+export * from '../models/QueryEntityFacetsByPredicateRequest.model';
export * from '../models/RecursivePartialEntity.model';
export * from '../models/RecursivePartialEntityMeta.model';
-export * from '../models/RecursivePartialEntityMetaAllOf.model';
export * from '../models/RecursivePartialEntityRelation.model';
export * from '../models/RefreshEntityRequest.model';
export * from '../models/ValidateEntity400Response.model';
diff --git a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.test.ts b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.test.ts
index d867ab1d28..98b5d5c034 100644
--- a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.test.ts
+++ b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.test.ts
@@ -370,6 +370,25 @@ describe('InMemoryCatalogClient', () => {
{ kind: 'CustomKind', metadata: { name: 'e1' } },
]);
});
+
+ it('supports query predicate filter', async () => {
+ const client = new InMemoryCatalogClient({ entities });
+ const result = await client.getEntitiesByRefs({
+ entityRefs: ['secondcustomkind:default/e2', 'customkind:default/e1'],
+ query: { kind: 'CustomKind' },
+ });
+ expect(result.items).toEqual([undefined, entity1]);
+ });
+
+ it('supports both filter and query predicate together', async () => {
+ const client = new InMemoryCatalogClient({ entities });
+ const result = await client.getEntitiesByRefs({
+ entityRefs: ['customkind:default/e1', 'customkind:other/e3'],
+ filter: { kind: 'CustomKind' },
+ query: { 'metadata.namespace': 'other' },
+ });
+ expect(result.items).toEqual([undefined, entity3]);
+ });
});
describe('queryEntities', () => {
@@ -683,6 +702,83 @@ describe('InMemoryCatalogClient', () => {
]);
});
+ it('filters by predicate query', async () => {
+ const client = new InMemoryCatalogClient({ entities });
+ const result = await client.queryEntities({
+ query: { kind: 'CustomKind' },
+ });
+ expect(result.items).toEqual([entity1, entity3]);
+ expect(result.totalItems).toBe(2);
+ });
+
+ it('filters by predicate query with $all', async () => {
+ const client = new InMemoryCatalogClient({ entities });
+ const result = await client.queryEntities({
+ query: {
+ $all: [{ kind: 'CustomKind' }, { 'spec.type': 'service' }],
+ },
+ });
+ expect(result.items).toEqual([entity1, entity3]);
+ });
+
+ it('filters by predicate query with $any', async () => {
+ const client = new InMemoryCatalogClient({ entities });
+ const result = await client.queryEntities({
+ query: {
+ $any: [{ 'spec.type': 'service' }, { 'spec.type': 'website' }],
+ },
+ });
+ expect(result.items).toEqual([entity1, entity3, entity4]);
+ });
+
+ it('filters by predicate query with $not', async () => {
+ const client = new InMemoryCatalogClient({ entities });
+ const result = await client.queryEntities({
+ query: {
+ $all: [
+ { kind: 'CustomKind' },
+ { $not: { 'spec.lifecycle': 'production' } },
+ ],
+ },
+ });
+ expect(result.items).toEqual([]);
+ });
+
+ it('filters by predicate query with $in', async () => {
+ const client = new InMemoryCatalogClient({ entities });
+ const result = await client.queryEntities({
+ query: { 'spec.type': { $in: ['service', 'library'] } },
+ });
+ expect(result.items).toEqual([entity1, entity2, entity3]);
+ });
+
+ it('filters by predicate query with $exists', async () => {
+ const client = new InMemoryCatalogClient({ entities });
+ const result = await client.queryEntities({
+ query: { 'spec.lifecycle': { $exists: false } },
+ });
+ expect(result.items).toEqual([entity4]);
+ });
+
+ it('preserves query predicate through cursor pagination', async () => {
+ const client = new InMemoryCatalogClient({ entities });
+ const page1 = await client.queryEntities({
+ query: { kind: 'CustomKind' },
+ orderFields: { field: 'metadata.name', order: 'asc' },
+ limit: 1,
+ });
+ expect(page1.items.map(e => e.metadata.name)).toEqual(['e1']);
+ expect(page1.totalItems).toBe(2);
+ expect(page1.pageInfo.nextCursor).toBeDefined();
+
+ const page2 = await client.queryEntities({
+ cursor: page1.pageInfo.nextCursor!,
+ limit: 1,
+ });
+ expect(page2.items.map(e => e.metadata.name)).toEqual(['e3']);
+ expect(page2.pageInfo.nextCursor).toBeUndefined();
+ });
+
it('throws InputError for invalid cursor', async () => {
const client = new InMemoryCatalogClient({ entities });
await expect(
@@ -898,6 +994,29 @@ describe('InMemoryCatalogClient', () => {
});
expect(result.facets['spec.nonexistent']).toEqual([]);
});
+
+ it('supports query predicate filter', async () => {
+ const client = new InMemoryCatalogClient({ entities });
+ const result = await client.getEntityFacets({
+ facets: ['spec.type'],
+ query: { kind: 'CustomKind' },
+ });
+ expect(result.facets['spec.type']).toEqual([
+ { value: 'service', count: 2 },
+ ]);
+ });
+
+ it('supports both filter and query predicate together', async () => {
+ const client = new InMemoryCatalogClient({ entities });
+ const result = await client.getEntityFacets({
+ facets: ['spec.type'],
+ filter: { kind: 'CustomKind' },
+ query: { 'metadata.namespace': 'default' },
+ });
+ expect(result.facets['spec.type']).toEqual([
+ { value: 'service', count: 1 },
+ ]);
+ });
});
describe('not implemented methods', () => {
diff --git a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts
index e64706ff8d..dd7f269188 100644
--- a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts
+++ b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts
@@ -51,6 +51,10 @@ import {
NotFoundError,
NotImplementedError,
} from '@backstage/errors';
+import {
+ FilterPredicate,
+ filterPredicateToFilterFunction,
+} from '@backstage/filter-predicates';
import lodash from 'lodash';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { traverse } from '../../../../plugins/catalog-backend/src/database/operations/stitcher/buildEntitySearch';
@@ -357,10 +361,15 @@ export class InMemoryCatalogClient implements CatalogApi {
request: GetEntitiesByRefsRequest,
): Promise {
const filter = createFilter(request.filter);
+ const queryFilter = request.query
+ ? filterPredicateToFilterFunction(request.query)
+ : undefined;
const refMap = this.#createEntityRefMap();
const items = request.entityRefs
.map(ref => refMap.get(ref))
- .map(e => (e && filter(e) ? e : undefined));
+ .map(e =>
+ e && filter(e) && (!queryFilter || queryFilter(e)) ? e : undefined,
+ );
return {
items: request.fields
? items.map(e => (e ? applyFieldsFilter(e, request.fields) : undefined))
@@ -373,6 +382,7 @@ export class InMemoryCatalogClient implements CatalogApi {
): Promise {
// Decode query parameters from cursor or from the request directly
let filter: EntityFilterQuery | undefined;
+ let query: FilterPredicate | undefined;
let orderFields: EntityOrderQuery | undefined;
let fullTextFilter: { term: string; fields?: string[] } | undefined;
let offset: number;
@@ -386,12 +396,14 @@ export class InMemoryCatalogClient implements CatalogApi {
throw new InputError('Invalid cursor');
}
filter = deserializeFilter(c.filter as any[]);
+ query = c.query as FilterPredicate | undefined;
orderFields = c.orderFields as EntityOrderQuery | undefined;
fullTextFilter = c.fullTextFilter as typeof fullTextFilter;
offset = c.offset as number;
limit = request.limit;
} else {
filter = request?.filter;
+ query = request?.query;
orderFields = request?.orderFields;
fullTextFilter = request?.fullTextFilter;
offset = request?.offset ?? 0;
@@ -401,6 +413,11 @@ export class InMemoryCatalogClient implements CatalogApi {
// Apply filter
let items = this.#entities.filter(createFilter(filter));
+ // Apply predicate-based query filter
+ if (query) {
+ items = items.filter(filterPredicateToFilterFunction(query));
+ }
+
// Apply full-text filter, defaulting to the sort field or metadata.uid
if (fullTextFilter) {
const orderFieldsList = orderFields ? [orderFields].flat() : [];
@@ -432,6 +449,7 @@ export class InMemoryCatalogClient implements CatalogApi {
const cursorBase = {
filter: serializeFilter(filter),
+ query,
orderFields,
fullTextFilter,
totalItems,
@@ -493,7 +511,12 @@ export class InMemoryCatalogClient implements CatalogApi {
request: GetEntityFacetsRequest,
): Promise {
const filter = createFilter(request.filter);
- const filteredEntities = this.#entities.filter(filter);
+ let filteredEntities = this.#entities.filter(filter);
+ if (request.query) {
+ filteredEntities = filteredEntities.filter(
+ filterPredicateToFilterFunction(request.query),
+ );
+ }
const facets = Object.fromEntries(
request.facets.map(facet => {
const facetValues = new Map();
diff --git a/packages/catalog-client/src/types/api.ts b/packages/catalog-client/src/types/api.ts
index c766b094b8..d6d5392fcf 100644
--- a/packages/catalog-client/src/types/api.ts
+++ b/packages/catalog-client/src/types/api.ts
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-import { CompoundEntityRef, Entity } from '@backstage/catalog-model';
-import { SerializedError } from '@backstage/errors';
+import type { CompoundEntityRef, Entity } from '@backstage/catalog-model';
+import type { SerializedError } from '@backstage/errors';
import type {
AnalyzeLocationRequest,
AnalyzeLocationResponse,
} from '@backstage/plugin-catalog-common';
-import { FilterPredicate } from '@backstage/filter-predicates';
+import type { FilterPredicate } from '@backstage/filter-predicates';
/**
* This symbol can be used in place of a value when passed to filters in e.g.
@@ -212,6 +212,16 @@ export interface GetEntitiesByRefsRequest {
* If given, return only entities that match the given filter.
*/
filter?: EntityFilterQuery;
+ /**
+ * If given, return only entities that match the given predicate query.
+ *
+ * @remarks
+ *
+ * Supports operators like `$all`, `$any`, `$not`, `$exists`, `$in`,
+ * `$contains`, and `$hasPrefix`. When both `filter` and `query` are
+ * provided, they are combined with `$all`.
+ */
+ query?: FilterPredicate;
}
/**
@@ -297,6 +307,16 @@ export interface GetEntityFacetsRequest {
* of that key, no matter what its value is.
*/
filter?: EntityFilterQuery;
+ /**
+ * If given, return only entities that match the given predicate query.
+ *
+ * @remarks
+ *
+ * Supports operators like `$all`, `$any`, `$not`, `$exists`, `$in`,
+ * `$contains`, and `$hasPrefix`. When both `filter` and `query` are
+ * provided, they are combined with `$all`.
+ */
+ query?: FilterPredicate;
/**
* Dot separated paths for the facets to extract from each entity.
*
@@ -376,6 +396,12 @@ export type AddLocationRequest = {
* contain the entities that match the given location.
*/
dryRun?: boolean;
+ /**
+ * Behavior when the location already exists. If set to `'reject'` (the
+ * default), a conflict error is returned. If set to `'refresh'`, the
+ * existing location entity is marked for refresh and a 201 is returned.
+ */
+ onConflict?: 'refresh' | 'reject';
};
/**
@@ -418,16 +444,43 @@ export type QueryEntitiesRequest =
* The method takes this type in an initial pagination request,
* when requesting the first batch of entities.
*
- * The properties filter, sortField, query and sortFieldOrder, are going
+ * The properties filter, query, sortField and sortFieldOrder, are going
* to be immutable for the entire lifecycle of the following requests.
*
+ * @remarks
+ *
+ * Either `filter` or `query` can be provided, or even both:
+ * - `filter`: Uses the traditional key-value filter syntax (GET endpoint)
+ * - `query`: Uses the predicate-based filter syntax with logical operators (POST endpoint)
+ *
* @public
*/
export type QueryEntitiesInitialRequest = {
fields?: string[];
limit?: number;
offset?: number;
+ /**
+ * Traditional key-value based filter.
+ */
filter?: EntityFilterQuery;
+ /**
+ * Predicate-based filter with operators for logical expressions (`$all`,
+ * `$any`, and `$not`) and matching (`$exists`, `$in`, `$hasPrefix`, and
+ * (partially) `$contains`).
+ *
+ * @example
+ * ```typescript
+ * {
+ * query: {
+ * $all: [
+ * { kind: 'component' },
+ * { 'spec.type': { $in: ['service', 'website'] } }
+ * ]
+ * }
+ * }
+ * ```
+ */
+ query?: FilterPredicate;
orderFields?: EntityOrderQuery;
fullTextFilter?: {
term: string;
@@ -567,6 +620,7 @@ export interface CatalogApi {
* const response = await catalogClient.queryEntities({
* filter: [{ kind: 'group' }],
* limit: 20,
+ * fields: ['metadata', 'kind'],
* fullTextFilter: {
* term: 'A',
* },
@@ -583,11 +637,15 @@ export interface CatalogApi {
*
* ```
* const secondBatchResponse = await catalogClient
- * .queryEntities({ cursor: response.nextCursor });
+ * .queryEntities({
+ * cursor: response.nextCursor,
+ * limit: 20,
+ * fields: ['metadata', 'kind'],
+ * });
* ```
*
- * secondBatchResponse will contain the next batch of (maximum) 20 entities,
- * together with a prevCursor property, useful to fetch the previous batch.
+ * `secondBatchResponse` will contain the next batch of (maximum) 20 entities,
+ * together with a `prevCursor` property, useful to fetch the previous batch.
*
* @public
*
diff --git a/packages/catalog-client/src/utils.test.ts b/packages/catalog-client/src/utils.test.ts
index 2f00859985..ce60cc3083 100644
--- a/packages/catalog-client/src/utils.test.ts
+++ b/packages/catalog-client/src/utils.test.ts
@@ -14,7 +14,87 @@
* limitations under the License.
*/
-import { splitRefsIntoChunks } from './utils';
+import { CATALOG_FILTER_EXISTS } from './types/api';
+import { convertFilterToPredicate, splitRefsIntoChunks } from './utils';
+
+describe('convertFilterToPredicate', () => {
+ it('converts a single string value', () => {
+ expect(convertFilterToPredicate({ kind: 'component' })).toEqual({
+ kind: 'component',
+ });
+ });
+
+ it('converts multiple keys into $all', () => {
+ expect(
+ convertFilterToPredicate({
+ kind: 'component',
+ 'spec.type': 'service',
+ }),
+ ).toEqual({
+ $all: [{ kind: 'component' }, { 'spec.type': 'service' }],
+ });
+ });
+
+ it('converts an array of string values into $in', () => {
+ expect(
+ convertFilterToPredicate({ 'spec.type': ['service', 'website'] }),
+ ).toEqual({
+ 'spec.type': { $in: ['service', 'website'] },
+ });
+ });
+
+ it('converts CATALOG_FILTER_EXISTS into $exists', () => {
+ expect(
+ convertFilterToPredicate({ 'spec.owner': CATALOG_FILTER_EXISTS }),
+ ).toEqual({
+ 'spec.owner': { $exists: true },
+ });
+ });
+
+ it('converts an array of records into $any (OR)', () => {
+ expect(
+ convertFilterToPredicate([{ kind: 'component' }, { kind: 'api' }]),
+ ).toEqual({
+ $any: [{ kind: 'component' }, { kind: 'api' }],
+ });
+ });
+
+ it('converts array of records with multiple keys each', () => {
+ expect(
+ convertFilterToPredicate([
+ { kind: 'component', 'spec.type': 'service' },
+ { kind: 'api' },
+ ]),
+ ).toEqual({
+ $any: [
+ { $all: [{ kind: 'component' }, { 'spec.type': 'service' }] },
+ { kind: 'api' },
+ ],
+ });
+ });
+
+ it('treats CATALOG_FILTER_EXISTS mixed with string values as just existence', () => {
+ expect(
+ convertFilterToPredicate({
+ 'spec.owner': [CATALOG_FILTER_EXISTS, 'team-a'],
+ }),
+ ).toEqual({
+ 'spec.owner': { $exists: true },
+ });
+ });
+
+ it('converts a single-element array filter without wrapping in $any', () => {
+ expect(convertFilterToPredicate([{ kind: 'component' }])).toEqual({
+ kind: 'component',
+ });
+ });
+
+ it('ignores entries with no valid values', () => {
+ expect(
+ convertFilterToPredicate({ kind: 'component', other: [] as string[] }),
+ ).toEqual({ kind: 'component' });
+ });
+});
describe('splitRefsIntoChunks', () => {
it('splits by count limit', () => {
diff --git a/packages/catalog-client/src/utils.ts b/packages/catalog-client/src/utils.ts
index a16cf53db1..231aa2409f 100644
--- a/packages/catalog-client/src/utils.ts
+++ b/packages/catalog-client/src/utils.ts
@@ -14,7 +14,13 @@
* limitations under the License.
*/
+import type {
+ FilterPredicate,
+ FilterPredicateExpression,
+} from '@backstage/filter-predicates';
import {
+ CATALOG_FILTER_EXISTS,
+ EntityFilterQuery,
QueryEntitiesCursorRequest,
QueryEntitiesInitialRequest,
QueryEntitiesRequest,
@@ -26,6 +32,58 @@ export function isQueryEntitiesInitialRequest(
return !(request as QueryEntitiesCursorRequest).cursor;
}
+/**
+ * Check if a cursor contains a predicate query by attempting to decode it.
+ * @internal
+ */
+export function cursorContainsQuery(cursor: string): boolean {
+ try {
+ const decoded = JSON.parse(atob(cursor));
+ return 'query' in decoded;
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * Converts an {@link EntityFilterQuery} into a predicate query object.
+ * @internal
+ */
+export function convertFilterToPredicate(filter: EntityFilterQuery):
+ | FilterPredicateExpression
+ | {
+ $all: FilterPredicate[];
+ }
+ | {
+ $any: FilterPredicate[];
+ } {
+ const records = [filter].flat();
+
+ const clauses = records.map(record => {
+ const parts: FilterPredicateExpression[] = [];
+
+ for (const [key, value] of Object.entries(record)) {
+ const values = [value].flat();
+ const strings = values.filter((v): v is string => typeof v === 'string');
+ const hasExists = values.some(v => v === CATALOG_FILTER_EXISTS);
+
+ if (hasExists) {
+ // Ignore whether there ALSO were some strings - that would boil down to
+ // just existence anyway since there's effectively an OR between them
+ parts.push({ [key]: { $exists: true } } as FilterPredicateExpression);
+ } else if (strings.length === 1) {
+ parts.push({ [key]: strings[0] } as FilterPredicateExpression);
+ } else if (strings.length > 1) {
+ parts.push({ [key]: { $in: strings } } as FilterPredicateExpression);
+ }
+ }
+
+ return parts.length === 1 ? parts[0] : { $all: parts };
+ });
+
+ return clauses.length === 1 ? clauses[0] : { $any: clauses };
+}
+
/**
* Takes a set of entity refs, and splits them into chunks (groups) such that
* the total string length in each chunk does not exceed the default Express.js
diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md
index 2fb24c4943..6d3927a404 100644
--- a/packages/catalog-model/CHANGELOG.md
+++ b/packages/catalog-model/CHANGELOG.md
@@ -1,5 +1,11 @@
# @backstage/catalog-model
+## 1.7.7
+
+### Patch Changes
+
+- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
+
## 1.7.6
### Patch Changes
diff --git a/packages/catalog-model/examples/apis/hello-world-trpc-api.yaml b/packages/catalog-model/examples/apis/hello-world-trpc-api.yaml
index f5807537b3..f1847154ce 100644
--- a/packages/catalog-model/examples/apis/hello-world-trpc-api.yaml
+++ b/packages/catalog-model/examples/apis/hello-world-trpc-api.yaml
@@ -8,7 +8,7 @@ spec:
lifecycle: experimental
owner: team-c
definition: |
- import { z } from 'zod';
+ import { z } from 'zod/v3';
import { publicProcedure, router } from '../trpc';
export const apiRouter = router({
diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json
index 85c299e941..ddee09d18f 100644
--- a/packages/catalog-model/package.json
+++ b/packages/catalog-model/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
- "version": "1.7.6",
+ "version": "1.7.7",
"description": "Types and validators that help describe the model of a Backstage Catalog",
"backstage": {
"role": "common-library"
diff --git a/packages/cli-common/CHANGELOG.md b/packages/cli-common/CHANGELOG.md
index c36322d60f..cbff8fe79c 100644
--- a/packages/cli-common/CHANGELOG.md
+++ b/packages/cli-common/CHANGELOG.md
@@ -1,5 +1,95 @@
# @backstage/cli-common
+## 0.2.1-next.0
+
+### Patch Changes
+
+- e928e73: chore(deps): bump `undici` from 7.22.0 to 7.24.0
+- Updated dependencies
+ - @backstage/errors@1.2.7
+
+## 0.2.0
+
+### Minor Changes
+
+- 56bd494: Added `targetPaths` and `findOwnPaths` as replacements for `findPaths`, with a cleaner separation between target project paths and package-relative paths.
+
+ To migrate existing `findPaths` usage:
+
+ ```ts
+ // Before
+ import { findPaths } from '@backstage/cli-common';
+ const paths = findPaths(__dirname);
+
+ // After — for target project paths (cwd-based):
+ import { targetPaths } from '@backstage/cli-common';
+ // paths.targetDir → targetPaths.dir
+ // paths.targetRoot → targetPaths.rootDir
+ // paths.resolveTarget('src') → targetPaths.resolve('src')
+ // paths.resolveTargetRoot('yarn.lock') → targetPaths.resolveRoot('yarn.lock')
+
+ // After — for package-relative paths:
+ import { findOwnPaths } from '@backstage/cli-common';
+ const own = findOwnPaths(__dirname);
+ // paths.ownDir → own.dir
+ // paths.ownRoot → own.rootDir
+ // paths.resolveOwn('config/jest.js') → own.resolve('config/jest.js')
+ // paths.resolveOwnRoot('tsconfig.json') → own.resolveRoot('tsconfig.json')
+ ```
+
+### Patch Changes
+
+- e44b6a9: The `findOwnRootDir` utility now searches for the monorepo root by traversing up the directory tree looking for a `package.json` with `workspaces`, instead of assuming a fixed `../..` relative path. If no workspaces root is found during this traversal, `findOwnRootDir` now throws to enforce stricter validation of the repository layout.
+- 9361965: Fixed `runCheck` to ignore stdio of the spawned process, preventing unwanted output from leaking to the terminal.
+
+## 0.2.0-next.2
+
+### Patch Changes
+
+- 9361965: Fixed `runCheck` to ignore stdio of the spawned process, preventing unwanted output from leaking to the terminal.
+
+## 0.2.0-next.1
+
+### Patch Changes
+
+- e44b6a9: The `findOwnRootDir` utility now searches for the monorepo root by traversing up the directory tree looking for a `package.json` with `workspaces`, instead of assuming a fixed `../..` relative path. If no workspaces root is found during this traversal, `findOwnRootDir` now throws to enforce stricter validation of the repository layout.
+- Updated dependencies
+ - @backstage/errors@1.2.7
+
+## 0.2.0-next.0
+
+### Minor Changes
+
+- 56bd494: Added `targetPaths` and `findOwnPaths` as replacements for `findPaths`, with a cleaner separation between target project paths and package-relative paths.
+
+ To migrate existing `findPaths` usage:
+
+ ```ts
+ // Before
+ import { findPaths } from '@backstage/cli-common';
+ const paths = findPaths(__dirname);
+
+ // After — for target project paths (cwd-based):
+ import { targetPaths } from '@backstage/cli-common';
+ // paths.targetDir → targetPaths.dir
+ // paths.targetRoot → targetPaths.rootDir
+ // paths.resolveTarget('src') → targetPaths.resolve('src')
+ // paths.resolveTargetRoot('yarn.lock') → targetPaths.resolveRoot('yarn.lock')
+
+ // After — for package-relative paths:
+ import { findOwnPaths } from '@backstage/cli-common';
+ const own = findOwnPaths(__dirname);
+ // paths.ownDir → own.dir
+ // paths.ownRoot → own.rootDir
+ // paths.resolveOwn('config/jest.js') → own.resolve('config/jest.js')
+ // paths.resolveOwnRoot('tsconfig.json') → own.resolveRoot('tsconfig.json')
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/errors@1.2.7
+
## 0.1.18
### Patch Changes
diff --git a/packages/cli-common/package.json b/packages/cli-common/package.json
index 4eae1d96a4..b393335552 100644
--- a/packages/cli-common/package.json
+++ b/packages/cli-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/cli-common",
- "version": "0.1.18",
+ "version": "0.2.1-next.0",
"description": "Common functionality used by cli, backend, and create-app",
"backstage": {
"role": "node-library"
@@ -51,7 +51,7 @@
"@backstage/errors": "workspace:^",
"cross-spawn": "^7.0.3",
"global-agent": "^3.0.0",
- "undici": "^7.2.3"
+ "undici": "^7.24.5"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
diff --git a/packages/cli-common/src/paths.test.ts b/packages/cli-common/src/paths.test.ts
index 6ade904e12..df0e7c2044 100644
--- a/packages/cli-common/src/paths.test.ts
+++ b/packages/cli-common/src/paths.test.ts
@@ -16,18 +16,18 @@
/* eslint-disable no-restricted-syntax */
import { resolve as resolvePath } from 'node:path';
-import { findPaths, findRootPath, findOwnDir, findOwnRootDir } from './paths';
+import { findPaths, findRootPath, findOwnRootDir, findOwnPaths } from './paths';
describe('paths', () => {
afterEach(() => {
jest.restoreAllMocks();
});
- it('findOwnDir and findOwnRootDir should find owns paths', () => {
- const dir = findOwnDir(__dirname);
- const root = findOwnRootDir(dir);
+ it('findOwnPaths and findOwnRootDir should find own paths', () => {
+ const own = findOwnPaths(__dirname);
+ const root = findOwnRootDir(own.dir);
- expect(dir).toBe(resolvePath(__dirname, '..'));
+ expect(own.dir).toBe(resolvePath(__dirname, '..'));
expect(root).toBe(resolvePath(__dirname, '../../..'));
});
@@ -98,7 +98,9 @@ describe('paths', () => {
});
it('findPaths should find workspace root with object', () => {
- jest.spyOn(JSON, 'parse').mockReturnValue({ workspaces: { packages: [] } });
+ jest
+ .spyOn(JSON, 'parse')
+ .mockReturnValue({ workspaces: { packages: ['packages/*'] } });
jest.spyOn(process, 'cwd').mockReturnValue(__dirname);
const paths = findPaths(__dirname);
@@ -110,7 +112,7 @@ describe('paths', () => {
});
it('findPaths should find workspace root with array', () => {
- jest.spyOn(JSON, 'parse').mockReturnValue({ workspaces: [] });
+ jest.spyOn(JSON, 'parse').mockReturnValue({ workspaces: ['packages/*'] });
jest.spyOn(process, 'cwd').mockReturnValue(__dirname);
const paths = findPaths(__dirname);
diff --git a/packages/cli-common/src/paths.ts b/packages/cli-common/src/paths.ts
index 1cd84d4f5b..c6bedc77e4 100644
--- a/packages/cli-common/src/paths.ts
+++ b/packages/cli-common/src/paths.ts
@@ -119,7 +119,23 @@ export function findOwnRootDir(ownDir: string) {
);
}
- return resolvePath(ownDir, '../..');
+ const rootDir = findRootPath(ownDir, pkgJsonPath => {
+ try {
+ const content = fs.readFileSync(pkgJsonPath, 'utf8');
+ const data = JSON.parse(content);
+ return Boolean(data.workspaces);
+ } catch (error) {
+ throw new Error(
+ `Failed to read package.json at '${pkgJsonPath}', ${error}`,
+ );
+ }
+ });
+
+ if (!rootDir) {
+ throw new Error(`No monorepo root found when searching from '${ownDir}'`);
+ }
+
+ return rootDir;
}
// Hierarchical directory cache shared across all OwnPathsImpl instances.
@@ -199,11 +215,6 @@ class OwnPathsImpl implements OwnPaths {
};
}
-// Finds the root of a given package
-export function findOwnDir(searchDir: string) {
- return OwnPathsImpl.findDir(searchDir);
-}
-
// Used by the test utility in testUtils.ts to override targetPaths
export let targetPathsOverride: TargetPaths | undefined;
diff --git a/packages/cli-common/src/run.test.ts b/packages/cli-common/src/run.test.ts
index 01c6c915d6..f38c1659ba 100644
--- a/packages/cli-common/src/run.test.ts
+++ b/packages/cli-common/src/run.test.ts
@@ -338,5 +338,26 @@ describe('run', () => {
const result = await runCheck(['nonexistent-command-12345']);
expect(result).toBe(false);
});
+
+ it('should not leak stdout or stderr from the child process', async () => {
+ const stdoutSpy = jest.spyOn(process.stdout, 'write');
+ const stderrSpy = jest.spyOn(process.stderr, 'write');
+ const stdoutBefore = stdoutSpy.mock.calls.length;
+ const stderrBefore = stderrSpy.mock.calls.length;
+
+ await runCheck([
+ 'node',
+ '--eval',
+ 'console.log("leaked stdout"); console.error("leaked stderr")',
+ ]);
+
+ const stdoutCalls = stdoutSpy.mock.calls.slice(stdoutBefore);
+ const stderrCalls = stderrSpy.mock.calls.slice(stderrBefore);
+ const stdout = stdoutCalls.map(c => String(c[0])).join('');
+ const stderr = stderrCalls.map(c => String(c[0])).join('');
+
+ expect(stdout).not.toContain('leaked stdout');
+ expect(stderr).not.toContain('leaked stderr');
+ });
});
});
diff --git a/packages/cli-common/src/run.ts b/packages/cli-common/src/run.ts
index 23e71460e4..0e95bd517b 100644
--- a/packages/cli-common/src/run.ts
+++ b/packages/cli-common/src/run.ts
@@ -211,7 +211,7 @@ export async function runOutput(
*/
export async function runCheck(args: string[]): Promise {
try {
- await run(args).waitForExit();
+ await run(args, { stdio: 'ignore' }).waitForExit();
return true;
} catch {
return false;
diff --git a/plugins/scaffolder-backend-module-bitbucket/.eslintrc.js b/packages/cli-defaults/.eslintrc.js
similarity index 100%
rename from plugins/scaffolder-backend-module-bitbucket/.eslintrc.js
rename to packages/cli-defaults/.eslintrc.js
diff --git a/packages/cli-defaults/CHANGELOG.md b/packages/cli-defaults/CHANGELOG.md
new file mode 100644
index 0000000000..df2ea6932b
--- /dev/null
+++ b/packages/cli-defaults/CHANGELOG.md
@@ -0,0 +1,42 @@
+# @backstage/cli-defaults
+
+## 0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-module-build@0.1.1-next.0
+ - @backstage/cli-module-config@0.1.1-next.0
+ - @backstage/cli-module-github@0.1.1-next.0
+ - @backstage/cli-module-info@0.1.1-next.0
+ - @backstage/cli-module-lint@0.1.1-next.0
+ - @backstage/cli-module-maintenance@0.1.1-next.0
+ - @backstage/cli-module-migrate@0.1.1-next.0
+ - @backstage/cli-module-new@0.1.1-next.0
+ - @backstage/cli-module-test-jest@0.1.1-next.0
+ - @backstage/cli-module-translations@0.1.1-next.0
+ - @backstage/cli-module-actions@0.0.2-next.0
+ - @backstage/cli-module-auth@0.1.1-next.0
+
+## 0.1.0
+
+### Minor Changes
+
+- 7781ae5: Introduced `@backstage/cli-defaults`, a convenience package that bundles all standard Backstage CLI modules. Install this single package as a `devDependency` to get the full default set of CLI commands without listing each module individually.
+
+### Patch Changes
+
+- 42960f1: Added `@backstage/cli-module-actions` to the default set of CLI modules.
+- Updated dependencies
+ - @backstage/cli-module-actions@0.0.1
+ - @backstage/cli-module-new@0.1.0
+ - @backstage/cli-module-maintenance@0.1.0
+ - @backstage/cli-module-build@0.1.0
+ - @backstage/cli-module-auth@0.1.0
+ - @backstage/cli-module-config@0.1.0
+ - @backstage/cli-module-github@0.1.0
+ - @backstage/cli-module-info@0.1.0
+ - @backstage/cli-module-lint@0.1.0
+ - @backstage/cli-module-migrate@0.1.0
+ - @backstage/cli-module-test-jest@0.1.0
+ - @backstage/cli-module-translations@0.1.0
diff --git a/packages/cli-defaults/README.md b/packages/cli-defaults/README.md
new file mode 100644
index 0000000000..2066aa1e35
--- /dev/null
+++ b/packages/cli-defaults/README.md
@@ -0,0 +1,26 @@
+# @backstage/cli-defaults
+
+The default set of CLI modules for the Backstage CLI. Installing this single package provides all standard CLI commands without needing to list each module individually.
+
+## Included Modules
+
+| Module | Description |
+| :----------------------------------------------------------------- | :--------------------------------------- |
+| [`@backstage/cli-module-auth`](../cli-module-auth) | Authentication commands |
+| [`@backstage/cli-module-build`](../cli-module-build) | Build, start, and packaging commands |
+| [`@backstage/cli-module-config`](../cli-module-config) | Configuration inspection commands |
+| [`@backstage/cli-module-github`](../cli-module-github) | GitHub App creation |
+| [`@backstage/cli-module-info`](../cli-module-info) | Environment and dependency info |
+| [`@backstage/cli-module-lint`](../cli-module-lint) | Linting commands |
+| [`@backstage/cli-module-maintenance`](../cli-module-maintenance) | Repository maintenance commands |
+| [`@backstage/cli-module-migrate`](../cli-module-migrate) | Migration and version management |
+| [`@backstage/cli-module-new`](../cli-module-new) | Scaffolding for new plugins and packages |
+| [`@backstage/cli-module-test-jest`](../cli-module-test-jest) | Jest-based testing commands |
+| [`@backstage/cli-module-translations`](../cli-module-translations) | Translation management commands |
+
+For fine-grained control over which CLI commands are available, you can install individual modules instead.
+
+## Documentation
+
+- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
+- [Backstage Documentation](https://backstage.io/docs)
diff --git a/packages/cli-defaults/catalog-info.yaml b/packages/cli-defaults/catalog-info.yaml
new file mode 100644
index 0000000000..3cac883c36
--- /dev/null
+++ b/packages/cli-defaults/catalog-info.yaml
@@ -0,0 +1,10 @@
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: backstage-cli-defaults
+ title: '@backstage/cli-defaults'
+ description: Default set of CLI modules for the Backstage CLI
+spec:
+ lifecycle: experimental
+ type: backstage-cli-module
+ owner: tooling-maintainers
diff --git a/packages/cli-defaults/package.json b/packages/cli-defaults/package.json
new file mode 100644
index 0000000000..63f5507729
--- /dev/null
+++ b/packages/cli-defaults/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "@backstage/cli-defaults",
+ "version": "0.1.1-next.0",
+ "description": "Default set of CLI modules for the Backstage CLI",
+ "backstage": {
+ "role": "cli-module"
+ },
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.cjs.js",
+ "types": "dist/index.d.ts"
+ },
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "packages/cli-defaults"
+ },
+ "license": "Apache-2.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "backstage-cli package build",
+ "clean": "backstage-cli package clean",
+ "lint": "backstage-cli package lint",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack"
+ },
+ "dependencies": {
+ "@backstage/cli-module-actions": "workspace:^",
+ "@backstage/cli-module-auth": "workspace:^",
+ "@backstage/cli-module-build": "workspace:^",
+ "@backstage/cli-module-config": "workspace:^",
+ "@backstage/cli-module-github": "workspace:^",
+ "@backstage/cli-module-info": "workspace:^",
+ "@backstage/cli-module-lint": "workspace:^",
+ "@backstage/cli-module-maintenance": "workspace:^",
+ "@backstage/cli-module-migrate": "workspace:^",
+ "@backstage/cli-module-new": "workspace:^",
+ "@backstage/cli-module-test-jest": "workspace:^",
+ "@backstage/cli-module-translations": "workspace:^"
+ },
+ "devDependencies": {
+ "@backstage/cli": "workspace:^"
+ }
+}
diff --git a/packages/cli-defaults/report.api.md b/packages/cli-defaults/report.api.md
new file mode 100644
index 0000000000..573f8ee454
--- /dev/null
+++ b/packages/cli-defaults/report.api.md
@@ -0,0 +1,13 @@
+## API Report File for "@backstage/cli-defaults"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { CliModule } from '@backstage/cli-node';
+
+// @public
+const _default: CliModule[];
+export default _default;
+
+// (No @packageDocumentation comment for this package)
+```
diff --git a/packages/cli-defaults/src/index.ts b/packages/cli-defaults/src/index.ts
new file mode 100644
index 0000000000..f167c3c424
--- /dev/null
+++ b/packages/cli-defaults/src/index.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import actions from '@backstage/cli-module-actions';
+import auth from '@backstage/cli-module-auth';
+import build from '@backstage/cli-module-build';
+import config from '@backstage/cli-module-config';
+import github from '@backstage/cli-module-github';
+import info from '@backstage/cli-module-info';
+import lint from '@backstage/cli-module-lint';
+import maintenance from '@backstage/cli-module-maintenance';
+import migrate from '@backstage/cli-module-migrate';
+import newModule from '@backstage/cli-module-new';
+import testJest from '@backstage/cli-module-test-jest';
+import translations from '@backstage/cli-module-translations';
+
+/**
+ * The default set of CLI modules for the Backstage CLI.
+ *
+ * @public
+ */
+export default [
+ actions,
+ auth,
+ build,
+ config,
+ github,
+ info,
+ lint,
+ maintenance,
+ migrate,
+ newModule,
+ testJest,
+ translations,
+];
diff --git a/packages/cli/templates/backend-plugin-module/.eslintrc.js.hbs b/packages/cli-internal/.eslintrc.js
similarity index 100%
rename from packages/cli/templates/backend-plugin-module/.eslintrc.js.hbs
rename to packages/cli-internal/.eslintrc.js
diff --git a/packages/cli-internal/CHANGELOG.md b/packages/cli-internal/CHANGELOG.md
new file mode 100644
index 0000000000..30a090f3ef
--- /dev/null
+++ b/packages/cli-internal/CHANGELOG.md
@@ -0,0 +1,15 @@
+# @internal/cli
+
+## 0.0.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.1-next.0
+
+## 0.0.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
diff --git a/packages/cli-internal/catalog-info.yaml b/packages/cli-internal/catalog-info.yaml
new file mode 100644
index 0000000000..2f2a1c837a
--- /dev/null
+++ b/packages/cli-internal/catalog-info.yaml
@@ -0,0 +1,9 @@
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: internal-cli
+ title: '@internal/cli'
+spec:
+ lifecycle: experimental
+ type: backstage-node-library
+ owner: tooling-maintainers
diff --git a/packages/cli-internal/package.json b/packages/cli-internal/package.json
new file mode 100644
index 0000000000..79030bdbe0
--- /dev/null
+++ b/packages/cli-internal/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@internal/cli",
+ "version": "0.0.3-next.0",
+ "backstage": {
+ "role": "node-library",
+ "inline": true
+ },
+ "private": true,
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "packages/cli-internal"
+ },
+ "license": "Apache-2.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test"
+ },
+ "dependencies": {
+ "@backstage/cli-node": "workspace:^"
+ },
+ "devDependencies": {
+ "@backstage/cli": "workspace:^"
+ }
+}
diff --git a/packages/cli-internal/src/InternalCliModule.ts b/packages/cli-internal/src/InternalCliModule.ts
new file mode 100644
index 0000000000..213e309371
--- /dev/null
+++ b/packages/cli-internal/src/InternalCliModule.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { CliCommand, CliModule } from '@backstage/cli-node';
+import { OpaqueType } from '@internal/opaque';
+
+export const OpaqueCliModule = OpaqueType.create<{
+ public: CliModule;
+ versions: {
+ readonly version: 'v1';
+ readonly packageName: string;
+ readonly commands: Promise>;
+ };
+}>({
+ type: '@backstage/CliModule',
+ versions: ['v1'],
+});
diff --git a/packages/cli-internal/src/InternalCommandNode.ts b/packages/cli-internal/src/InternalCommandNode.ts
new file mode 100644
index 0000000000..233e9128cb
--- /dev/null
+++ b/packages/cli-internal/src/InternalCommandNode.ts
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { CliCommand, CliModule } from '@backstage/cli-node';
+import { OpaqueType } from '@internal/opaque';
+
+/** @internal */
+export interface CommandTreeNode {
+ readonly $$type: '@backstage/CommandTreeNode';
+}
+
+/** @internal */
+export interface CommandLeafNode {
+ readonly $$type: '@backstage/CommandLeafNode';
+}
+
+export type CommandNode = CommandTreeNode | CommandLeafNode;
+
+export const OpaqueCommandTreeNode = OpaqueType.create<{
+ public: CommandTreeNode;
+ versions: {
+ readonly version: 'v1';
+ readonly name: string;
+ readonly children: CommandNode[];
+ };
+}>({
+ type: '@backstage/CommandTreeNode',
+ versions: ['v1'],
+});
+
+export const OpaqueCommandLeafNode = OpaqueType.create<{
+ public: CommandLeafNode;
+ versions: {
+ readonly version: 'v1';
+ readonly name: string;
+ readonly command: CliCommand;
+ readonly module?: CliModule;
+ };
+}>({
+ type: '@backstage/CommandLeafNode',
+ versions: ['v1'],
+});
+
+/**
+ * Checks whether a command node should be hidden from help output.
+ * Leaf nodes are hidden if they are deprecated or experimental.
+ * Tree nodes are hidden if all of their children are hidden.
+ */
+export function isCommandNodeHidden(node: CommandNode): boolean {
+ if (OpaqueCommandLeafNode.isType(node)) {
+ const { command } = OpaqueCommandLeafNode.toInternal(node);
+ return !!command.deprecated || !!command.experimental;
+ }
+ const { children } = OpaqueCommandTreeNode.toInternal(node);
+ return children.every(child => isCommandNodeHidden(child));
+}
diff --git a/packages/cli-internal/src/authIdentifiers.ts b/packages/cli-internal/src/authIdentifiers.ts
new file mode 100644
index 0000000000..6b72554a4f
--- /dev/null
+++ b/packages/cli-internal/src/authIdentifiers.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Returns the secret-store service key for a given auth instance. */
+export function getAuthInstanceService(instanceName: string): string {
+ return `backstage-cli:auth-instance:${instanceName}`;
+}
diff --git a/packages/cli-internal/src/index.ts b/packages/cli-internal/src/index.ts
new file mode 100644
index 0000000000..dfd8968c6a
--- /dev/null
+++ b/packages/cli-internal/src/index.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { OpaqueCliModule } from './InternalCliModule';
+export type {
+ CommandNode,
+ CommandTreeNode,
+ CommandLeafNode,
+} from './InternalCommandNode';
+export {
+ OpaqueCommandTreeNode,
+ OpaqueCommandLeafNode,
+ isCommandNodeHidden,
+} from './InternalCommandNode';
+export { getAuthInstanceService } from './authIdentifiers';
+export {
+ getSecretStore,
+ resetSecretStore,
+ type SecretStore,
+} from './secretStore';
+export {
+ knownBackendPluginPackageNameByPluginId,
+ knownFrontendPluginPackageNameByPluginId,
+} from './knownPluginPackages';
diff --git a/packages/cli-internal/src/knownPluginPackages.ts b/packages/cli-internal/src/knownPluginPackages.ts
new file mode 100644
index 0000000000..c5d13beef1
--- /dev/null
+++ b/packages/cli-internal/src/knownPluginPackages.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const knownBackendPluginIds = [
+ 'app',
+ 'auth',
+ 'catalog',
+ 'events',
+ 'kubernetes',
+ 'notifications',
+ 'permission',
+ 'proxy',
+ 'scaffolder',
+ 'search',
+ 'signals',
+ 'techdocs',
+];
+
+// Only includes plugin IDs that have a corresponding frontend package; some plugins are backend-only and not listed here.
+const knownFrontendPluginIds = [
+ 'app',
+ 'auth',
+ 'catalog',
+ 'kubernetes',
+ 'notifications',
+ 'scaffolder',
+ 'search',
+ 'signals',
+ 'techdocs',
+];
+
+/**
+ * Maps known plugin IDs to their corresponding backend package names.
+ */
+export const knownBackendPluginPackageNameByPluginId: Record =
+ Object.fromEntries(
+ knownBackendPluginIds.map(pluginId => [
+ pluginId,
+ `@backstage/plugin-${pluginId}-backend`,
+ ]),
+ );
+
+/**
+ * Maps known plugin IDs to their corresponding frontend package names.
+ */
+export const knownFrontendPluginPackageNameByPluginId: Record =
+ Object.fromEntries(
+ knownFrontendPluginIds.map(pluginId => [
+ pluginId,
+ `@backstage/plugin-${pluginId}`,
+ ]),
+ );
diff --git a/packages/cli-internal/src/secretStore.ts b/packages/cli-internal/src/secretStore.ts
new file mode 100644
index 0000000000..26c1e6ad2b
--- /dev/null
+++ b/packages/cli-internal/src/secretStore.ts
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { promises as fs } from 'node:fs';
+import os from 'node:os';
+import path from 'node:path';
+
+export type SecretStore = {
+ get(service: string, account: string): Promise;
+ set(service: string, account: string, secret: string): Promise;
+ delete(service: string, account: string): Promise;
+};
+
+async function loadKeytar(): Promise {
+ try {
+ // eslint-disable-next-line import/no-extraneous-dependencies, @backstage/no-undeclared-imports
+ const keytar = require('keytar') as typeof import('keytar');
+ if (keytar && typeof keytar.getPassword === 'function') {
+ return keytar;
+ }
+ } catch {
+ // keytar not available
+ }
+ return undefined;
+}
+
+class KeytarSecretStore implements SecretStore {
+ private readonly keytar: typeof import('keytar');
+ constructor(keytar: typeof import('keytar')) {
+ this.keytar = keytar;
+ }
+ async get(service: string, account: string): Promise {
+ const result = await this.keytar.getPassword(service, account);
+ return result ?? undefined;
+ }
+ async set(service: string, account: string, secret: string): Promise {
+ await this.keytar.setPassword(service, account, secret);
+ }
+ async delete(service: string, account: string): Promise {
+ await this.keytar.deletePassword(service, account);
+ }
+}
+
+async function pathExists(p: string): Promise {
+ try {
+ await fs.stat(p);
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+class FileSecretStore implements SecretStore {
+ private readonly baseDir: string;
+ constructor() {
+ const root =
+ process.env.XDG_DATA_HOME ||
+ (process.platform === 'win32'
+ ? process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')
+ : path.join(os.homedir(), '.local', 'share'));
+ this.baseDir = path.join(root, 'backstage-cli', 'auth-secrets');
+ }
+ private filePath(service: string, account: string): string {
+ return path.join(
+ this.baseDir,
+ encodeURIComponent(service),
+ `${encodeURIComponent(account)}.secret`,
+ );
+ }
+ async get(service: string, account: string): Promise {
+ const file = this.filePath(service, account);
+ if (!(await pathExists(file))) {
+ return undefined;
+ }
+ return await fs.readFile(file, 'utf8');
+ }
+ async set(service: string, account: string, secret: string): Promise {
+ const file = this.filePath(service, account);
+ await fs.mkdir(path.dirname(file), { recursive: true });
+ await fs.writeFile(file, secret, { encoding: 'utf8', mode: 0o600 });
+ }
+ async delete(service: string, account: string): Promise {
+ const file = this.filePath(service, account);
+ try {
+ await fs.unlink(file);
+ } catch (err) {
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
+ throw err;
+ }
+ }
+ }
+}
+
+let singleton: SecretStore | undefined;
+
+export async function getSecretStore(): Promise {
+ if (!singleton) {
+ const keytar = await loadKeytar();
+ if (keytar) {
+ singleton = new KeytarSecretStore(keytar);
+ } else {
+ singleton = new FileSecretStore();
+ }
+ }
+ return singleton;
+}
+
+/**
+ * Reset the singleton instance (for testing purposes only)
+ * @internal
+ */
+export function resetSecretStore(): void {
+ singleton = undefined;
+}
diff --git a/packages/cli/templates/backend-plugin/.eslintrc.js.hbs b/packages/cli-module-actions/.eslintrc.js
similarity index 100%
rename from packages/cli/templates/backend-plugin/.eslintrc.js.hbs
rename to packages/cli-module-actions/.eslintrc.js
diff --git a/packages/cli-module-actions/CHANGELOG.md b/packages/cli-module-actions/CHANGELOG.md
new file mode 100644
index 0000000000..3305dddb37
--- /dev/null
+++ b/packages/cli-module-actions/CHANGELOG.md
@@ -0,0 +1,17 @@
+# @backstage/cli-module-actions
+
+## 0.0.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.3.1-next.0
+ - @backstage/errors@1.2.7
+
+## 0.0.1
+
+### Patch Changes
+
+- 42960f1: Added `actions` CLI module for listing and executing actions from the distributed actions registry. Includes `actions list`, `actions execute`, and `actions sources` commands for managing plugin sources.
+- Updated dependencies
+ - @backstage/cli-node@0.3.0
diff --git a/packages/cli-module-actions/bin/backstage-cli-module-actions b/packages/cli-module-actions/bin/backstage-cli-module-actions
new file mode 100755
index 0000000000..59f8267233
--- /dev/null
+++ b/packages/cli-module-actions/bin/backstage-cli-module-actions
@@ -0,0 +1,32 @@
+#!/usr/bin/env node
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const path = require('node:path');
+
+/* eslint-disable-next-line no-restricted-syntax */
+const isLocal = require('node:fs').existsSync(
+ path.resolve(__dirname, '../src'),
+);
+
+if (isLocal) {
+ require('@backstage/cli-node/config/nodeTransform.cjs');
+}
+
+const { runCliModule } = require('@backstage/cli-node');
+const cliModule = require(isLocal ? '../src/index' : '..').default;
+const pkg = require('../package.json');
+runCliModule({ module: cliModule, name: pkg.name, version: pkg.version });
diff --git a/packages/cli-module-actions/catalog-info.yaml b/packages/cli-module-actions/catalog-info.yaml
new file mode 100644
index 0000000000..fd282ca6a3
--- /dev/null
+++ b/packages/cli-module-actions/catalog-info.yaml
@@ -0,0 +1,10 @@
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: backstage-cli-module-actions
+ title: '@backstage/cli-module-actions'
+ description: CLI module for executing distributed actions
+spec:
+ lifecycle: experimental
+ type: backstage-cli-module
+ owner: tooling-maintainers
diff --git a/packages/cli-module-actions/cli-report.md b/packages/cli-module-actions/cli-report.md
new file mode 100644
index 0000000000..bbbab62b3f
--- /dev/null
+++ b/packages/cli-module-actions/cli-report.md
@@ -0,0 +1,94 @@
+## CLI Report file for "@backstage/cli-module-actions"
+
+> Do not edit this file. It is a report generated by `yarn build:api-reports`
+
+### `backstage-cli-module-actions`
+
+```
+Usage: @backstage/cli-module-actions [options] [command]
+
+Options:
+ -V, --version
+ -h, --help
+
+Commands:
+ actions [command]
+ help [command]
+```
+
+### `backstage-cli-module-actions actions`
+
+```
+Usage: @backstage/cli-module-actions actions [options] [command] [command]
+
+Options:
+ -h, --help
+
+Commands:
+ execute
+ help [command]
+ list
+ sources [command]
+```
+
+### `backstage-cli-module-actions actions execute`
+
+```
+Usage: @backstage/cli-module-actions actions execute
+
+Options:
+ --instance
+ -h, --help
+```
+
+### `backstage-cli-module-actions actions list`
+
+```
+Usage: @backstage/cli-module-actions actions list
+
+Options:
+ --instance
+ -h, --help
+```
+
+### `backstage-cli-module-actions actions sources`
+
+```
+Usage: @backstage/cli-module-actions actions sources [options] [command] [command]
+
+Options:
+ -h, --help
+
+Commands:
+ add
+ help [command]
+ list
+ remove
+```
+
+### `backstage-cli-module-actions actions sources add`
+
+```
+Usage: @backstage/cli-module-actions actions sources add
+
+Options:
+ -h, --help
+```
+
+### `backstage-cli-module-actions actions sources list`
+
+```
+Usage: @backstage/cli-module-actions actions sources list
+
+Options:
+ -h, --help
+```
+
+### `backstage-cli-module-actions actions sources remove`
+
+```
+Usage: @backstage/cli-module-actions actions sources remove
+
+Options:
+ -h, --help
+```
diff --git a/packages/cli-module-actions/package.json b/packages/cli-module-actions/package.json
new file mode 100644
index 0000000000..138eff06cf
--- /dev/null
+++ b/packages/cli-module-actions/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "@backstage/cli-module-actions",
+ "version": "0.0.2-next.0",
+ "description": "CLI module for executing distributed actions",
+ "backstage": {
+ "role": "cli-module"
+ },
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.cjs.js",
+ "types": "dist/index.d.ts"
+ },
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "packages/cli-module-actions"
+ },
+ "license": "Apache-2.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "files": [
+ "dist",
+ "bin"
+ ],
+ "bin": "bin/backstage-cli-module-actions",
+ "scripts": {
+ "build": "backstage-cli package build",
+ "clean": "backstage-cli package clean",
+ "lint": "backstage-cli package lint",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack",
+ "test": "backstage-cli package test"
+ },
+ "dependencies": {
+ "@backstage/cli-node": "workspace:^",
+ "@backstage/errors": "workspace:^",
+ "cleye": "^2.3.0",
+ "zod": "^3.25.76 || ^4.0.0"
+ },
+ "devDependencies": {
+ "@backstage/backend-test-utils": "workspace:^",
+ "@backstage/cli": "workspace:^"
+ }
+}
diff --git a/packages/cli-module-actions/report.api.md b/packages/cli-module-actions/report.api.md
new file mode 100644
index 0000000000..180186482d
--- /dev/null
+++ b/packages/cli-module-actions/report.api.md
@@ -0,0 +1,13 @@
+## API Report File for "@backstage/cli-module-actions"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { CliModule } from '@backstage/cli-node';
+
+// @public (undocumented)
+const _default: CliModule;
+export default _default;
+
+// (No @packageDocumentation comment for this package)
+```
diff --git a/packages/cli-module-actions/src/commands/execute.ts b/packages/cli-module-actions/src/commands/execute.ts
new file mode 100644
index 0000000000..f130c597ce
--- /dev/null
+++ b/packages/cli-module-actions/src/commands/execute.ts
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { cli } from 'cleye';
+import type { CliCommandContext } from '@backstage/cli-node';
+import { ActionsClient } from '../lib/ActionsClient';
+import { schemaToFlags } from '../lib/schemaToFlags';
+import { resolveAuth } from '../lib/resolveAuth';
+
+export default async ({ args, info }: CliCommandContext) => {
+ if (args.includes('--help') || args.includes('-h')) {
+ cli(
+ {
+ help: info,
+ parameters: [''],
+ flags: {
+ instance: {
+ type: String,
+ description: 'Name of the instance to use',
+ },
+ },
+ },
+ undefined,
+ args,
+ );
+ return;
+ }
+
+ const instanceIdx = args.indexOf('--instance');
+ const instanceFlag = instanceIdx !== -1 ? args[instanceIdx + 1] : undefined;
+
+ // Skip flag names, flag values (the argument after a known flag), and
+ // the --instance value position so we only pick up positional arguments.
+ const skipIndices = new Set();
+ if (instanceIdx !== -1) {
+ skipIndices.add(instanceIdx);
+ skipIndices.add(instanceIdx + 1);
+ }
+
+ let actionId: string | undefined;
+ let actionIdIdx = -1;
+ for (let i = 0; i < args.length; i++) {
+ if (!skipIndices.has(i) && !args[i].startsWith('-')) {
+ actionId = args[i];
+ actionIdIdx = i;
+ break;
+ }
+ }
+
+ if (!actionId) {
+ process.stderr.write('Usage: actions execute [flags]\n');
+ process.exit(1);
+ }
+
+ const { accessToken, baseUrl } = await resolveAuth(instanceFlag);
+
+ const client = new ActionsClient(baseUrl, accessToken);
+ const actions = await client.listForPlugin(actionId);
+ const action = actions.find(a => a.id === actionId);
+
+ if (!action) {
+ throw new Error(
+ `Action "${actionId}" not found. Run "actions list" to see available actions.`,
+ );
+ }
+
+ const schemaFlags = schemaToFlags(action.schema.input as any);
+
+ const flagArgs = args.filter((_, i) => i !== actionIdIdx);
+
+ const { flags } = cli(
+ {
+ help: info,
+ flags: {
+ instance: {
+ type: String,
+ description: 'Name of the instance to use',
+ },
+ ...schemaFlags,
+ },
+ },
+ undefined,
+ flagArgs,
+ );
+
+ const allFlags = flags as Record;
+ const input: Record = {};
+ for (const [key, value] of Object.entries(allFlags)) {
+ if (key !== 'instance' && value !== undefined) {
+ input[key] = value;
+ }
+ }
+
+ const output = await client.execute(actionId, input);
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
+};
diff --git a/packages/cli-module-actions/src/commands/list.ts b/packages/cli-module-actions/src/commands/list.ts
new file mode 100644
index 0000000000..2049fc3e44
--- /dev/null
+++ b/packages/cli-module-actions/src/commands/list.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { cli } from 'cleye';
+import type { CliCommandContext } from '@backstage/cli-node';
+import { ActionsClient } from '../lib/ActionsClient';
+import { resolveAuth } from '../lib/resolveAuth';
+
+export default async ({ args, info }: CliCommandContext) => {
+ const {
+ flags: { instance: instanceFlag },
+ } = cli(
+ {
+ help: info,
+ flags: {
+ instance: {
+ type: String,
+ description: 'Name of the instance to use',
+ },
+ },
+ },
+ undefined,
+ args,
+ );
+
+ const { accessToken, pluginSources, baseUrl } = await resolveAuth(
+ instanceFlag,
+ );
+
+ if (!pluginSources.length) {
+ process.stderr.write(
+ 'No plugin sources configured. Run "actions sources add