diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md
index 861f454280..d89eae87b0 100644
--- a/docs/frontend-system/building-plugins/01-index.md
+++ b/docs/frontend-system/building-plugins/01-index.md
@@ -238,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/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 (
+
+
+
+);
+```
+
+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/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..a758d63ebe 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
diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md
index 2872fdcda9..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.
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 2d0e34ba4b..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).
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 417b932451..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
diff --git a/docs/plugins/integrating-search-into-plugins.md b/docs/plugins/integrating-search-into-plugins.md
index 7a7201d9e6..cbd9c89e14 100644
--- a/docs/plugins/integrating-search-into-plugins.md
+++ b/docs/plugins/integrating-search-into-plugins.md
@@ -4,6 +4,12 @@ title: Integrating Search into a plugin
description: How to integrate Search into a Backstage plugin
---
+:::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
and interfaces needed to offer search experiences within their plugins, while
abstracting away (and instead empowering application integrators to choose) the
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/microsite/sidebars.ts b/microsite/sidebars.ts
index ec4585b407..1f4eb7e0ef 100644
--- a/microsite/sidebars.ts
+++ b/microsite/sidebars.ts
@@ -410,60 +410,6 @@ export default {
sidebarElementWithIndex({ label: 'Okta' }, ['integrations/okta/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: 'Configuration',
@@ -597,6 +543,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(
@@ -666,6 +615,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(
@@ -719,6 +680,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'],
diff --git a/mkdocs.yml b/mkdocs.yml
index 618f77a59f..19c2098496 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -141,7 +141,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'
@@ -162,9 +162,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'