> ## Documentation Index
> Fetch the complete documentation index at: https://backstage.spotify.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Setup & Installation

> Install and configure the Insights backend and frontend plugins, set up the sign-in resolver, and enable usage data collection.

## Prerequisites

### Configure the Sign-In Resolver

To allow Insights to identify the user entity associated with the signed-in user, your **SignInResolver** must issue an identity token with a **sub** (subject) claim pointing to the user entity in the catalog corresponding to the signed-in user. Check out [Backstage user identity documentation on backstage.io](https://backstage.io/docs/auth/identity-resolver/#backstage-user-identity) for complete details on how to supply an identity resolver.

> Note: If you alter the Sign-In Resolver and the returned subject claim changes for a signed in user, this will affect the ability to correlate data between the old and new subject claims and active user counts may appear inflated.

While many authentication providers handle this automatically, if you're using a custom provider, you might need to implement this behavior yourself. Refer to examples in the [Google auth sign-in resolver](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/src/providers/google/provider.ts) or the [GitHub auth sign-in resolver](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/src/providers/github/provider.ts). For more information on configuring a sign-in resolver, see the [identity resolver documentation on backstage.io](https://backstage.io/docs/auth/identity-resolver).

## Backend Installation

### Obtain the Plugin

Add the Insights backend as a dependency to your Backstage backend app:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
yarn workspace backend add @spotify/backstage-plugin-insights-backend
```

### Integrate the Plugin

You can integrate the Insights backend plugin with your Backstage backend like this:

```ts packages/backend/src/index.ts highlight={6} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { createBackend } from '@backstage/backend-defaults';

const backend = createBackend();

backend.add(import('@backstage/plugin-app-backend/alpha'));
backend.add(import('@spotify/backstage-plugin-insights-backend'));
// ...

backend.start();
```

#### Legacy Backend

<Warning>
  If you are still using the Legacy Backend you can follow these instructions
  but we highly recommend migrating to the [New Backend
  System](https://backstage.io/docs/backend-system/building-backends/migrating).
</Warning>

Create a new file named `packages/backend/src/plugins/backstageInsights.ts` with the following content:

```ts packages/backend/src/backstageInsights.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Router } from 'express';
import { PluginEnvironment } from '../types';
import { createRouter } from '@spotify/backstage-plugin-insights-backend';

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  return await createRouter({
    ...env,
  });
}
```

Next, wire up the Insights backend in `packages/backend/src/index.ts`. Import the module from the previous step, create a plugin environment, and add the router to the Express app.

```ts packages/backend/src/index.ts highlight={6,13,20} theme={"theme":{"light":"github-light","dark":"dracula"}}
...

import proxy from './plugins/proxy';
import techdocs from './plugins/techdocs';
import search from './plugins/search';
import backstageInsights from './plugins/backstageInsights';

...

const techdocsEnv = createEnv('techdocs');
const searchEnv = createEnv('search');
const appEnv = createEnv('app');
const backstageInsightsEnv = createEnv('backstageInsights');

...

apiRouter.use('/techdocs', await techdocs(techdocsEnv));
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/search', await search(searchEnv));
apiRouter.use('/backstage-insights', await backstageInsights(backstageInsightsEnv));
```

**Note:** be sure to use "backstage-insights" exactly as your route in the above file

### Create an API Token for Your Organization

To secure communication between your Backstage backend and Spotify's servers, you need to create an API token. Visit [backstage.spotify.com/account/tokens](https://backstage.spotify.com/account/tokens/) and create a token with a descriptive name, such as `backstage-insights-production-token`. Note down the token value as you'll need it in the next step.

### Configure the Spotify Backstage API Token

In your `app-config.yaml`, add the following entry to enable communication with Spotify's servers:

```yaml app-config.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
spotify:
  apiToken: ${SPOTIFY_BACKSTAGE_API_TOKEN}
