A frontend library that helps other Backstage plugins interact with the catalog through React components, hooks, and utilities.
—
Extension system functions for creating custom entity cards and content extensions in the new Backstage frontend system. These features are experimental and subject to change.
Alpha features define extension data references for the new frontend system.
/**
* Extension data reference for entity content titles
* @alpha
*/
const entityContentTitleExtensionDataRef: ExtensionDataRef<string>;
/**
* Extension data reference for entity filters
* @alpha
*/
const entityFilterExtensionDataRef: ExtensionDataRef<
(ctx: { entity: Entity }) => boolean
>;Function for creating custom entity card extensions that can be displayed in entity overviews.
/**
* Creates an entity card extension for the new frontend system
* @param options - Configuration for the entity card extension
* @returns Extension instance for the card
* @alpha
*/
function createEntityCardExtension<TInputs extends AnyExtensionInputMap>(
options: EntityCardExtensionOptions<TInputs>
): Extension<any>;
interface EntityCardExtensionOptions<TInputs extends AnyExtensionInputMap> {
/** Unique identifier for the extension */
id: string;
/** Where to attach this extension (default: entity.content.overview cards) */
attachTo?: { id: string; input: string };
/** Whether the extension is disabled by default */
disabled?: boolean;
/** Extension inputs for dependency injection */
inputs?: TInputs;
/** Filter function to determine when card should be shown */
filter?: (ctx: { entity: Entity }) => boolean;
/** Loader function that returns the card component */
loader: (options: {
inputs: Expand<ExtensionInputValues<TInputs>>;
}) => Promise<JSX.Element>;
}Usage Examples:
import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha';
import { Entity } from '@backstage/catalog-model';
// Basic entity card extension
const myEntityCardExtension = createEntityCardExtension({
id: 'my-entity-card',
filter: ({ entity }: { entity: Entity }) => entity.kind === 'component',
loader: async () => {
// Dynamically import your card component
const { MyEntityCard } = await import('./MyEntityCard');
return <MyEntityCard />;
},
});
// Card with custom filter and attachment
const apiDocumentationCardExtension = createEntityCardExtension({
id: 'api-documentation-card',
attachTo: {
id: 'entity.content.overview',
input: 'cards'
},
filter: ({ entity }: { entity: Entity }) =>
entity.kind === 'api' && entity.spec?.definition,
loader: async ({ inputs }) => {
const { ApiDocumentationCard } = await import('./ApiDocumentationCard');
return <ApiDocumentationCard inputs={inputs} />;
},
});
// Card with inputs for dependency injection
const metricsCardExtension = createEntityCardExtension({
id: 'metrics-card',
inputs: {
metricsApi: metricsApiRef,
},
filter: ({ entity }: { entity: Entity }) =>
entity.kind === 'component' && entity.spec?.type === 'service',
loader: async ({ inputs }) => {
const { MetricsCard } = await import('./MetricsCard');
return <MetricsCard metricsApi={inputs.metricsApi} />;
},
});Function for creating custom entity content pages that appear as tabs in entity views.
/**
* Creates an entity content extension for the new frontend system
* @param options - Configuration for the entity content extension
* @returns Extension instance for the content page
* @alpha
*/
function createEntityContentExtension<TInputs extends AnyExtensionInputMap>(
options: EntityContentExtensionOptions<TInputs>
): Extension<any>;
interface EntityContentExtensionOptions<TInputs extends AnyExtensionInputMap> {
/** Unique identifier for the extension */
id: string;
/** Where to attach this extension (default: plugin.catalog.page.entity contents) */
attachTo?: { id: string; input: string };
/** Whether the extension is disabled by default */
disabled?: boolean;
/** Extension inputs for dependency injection */
inputs?: TInputs;
/** Route reference for the content page */
routeRef?: RouteRef;
/** Default path for the content tab */
defaultPath: string;
/** Default title for the content tab */
defaultTitle: string;
/** Filter function to determine when content should be shown */
filter?: (ctx: { entity: Entity }) => boolean;
/** Loader function that returns the content component */
loader: (options: {
inputs: Expand<ExtensionInputValues<TInputs>>;
}) => Promise<JSX.Element>;
}Usage Examples:
import { createEntityContentExtension } from '@backstage/plugin-catalog-react/alpha';
import { Entity } from '@backstage/catalog-model';
// Basic entity content extension
const deploymentContentExtension = createEntityContentExtension({
id: 'deployment-content',
defaultPath: '/deployments',
defaultTitle: 'Deployments',
filter: ({ entity }: { entity: Entity }) =>
entity.kind === 'component' && entity.spec?.type === 'service',
loader: async () => {
const { DeploymentContent } = await import('./DeploymentContent');
return <DeploymentContent />;
},
});
// Content extension with custom route and inputs
const monitoringContentExtension = createEntityContentExtension({
id: 'monitoring-content',
defaultPath: '/monitoring',
defaultTitle: 'Monitoring',
routeRef: monitoringRouteRef,
inputs: {
monitoringApi: monitoringApiRef,
},
filter: ({ entity }: { entity: Entity }) =>
entity.kind === 'component' &&
entity.metadata.annotations?.['monitoring.io/dashboard'],
loader: async ({ inputs }) => {
const { MonitoringContent } = await import('./MonitoringContent');
return <MonitoringContent monitoringApi={inputs.monitoringApi} />;
},
});
// API-specific content extension
const apiSpecContentExtension = createEntityContentExtension({
id: 'api-spec-content',
defaultPath: '/spec',
defaultTitle: 'API Spec',
filter: ({ entity }: { entity: Entity }) =>
entity.kind === 'api' && entity.spec?.definition,
loader: async () => {
const { ApiSpecContent } = await import('./ApiSpecContent');
return <ApiSpecContent />;
},
});Alpha features also re-export some utility functions for convenience.
/**
* Re-exported from utils module
* @alpha
*/
function isOwnerOf(owner: Entity, entity: Entity): boolean;
/**
* Re-exported from hooks module
* Hook for checking permissions on the current entity in context
* @alpha
*/
function useEntityPermission(
permission: ResourcePermission<'catalog-entity'>
): {
loading: boolean;
allowed: boolean;
error?: Error;
};Usage Examples:
import {
isOwnerOf,
useEntityPermission
} from '@backstage/plugin-catalog-react/alpha';
import { catalogEntityDeletePermission } from '@backstage/plugin-catalog-common';
function EntityActionsCard() {
const { entity } = useEntity();
const currentUser = useCurrentUser();
const canDelete = useEntityPermission(
catalogEntityDeletePermission
);
const isOwner = isOwnerOf(currentUser, entity);
return (
<div>
<h3>Actions</h3>
{isOwner && <button>Edit</button>}
{canDelete.allowed && !canDelete.loading && (
<button>Delete</button>
)}
</div>
);
}Extensions need to be registered with the Backstage app:
// In your app-config or plugin configuration
import {
myEntityCardExtension,
deploymentContentExtension
} from './extensions';
const app = createApp({
extensions: [
myEntityCardExtension,
deploymentContentExtension,
// ... other extensions
],
});Extensions support configuration through the app-config system:
// Extension with configuration
const configurableCardExtension = createEntityCardExtension({
id: 'configurable-card',
// Configuration is automatically generated with filters
loader: async () => {
const { ConfigurableCard } = await import('./ConfigurableCard');
return <ConfigurableCard />;
},
});Configuration in app-config.yaml:
extensions:
- entity.cards.configurable-card:
filter:
- isKind: component
- isType: service
disabled: falseAlpha extensions provide a migration path from the legacy plugin system:
// Legacy approach (deprecated)
export const EntityPage = () => (
<EntityLayout>
<EntityLayout.Route path="/" title="Overview">
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EntityAboutCard />
</Grid>
<Grid item xs={12} md={6}>
<MyCustomCard />
</Grid>
</Grid>
</EntityLayout.Route>
</EntityLayout>
);
// New alpha approach
const myCustomCardExtension = createEntityCardExtension({
id: 'my-custom-card',
loader: async () => {
const { MyCustomCard } = await import('./MyCustomCard');
return <MyCustomCard />;
},
});Install with Tessl CLI
npx tessl i tessl/npm-backstage--plugin-catalog-react