원클릭으로
backstage-frontend-plugin
Build Backstage frontend plugins with the new Frontend System.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Build Backstage frontend plugins with the new Frontend System.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | backstage-frontend-plugin |
| description | Build Backstage frontend plugins with the new Frontend System. |
| version | 0.1.0 |
This skill provides specialized knowledge and workflows for building Backstage frontend plugins using the New Frontend System. It guides the development of UI features including pages, navigation items, entity cards/content, and shared Utility APIs.
Use this skill when creating UI features for Backstage: pages, navigation items, entity cards/content, or shared Utility APIs.
Before building a frontend plugin, clearly understand:
Load reference files as needed based on the plugin requirements:
For Extension Development:
For Utility API Development:
For Testing:
Follow the Golden Path workflow below for implementation, referring to reference files as needed.
After implementing the plugin:
renderInTestAppcreateExtensionTesteryarn backstage-cli package test --coverage
Before publishing:
yarn new → select plugin; it generates plugins/<pluginId>/.createFrontendPlugin from @backstage/frontend-plugin-api. Export it as the default from src/index.ts.PageBlueprint, EntityContentBlueprint). These are lazy‑loaded using dynamic imports.createRouteRef (usually in src/routes.ts) and use them in blueprints.createApiRef + ApiBlueprint), consumed via useApi.@backstage/frontend-defaults discover plugin extensions when the plugin is included at app creation.PageBlueprint are mounted automatically.@backstage/app-defaults and manual FlatRoutes, add a <Route> that renders your page component directly.routeRefs in src/routes.tscreateSubRouteRef for nested pathssrc/index.ts)Use dynamic imports in loaders and wrap rendered elements in Suspense with a lightweight fallback. Add an error boundary for resilience:
// Suspense and error boundary around a lazy extension element
const Example = React.lazy(() => import('./components/ExamplePage'));
function ExampleWrapper() {
return (
<ErrorBoundary>
<React.Suspense fallback={<div>Loading…</div>}>
<Example />
</React.Suspense>
</ErrorBoundary>
);
}
ApiBlueprint and consume with useApiHide/show entity content based on permissions or ownership to avoid broken UX for unauthorized users:
import { usePermission } from '@backstage/plugin-permission-react';
import { somePermission } from '@backstage/plugin-permission-common';
export function ExampleEntityContent() {
const { loading, allowed } = usePermission({ permission: somePermission });
if (loading) return null;
if (!allowed) return null; // or render a friendly message/banner
return <div>Secret content</div>;
}
@testing-library/react to test extension outputuseRouteRef where navigation matters# From the repository root (interactive)
yarn new
# Select: frontend-plugin
# Enter plugin id (kebab case, e.g. example)
# Non-interactive (for AI agents/automation)
yarn new --select frontend-plugin --option pluginId=example --option owner=""
// src/api.ts
import { createApiRef } from '@backstage/frontend-plugin-api';
export interface ExampleApi {
getExample(): { example: string };
}
export const exampleApiRef = createApiRef<ExampleApi>({ id: 'plugin.example' });
export class DefaultExampleApi implements ExampleApi {
getExample() {
return { example: 'Hello World!' };
}
}
Register it with the ApiBlueprint and consume via useApi:
// src/plugin.ts
import { ApiBlueprint } from '@backstage/frontend-plugin-api';
import { exampleApiRef, DefaultExampleApi } from './api';
const exampleApi = ApiBlueprint.make({
name: 'example',
params: define =>
define({
api: exampleApiRef,
deps: {},
factory: () => new DefaultExampleApi(),
}),
});
export const examplePlugin = createFrontendPlugin({
pluginId: 'example',
extensions: [exampleApi, examplePage, exampleNavItem],
routes: { root: rootRouteRef },
});
import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha';
const exampleEntityContent = EntityContentBlueprint.make({
params: {
path: 'example',
title: 'Example',
loader: () =>
import('./components/ExampleEntityContent').then(m => <m.ExampleEntityContent />),
},
});
@backstage/frontend-defaults and your plugin is included at app creation.PageBlueprint.Run tests and lints with Backstage's CLI:
yarn backstage-cli package test
yarn backstage-cli package lint
yarn backstage-cli repo lint
Keep a predictable structure (API layer, hooks, components, routes.ts, plugin.ts, index.ts).
| Problem | Solution | Reference |
|---|---|---|
| Extensions don't render | Ensure they're passed in the plugin's extensions array; components must be lazy-loaded via dynamic imports | Backstage |
| Navigation/links break | Keep routeRefs in src/routes.ts and use useRouteRef to generate links | Backstage |
| Consumers can't install your plugin | Export the plugin as the default from src/index.ts | Backstage |
Load these resources as needed during development:
makeWithOverridescreateApiRefuseApirenderInTestAppcreateExtensionTesterTestApiProvider