```

Set the `SPOTIFY_BACKSTAGE_API_TOKEN` environment variable to the API token you created earlier. Ensure that you treat this value as a secret and avoid checking it into version control.

## Additional Configuration

### Extra Entity Metadata Capture

The Insights backend integrates with the Software Catalog to enhance analytics events with additional entity metadata. This allows filtering or grouping data based on attributes like the entity's `type`. By default, the following fields are captured on analytics events associated with catalog entities: `kind`, `metadata.namespace`, `spec.type`, `spec.lifecycle`, and `spec.owner`. To capture extra fields, provide them in the configuration. Common fields can be found in the [Catalog's YAML File Format documentation](https://backstage.io/docs/features/software-catalog/descriptor-format). You can also reference custom metadata stored on entities.

```yaml app-config.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
backstageInsights:
  captureEntityFields:
    - spec.someCustomField
```

Similarly, certain fields are captured on all analytics events related to the signed-in user, such as `metadata.namespace` and `spec.memberOf`. You can configure additional user metadata capture in the configuration as well:

```yaml app-config.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
backstageInsights:
  captureIdentityFields:
    - spec.profile.someCustomField
```

Note that the Insights backend applies redaction logic to avoid collecting personal data, but you should understand the data collected when configuring these additional fields.

### Identity groups filter

By default, membership groups for users are captured with analytic events, which are then used in the frontend to filter and segment the data on dashboards. However, some groups may not be logical representations of groups that you'd want to segment by. As an example, you may populate these groups from an external source that is used for application management, and therefore have no relation to Backstage usage.

For this reason, we provide a captureIdentityGroupsFilter config option to specify which types of groups you want to capture.

Some examples:

* Only capture specific group types

```yaml app-config.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
backstageInsights:
  captureIdentityGroupsFilter:
    'spec.type':
      - department
      - guild
      - team
```

* Only capture groups annotated with an insights flag

```yaml app-config.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
backstageInsights:
  captureIdentityGroupsFilter:
    'metadata.includeInInsights': true
```

### Total Users Count Filter

The Insights frontend uses the Software Catalog to determine the total number of users who can access Backstage. This number is essential for various dashboards and views. If the count is incorrect due to factors like retaining deactivated users or gradual rollouts, you can apply filters using an [EntityFilterQuery](https://backstage.io/api/stable/types/_backstage_catalog-client.index.EntityFilterQuery.html). Examples include counting members of a specific group or users under a certain namespace.

```yaml app-config.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
backstageInsights:
  totalUsersEntityQueryFilter:
    'relations.memberOf': 'group:default/rnd'
```

For more details, refer to the [EntityFilterQuery documentation on backstage.io](https://backstage.io/api/stable/types/_backstage_catalog-client.index.EntityFilterQuery.html).

### Host Filter

While it's recommended that, in local and pre-production environments, you set `backstageInsights.debug` to `true`, there may be a variety of scenarios where that's not possible or practical to standardize. You can set a host filter to ensure that dashboards only display data that's been collected from your production environment.

For instance, if your production Backstage app is accessible to employees at `backstage.acme.net`, you could add the following in your `app-config.yaml`:

```yaml app-config.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
backstageInsights:
  hostFilter: backstage.acme.net
```

### Scaffolder Template Time Savings

The Insights plugin can show time savings data in aggregate and on a template-by-template basis. This can help you quantify the impact that your templates are having on developer productivity and experience beyond a simple count of template runs.

In order to see time savings data, you'll need to configure a `backstage.io/time-saved` annotation on each of your Scaffolder templates definitions. For example:

```yaml template.yaml highlight={6-7} theme={"theme":{"light":"github-light","dark":"dracula"}}
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: create-react-app-template
  title: Create React App Template
  annotations:
    backstage.io/time-saved: PT8H
```

The value of the annotation is expected to be a valid ISO 8601 duration.

For more details, see the relevant [Backstage Catalog descriptor format docs](https://backstage.io/docs/features/software-catalog/descriptor-format/#metadataannotationsbackstageiotime-saved-optional).

## Frontend Installation - Adding the Insights API

### New Frontend System

The Insights Analytics Module is now included by default with the Insights plugin.

#### Get the Plugin

Add the Insights frontend as a dependency to your Backstage frontend app:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
yarn workspace app add @spotify/backstage-plugin-insights
```

#### Install the Insights frontend

