SuperAPI AI Directory

using-backstage-frontend-system

Frontend & websites

View repository

Updated 10-16 02:23

About this skill

Backstage New Frontend System (NFS) Patterns

When to use this skill

  • Defining a new frontend plugin (src/plugin.tsx)
  • Declaring route refs (src/routes.ts)
  • Exposing APIs from a plugin (src/apis.ts)
  • Adding pages, sub-pages, or other extensions (src/extensions.tsx)
  • Using NFS hooks (useApi, useRouteRef)

Choose the right pattern

Building... Pattern Reference
The plugin itself createFrontendPlugin({ pluginId, ..., extensions }) references/plugin-definition.md
Route refs createRouteRef, createSubRouteRef, createExternalRouteRef from @backstage/frontend-plugin-api references/route-refs.md
Calling a backend endpoint (default) A data hook wrapping useAsync / useAsyncFn around fetchApi.fetch — no client class, no extension references/api-extensions.md
Exposing an extensible API on an open-source plugin ApiBlueprint.make({ params }) references/api-extensions.md
A page (single route) PageBlueprint.make({ params: { path, routeRef, loader } }) references/page-extensions.md
A page with tabs PageBlueprint (no loader) + SubPageBlueprints references/sub-pages.md
Reading APIs in components useApi(apiRef), useRouteRef(routeRef) from @backstage/frontend-plugin-api references/hooks.md

Two invocation modes

  • Standalone: user is editing an existing NFS plugin. Pick a reference and apply.
  • Post-scaffold (called by creating-backstage-plugin for frontend-plugin / frontend-plugin-module templates): wire up the canonical NFS structure in the freshly scaffolded plugin — src/routes.ts, src/plugin.tsx using createFrontendPlugin, and an example PageBlueprint extension.

Common gotchas

  • In NFS, plugin-system imports come from @backstage/frontend-plugin-api, not @backstage/core-plugin-api. Hooks (useApi, useRouteRef), refs (identityApiRef, configApiRef, discoveryApiRef, fetchApiRef, storageApiRef, analyticsApiRef), and primitives (createApiRef) all live in the new package.
  • createRouteRef() takes no id argument — the id is derived from the extension that owns the route.
  • createExternalRouteRef({ defaultTarget: '<pluginId>.<routeName>' }) — set defaultTarget to the most common binding so apps don't need explicit bindRoutes calls.
  • useRouteRef(externalRef) may return undefined if the external route isn't bound — always handle the undefined case.
  • Page components inside a PageBlueprint loader should NOT include Page, Header, or PageWithHeader from @backstage/core-components. The framework's PageLayout renders PluginHeader automatically. Use Container from @backstage/ui as the outermost wrapper for page content. Use Header from @backstage/ui only when you need a subtitle or custom actions.
  • SubPageBlueprint paths are relative (no leading /). Sub-page components render content only — no Page, no Header, no HeaderTabs. Wrap sub-page content in Container from @backstage/ui if padding is needed.
  • createApiRef<T>().with({ id, pluginId }) — the builder form is preferred so ownership is explicit. The id must still be globally unique. Only the owning plugin (or a module targeting it) can override the API.
  • For any fetchApi.fetch call, handle non-OK responses with throw await ResponseError.fromResponse(response) from @backstage/errors — it parses the response body for structured error info and produces a typed error other Backstage UI surfaces (e.g. <ResponseErrorPanel>) understand. Never just throw new Error(\Failed: ${response.status}`)`.