Playlist lets you create, share, and follow collections of catalog entities in Backstage. Think of it as saved sets of services, websites, docs, or any entity type. You group what matters, keep it in one place, and come back to it fast. You can keep a playlist private or share it with your team. You can follow playlists from others to stay in sync.
The plugin adds an index where engineers browse, search, and sort playlists. Each playlist has a page with its description and its entities. You can create a new playlist, edit details, or remove one when it is no longer useful. Adding entities is simple. Do it from the playlist page or from an entity page using the add to playlist dialog. It reads from your catalog so the contents stay current.
Common uses include onboarding sets for new hires, runbooks for incident response, release or migration trackers, or collections for audits. Platform teams use it to highlight golden paths. Individual engineers use it to pin the services they touch every week. You can customize the term shown in the UI if you prefer a name like Collections. You can also compose your own index view to match your portal. The goal is simple. Help your teams discover and organize the things they use most.
Installation Instructions
These instructions apply to self-hosted Backstage only.
Install the frontend package
Run this in your Backstage root
yarn --cwd packages/app add @backstage-community/plugin-playlist
Register the frontend API client
Wire the client in your app apis file
// packages/app/src/apis.ts
import {
createApiFactory,
discoveryApiRef,
fetchApiRef,
} from '@backstage/core-plugin-api';
import {
playlistApiRef,
PlaylistClient,
} from '@backstage-community/plugin-playlist';
export const apis = [
// keep your existing factories
createApiFactory({
api: playlistApiRef,
deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
factory: ({ discoveryApi, fetchApi }) =>
new PlaylistClient({ discoveryApi, fetchApi }),
}),
];
Add routes to your app
Import the pages then add the routes
// packages/app/src/App.tsx
import React from 'react';
import { FlatRoutes, Route } from '@backstage/core-app-api';
import { CatalogIndexPage, CatalogEntityPage } from '@backstage/plugin-catalog';
import {
PlaylistIndexPage,
PlaylistPage,
} from '@backstage-community/plugin-playlist';
export const App = () => (
<FlatRoutes>
<Route path="/catalog" element={<CatalogIndexPage />} />
<Route
path="/catalog/:namespace/:kind/:name"
element={<CatalogEntityPage />}
/>
<Route path="/playlist" element={<PlaylistIndexPage />} />
<Route path="/playlist/:playlistId" element={<PlaylistPage />} />
</FlatRoutes>
);
Add a sidebar link
// packages/app/src/components/Root/Root.tsx
import React, { PropsWithChildren } from 'react';
import { Sidebar, SidebarItem, SidebarPage } from '@backstage/core-components';
import PlaylistPlayIcon from '@material-ui/icons/PlaylistPlay';
export const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarPage>
<Sidebar>
<SidebarItem icon={PlaylistPlayIcon} to="playlist" text="Playlists" />
{children}
</Sidebar>
</SidebarPage>
);
Add the entity page menu to support add to playlist
Imports
// packages/app/src/components/catalog/EntityPage.tsx
import React, { ReactNode, useMemo, useState } from 'react';
import { EntityLayout } from '@backstage/plugin-catalog';
import { EntityPlaylistDialog } from '@backstage-community/plugin-playlist';
import PlaylistAddIcon from '@material-ui/icons/PlaylistAdd';
Wrapper
// place this near the top of EntityPage.tsx after imports
const EntityLayoutWrapper = (props: { children?: ReactNode }) => {
const [playlistDialogOpen, setPlaylistDialogOpen] = useState(false);
const extraMenuItems = useMemo(
() => [
{
title: 'Add to playlist',
Icon: PlaylistAddIcon,
onClick: () => setPlaylistDialogOpen(true),
},
],
[],
);
return (
<>
<EntityLayout UNSTABLE_extraContextMenuItems={extraMenuItems}>
{props.children}
</EntityLayout>
<EntityPlaylistDialog
open={playlistDialogOpen}
onClose={() => setPlaylistDialogOpen(false)}
/>
</>
);
};
Wrap your entity routes
// still in EntityPage.tsx
const defaultEntityPage = (
<EntityLayoutWrapper>
<EntityLayout.Route path="/" title="Overview">
{overviewContent}
</EntityLayout.Route>
<EntityLayout.Route path="/docs" title="Docs">
<EntityTechdocsContent />
</EntityLayout.Route>
<EntityLayout.Route path="/todos" title="TODOs">
<EntityTodoContent />
</EntityLayout.Route>
</EntityLayoutWrapper>
);
Optional config for a custom title
# app-config.yaml
playlist:
title: Collection
Install the backend plugin
Add the backend package
yarn --cwd packages/backend add @backstage-community/plugin-playlist-backend
New backend system
Register the module in your backend index
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
// other backend modules you already add
// example
// backend.add(import('@backstage/plugin-catalog-backend'));
backend.add(import('@backstage-community/plugin-playlist-backend'));
backend.start();
This exposes the service at the playlist discovery service id. The frontend client uses discovery to reach it.
Old backend system
Create the plugin router
// packages/backend/src/plugins/playlist.ts
import { createRouter } from '@backstage-community/plugin-playlist-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
logger: env.logger,
database: env.database,
config: env.config,
});
}
Wire the router
// packages/backend/src/index.ts
import playlist from './plugins/playlist';
// inside your main bootstrap where apiRouter is created
const playlistEnv = useHotMemoize(module, () => createEnv('playlist'));
apiRouter.use('/playlist', await playlist(playlistEnv));
// keep the rest of your routers
// serviceBuilder.addRouter('/api', apiRouter) already exists in most apps
Now the backend serves at the api playlist path. The frontend client calls it through discovery.
Changelog
This changelog is produced from commits made to the Playlist plugin since a year ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.
Performance
- Improve playlist entity query speed by using getEntitiesByRefs. Large playlists load much faster. #4010 merged 4 months ago
Documentation
- Update README links to point to the community plugins repository. #3931 merged 4 months ago
Dependencies
- Update types uuid to v10 in dev dependencies. #2233 merged 7 months ago
- Remove unused dev dependency canvas. #3565 merged 5 months ago
Maintenance
Set up Backstage in minutes with Roadie
Focus on using Backstage, rather than building and maintaining it.