```tsx packages/app/src/App.tsx theme={"theme":{"light":"github-light","dark":"dracula"}}
import { createApp } from '@backstage/frontend-defaults';
import BackstageInsights from '@spotify/backstage-plugin-insights/alpha';

const app = createApp({
  ...
  features: [BackstageInsights],
});

export default app.createRoot();
```

Alternatively, [enable Feature Discovery](https://backstage.io/docs/frontend-system/architecture/app/#feature-discovery) to automatically discover and install the Insights plugin.

### Legacy Frontend System

<Warning>
  If you are still using the Legacy Frontend you can follow these instructions
  but we highly recommend migrating to the [New Frontend
  System](https://backstage.io/docs/frontend-system/building-apps/migrating).
</Warning>

#### Adding the Insights API

Add the Insights Analytics Module as a dependency to your Backstage frontend app:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
yarn workspace app add @spotify/backstage-plugin-analytics-module-insights
```

#### Supply the BackstageInsights API implementation in your app

This is typically done in `/packages/app/src/apis.ts`, or less often directly in the call to `createApp()`.

```ts packages/app/src/apis.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
...
import { BackstageInsights } from '@spotify/backstage-plugin-analytics-module-insights';

export const apis = [
  ...
  BackstageInsights.createDefaultApiFactory(),
];
```

### Configure the Backstage Insights API

Generate a random string, for example with the following terminal one-liner:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
node -p 'require("crypto").randomBytes(24).toString("base64")'
```

Then add the following configuration to your `app-config.yaml`, substituting the random string you just generated for the `salt` value. The salt is used to hash user identifiers in the insights event data that is sent to Spotify. If you change this value after data has been collected, you will lose the ability to correlate new data with the old data and active user counts may appear inflated, as such distinct users counts will be reset.

```yaml app-config.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
backstageInsights:
  salt: randomly-generated-string-here
  debug: true
```

> Note: This value is secret to Spotify (meaning: Spotify should never have access to this value), but it does not need to be treated as a secret within your organization (meaning: feel free to check this value in).

The `debug` property is set to `true`, which will output data to local logs instead of sending it to the production API. Don't forget to supply an override in your `app-config.production.yaml`:

```yaml app-config.production.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
backstageInsights:
  debug: false
```

### Check everything is working

Start up backstage locally. For the sake of testing, you could provide any necessary overrides as environment variables inline, like this:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
SPOTIFY_BACKSTAGE_API_TOKEN="{your token}" \
  APP_CONFIG_backstageInsights_debug=false \
  yarn dev
```

Once the app is built and running, open the app in your browser and open the browser's network console; check for requests being made against the insights backend with a path containing `backstage-insights/events`. As you navigate around Backstage, those requests should receive a `204` response if everything is configured correctly.

Once you've verified your configuration locally, deploy your changes to production (ensuring that you properly set the `SPOTIFY_BACKSTAGE_API_TOKEN` variable in your production environment). Perform the same network request checks in your production Backstage.

Please note that data is processed in batches with a slight delay. The dashboards only show full complete days so events from the present day are not displayed.

### Troubleshooting

If you're not seeing what's described in the `Check everything is working section`, here are a few common misconfigurations to be aware of:

* Are you seeing a `401` or `404` response instead of a `204`? There are two key items to check:
  * Check that you've set the `SPOTIFY_BACKSTAGE_API_TOKEN` environment variable as expected.
  * Be sure that you set the path correctly when you wired up the Insights backend. It should be `/backstage-insights`, as in `apiRouter.use('/backstage-insights', /* etc */)`.
* Are you not seeing any matching network requests at all? Check that you've set the `backstageInsights.salt` value as expected. A missing value could result in the analytics API instantiation failing silently. It's also possible that `backstageInsights.debug` is mistakenly set to `true` (if this is the case, you'll see backstageInsights logs in your browser's console log). Make sure that your app is loading in all relevant configuration files (`app-config.yaml` as well as `app-config.production.yaml`).
* Are you seeing an error similar to `tried to register duplicate or forbidden API factory` when you load the frontend? This is likely because you already have an analytics implementation supplied in your `apis.ts` file. To resolve, you can use the `MultipleAnalyticsApi` class to supply your existing analytics implementation alongside Backstage Insights. In place of `BackstageInsights.createDefaultApiFactory()`, you could do something similar to the following:

```ts packages/app/src/apis.ts highlight={10-12,32-42} theme={"theme":{"light":"github-light","dark":"dracula"}}
import {
  analyticsApiRef,
  configApiRef,
  createApiFactory,
  discoveryApiRef,
  fetchApiRef,
  identityApiRef,
  storageApiRef,
} from '@backstage/core-plugin-api';
import { MultipleAnalyticsApi } from '@backstage/core-app-api';
import { BackstageInsightsApi } from '@spotify/backstage-plugin-analytics-module-insights';
import { YourExistingAnalyticsApi } from '@somewhere/else';

export const apis = [
  ...
  createApiFactory({
    api: analyticsApiRef,
    deps: {
      configApi: configApiRef,
      discoveryApi: discoveryApiRef,
      fetchApi: fetchApiRef,
      identityApi: identityApiRef,
      storageApi: storageApiRef,
    },
    factory: ({
      configApi,
      discoveryApi,
      fetchApi,
      identityApi,
      storageApi,
    }) =>
      MultipleAnalyticsApi.fromApis([
        BackstageInsightsApi.fromConfig(configApi, {
          discoveryApi,
          fetchApi,
          identityApi,
          storageApi,
        }),
        YourExistingAnalyticsApi.fromConfig(configApi, {
          ...
        }),
      ]),
    ...
  }),
];
```

## Frontend Installation - Adding the UI

### New Frontend System

The UI was added when you installed the Insights plugin in the previous step. Skip ahead to the [Configure Access Controls](#configure-access-controls) section.

### Legacy Frontend System

<Warning>
  If you are still using the Legacy Frontend you can follow these instructions
  but we highly recommend migrating to the [New Frontend
  System](https://backstage.io/docs/frontend-system/building-apps/migrating).
</Warning>

#### Get the Plugin

Add the Insights frontend as a dependency to your Backstage frontend app:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
yarn workspace app add @spotify/backstage-plugin-insights
```

#### Install the Insights frontend

Wire up routing to the `InsightsPage` entry page. The route should look something like this:

```tsx packages/app/src/App.tsx highlight={1-5,8,17,29} theme={"theme":{"light":"github-light","dark":"dracula"}}
import {
  InsightsPage,
  insightsPlugin,
  InsightsSurveyLoader,
} from '@spotify/backstage-plugin-insights';
import {
  SearchPage,
  searchPlugin
} from '@backstage/plugin-search';

...

const routes = (
  <FlatRoutes>
    <Navigate key="/" to="catalog" />
     ...
     <Route path="/insights" element={<InsightsPage />} />
     ...
  </FlatRoutes>
);

/* Add the SurveyLoader, which renders configured pop-up surveys */

export default app.createRoot(
  <>
    <AlertDisplay />
    <OAuthRequestDialog />
    <AppRouter>
      <InsightsSurveyLoader />
      <Root>{routes}</Root>
    </AppRouter>
  </>,
);
```

Add a sidebar menu item that routes to the path setup in the previous step:

```tsx packages/app/src/components/Root/Root.tsx highlight={2,8} theme={"theme":{"light":"github-light","dark":"dracula"}}
...
import { InsightsSidebarItem } from '@spotify/backstage-plugin-insights'
...

<SidebarScrollWrapper>
  <SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
    ...
    <InsightsSidebarItem />
    ...
</SidebarScrollWrapper>;

...
```

### Configure Access Controls

The Insights plugin integrates with Backstage's permission framework and the [RBAC plugin](https://backstage.spotify.com/plugins/rbac/). This integration enables restricting which users/groups can view Insights dashboards as well as Create, Read, Update, or Delete (CRUD) Insights surveys and responses.

If you currently permission access to catalog resources, you'll likely want to configure who can access Insights as it could potentially expose information from the catalog that users might not normally have permissions to see.

The recommended approaches to setting access controls for this plugin require the Backstage permissions framework. You may have set this up already when configuring other plugins from the Spotify bundle, but if not, [follow this guide to setting up the permissions framework](https://backstage.io/docs/permissions/overview).

#### Option 1: Use the RBAC Plugin (Recommended)

*"The [RBAC plugin](https://backstage.spotify.com/plugins/rbac) is a no-code management UI for restricting access to plugins, routes, and data within Backstage. Admins can quickly define roles, assign users and groups, and configure permissions to encode authorization decisions according to your organization's evolving security and compliance needs."*

To have access to Insights permissions within the RBAC UI, be sure to add `backstage-insights` to the list of `permissionedPlugins` in your `app-config.yaml`.

```yaml app-config.yaml highlight={6} theme={"theme":{"light":"github-light","dark":"dracula"}}
  permission:
    enabled: true
   permissionedPlugins:
     - catalog
     - scaffolder
     - backstage-insights
   ...
```

Using the [RBAC plugin](../rbac/), it's easy to define access to Insights by configuring `Allow` for all of the Insights permissions:

<Frame>
  <img
    src="https://mintcdn.com/spotify-89f50c35/u5FqPpFSnmv3K-GI/plugins/insights/images/insights-rbac-setup.png?fit=max&auto=format&n=u5FqPpFSnmv3K-GI&q=85&s=33684f55356a7d806357ed596209d428"
    alt="An Insights Admin role set up via the RBAC
plugin"
    width="1930"
    height="1162"
    data-path="plugins/insights/images/insights-rbac-setup.png"
  />
</Frame>

See the [Available Permissions](#available-permissions) section for details on what the permissions entail.

Here is a basic sample `any-allow` policy that allows general access to the catalog and templates but restricts access to the Insights plugin to the group `backstage-admins`:

```yaml sample-policy.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
name: Sample Insights Policy
description: null
options:
  resolutionStrategy: any-allow
roles:
  - name: Default user
    members: '*'
    permissions:
      - match:
          resourceType: scaffolder-action
        decision: allow
      - match:
          resourceType: scaffolder-template
        decision: allow
      - match:
          resourceType: catalog-entity
        decision: allow
  - name: Admin
    members:
      - group:default/backstage-admins
    permissions:
      - match: '*'
        decision: allow
```

> To get started with this sample policy, save the content as a `yaml` file and follow the [RBAC import policy instructions](../rbac/setup-and-installation#import-and-export-a-policy).

#### Option 2: Use Custom Policy Handlers

If you don't use the RBAC plugin, you can define [custom policy handlers](https://backstage.io/docs/permissions/writing-a-policy) for permission framework policies.

```ts packages/backend/src/plugins/permissions.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
class ExamplePolicy implements PermissionPolicy {
  async handle(request: PolicyQuery): Promise<PolicyDecision> {
    if (request.permission.name === 'insights.read') {
      const isAllowed = await someEvaluationFunction();

      if (isAllowed) {
        return {
          result: AuthorizeResult.ALLOW,
        };
      }
    }

    return { result: AuthorizeResult.DENY };
  }
}
```

### Integrate the Plugin with Your Frontend App

Install the Insights routes in your app within `packages/app/src/App.tsx` and configure the route location for the Search plugin. You can also add a sidebar item for easy access to Insights if needed.

### Verify Functionality

After setting up access controls, users with the `insights.read` permission should see an Insights sidebar menu item. Ensure that the Backend plugin is correctly configured and data has been collected for at least 48 hours for the dashboards to display meaningful information.

## Available Permissions

The Insights plugin has the following permissions that can be used to control access to the plugin and its features:

| Permission               | Description                                                                                                                              |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `insights.read`          | Allows a user to access the Insights UI, view dashboards and data. This is also required in tandem with all of the following permissions |
| `insights.survey.read`   | Allows a user to view Insights surveys and any associated responses                                                                      |
| `insights.survey.update` | Allows a user to edit or close an existing Insights survey                                                                               |
| `insights.survey.delete` | Allows a user to delete an existing Insights survey and all associated responses                                                         |
| `insights.survey.create` | Allows a user to create an Insights survey via the Insights Feedback UI. Also allows a user to duplicate an existing survey              |
