backstage-plugin-migrate

backstage-plugin-migrate is a skill for Claude Code from bendaamerahmed/backstage-idp-plugin. It costs 41 tokens per session (3,032 once invoked), scanned A, original, MIT.

A guide for moving a Backstage plugin from its older frontend or backend system to the newer systems. Backstage plugins are extensions that add features to a developer portal, and dual exports let one package support both systems during the move.

In plain words
What is it for?
Use it to map a plugin's pages and integrations, add compatible exports, migrate frontend or backend code, and verify that behaviour remains the same.
Why use it?
It reduces the risk of changing what users see while the surrounding portal is being upgraded, and keeps older consumers working when needed.

Skill for Claude Code

Written for Claude Code: when-to-use in frontmatter.

Part of the backstage-idp plugin — 15 skills, 1 agent shipped together

Good fit Use it to map a plugin's pages and integrations, add compatible exports, migrate frontend or backend code, and verify that behaviour remains the same.

Compare 6 skills from other repositories ↓
Install with agentmods
npx agentmods add skills/bendaamerahmed/backstage-idp-plugin/backstage-plugin-migrate
Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

Any agent
npx skills add bendaamerahmed/backstage-idp-plugin --skill backstage-plugin-migrate
Clone the repo
git clone --depth 1 https://github.com/bendaamerahmed/backstage-idp-plugin

Made for: Claude Code.

Or install backstage-idp, the plugin that ships this one along with the rest of its 15 skills, 1 agent.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for backstage-plugin-migrate

README.md
[![agentmods](https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-plugin-migrate/github.svg)](https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-plugin-migrate)
Your own site
<a href="https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-plugin-migrate"><img src="https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-plugin-migrate/github.svg" alt="Measured on agentmods" height="20"></a>

Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.

agentmods 80×15 button for backstage-plugin-migrate

Your own site · 80×15
<a href="https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-plugin-migrate"><img src="https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-plugin-migrate.svg" alt="Reviewed on agentmods" width="80" height="20"></a>
Per session 41 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 3,032 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 1 finding. A grade says what 26 rules found in the file — not that it is safe.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5.1 $0.00041 $0.03032
Opus 5 $0.00020 $0.01516
Sonnet 5 $0.00008 $0.00606
Haiku 4.5 $0.00004 $0.00303

Measured 10d ago against content hash 8cffe6be7101, method: parsed. Prices are Anthropic first-party input rates as of 2026-09-10, from the pricing page.

Security

Grade A, and why

backstage-plugin-migrate scanned grade A with 1 finding against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 10d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Makes network callslowCapability

Not a fault in itself. Listed so you know the mod talks to something, and to what.

- Backend: `curl` each route through `/api/<pluginId>/...` and confirm identical
plugins/backstage-idp/skills/backstage-plugin-migrate/SKILL.md · 171 lines

How it starts

The opening of the file, as written. The whole thing — 171 lines — stays where its author put it; the contents beside it link to each section on GitHub.

Migrate a Backstage plugin to the new frontend/backend systems

Convert a legacy plugin package to the New Frontend System (NFS) and/or New Backend System without changing what users see, using dual exports so one package serves both systems during the transition. Scope, map, verify parity.

Preconditions

  • Generation known for both plugin and host app. Run backstage-repo-discovery, or grep: createPlugin/createRoutableExtension/<FlatRoutes>/createApp from @backstage/app-defaults = legacy frontend; createFrontendPlugin/createApp from @backstage/frontend-defaults//alpha imports = NFS. createBackend + backend.add() = new backend; createRouter + plugins/*.ts env wiring in packages/backend/src = legacy backend.
  • backstage.json release line recorded, workspace build and tests green on main before you touch anything. Migration on a red baseline is unverifiable.
  • The plugin's consumers are known. If it is published outside this repo, legacy exports must survive; if internal-only, they may be deleted at the end.
  • Node 22 or 24, yarn at the repo root.

Procedure

  1. Scope the migration. Inventory the plugin's surface: pages, entity page tabs, entity cards, utility APIs, route refs (internal and external), nav items, search result items, context-menu items, backend routers and modules. Each maps to exactly one blueprint below. Do NOT migrate frontend and backend in one commit, and never fold a Material UI → @backstage/ui rewrite into the same change (step 13).
  2. Add the /alpha entrypoint, do not rewrite src/index.ts. Create src/alpha.tsx and add to package.json: "exports": { ".": "./src/index.ts", "./alpha": "./src/alpha.tsx", "./package.json": "./package.json" } plus a matching typesVersions map with "alpha": ["src/alpha.tsx"]. Run yarn backstage-cli migrate package-exports to sync subpath export config rather than hand-editing every field. Legacy consumers keep importing the root entrypoint.
  3. Extract route refs to a shared module. In NFS, refs come from @backstage/frontend-plugin-api (createRouteRef, createSubRouteRef, createExternalRouteRef) and carry no id — identity comes from the routes/ externalRoutes keys on the plugin. To avoid two incompatible ref objects for the same route, keep the single legacy ref and wrap it with convertLegacyRouteRef / convertLegacyRouteRefs from @backstage/core-compat-api in alpha.tsx.
  4. createPlugincreateFrontendPlugin. In alpha.tsx, export default a createFrontendPlugin({ pluginId, extensions: [...], routes, externalRoutes }). id becomes pluginId; the apis option no longer exists (APIs are extensions). Extensions are not exported individually — only the plugin is.
  5. Pages: createRoutableExtensionPageBlueprint. PageBlueprint.make({ params: { path, routeRef, loader: () => import('./components').then(m => <m.Page />) } }). The path moves out of the app's <Route> into the plugin — copy the exact path the app used, or every existing bookmark and <RouteRef> link breaks.
  6. Sidebar entries. This is version-sensitive: NavItemBlueprint existed through v1.50 and is gone from v1.51 onward, where PageBlueprint's optional title and icon params produce the nav entry. Check the installed @backstage/frontend-plugin-api types before writing either. App-side sidebar layout is overridden with NavContentBlueprint from @backstage/plugin-app-react, which receives navItems (take(id), rest(), withComponent()).
  7. Entity page tabs → EntityContentBlueprint from @backstage/plugin-catalog-react/alpha: params path, title, loader, optional group, icon, routeRef, filter (an annotation expression string or an (entity) => boolean). Replace the isFooAvailable(entity) guard the app used in EntityLayout.Route if= with filter. defaultPath/defaultTitle are errors on current lines — use path/title.
  8. Entity cards → EntityCardBlueprint from the same package: params loader, filter, and type ('info' | 'content'). The old variant and gridSizes props no longer exist; card placement is now the layout's concern. Context menu items use EntityContextMenuItemBlueprint.
  9. Utility APIs → ApiBlueprint. ApiBlueprint.make({ params: defineParams => defineParams({ api: fooApiRef, deps: { ... }, factory: deps => new FooClient(deps) }) }). Move the createApiRef declaration to the -react package (or wherever legacy consumers already import it from) and re-export, so both systems share one ref object. Add the blueprint to extensions; delete the apis array only once no legacy app installs the same factory, or the app fails on a duplicate API.
  10. Remaining kinds. Search results use SearchResultListItemBlueprint (@backstage/plugin-search-react/alpha); app-level ones live in @backstage/plugin-app-react (SignInPageBlueprint, ThemeBlueprint, IconBundleBlueprint, TranslationBlueprint, SwappableComponentBlueprint). Only when no blueprint fits, drop to createExtension with createExtensionInput and createExtensionDataRef.
  11. Compatibility layer — only for plugins you do not own. In an NFS app, wrap a third-party legacy plugin with convertLegacyPlugin(legacyPlugin, { extensions: [...] }) from @backstage/core-compat-api, building those extensions with convertLegacyPageExtension and convertLegacyEntityContentExtension / convertLegacyEntityCardExtension (@backstage/plugin-catalog-react/alpha). App-level leftovers use convertLegacyAppOptions (apis, icons, components, themes, featureFlags) and convertLegacyAppRoot (the JSX root), both passed to createApp({ features: [...] }) from @backstage/frontend-defaults.
  12. Backend migration. Replace the exported createRouter entrypoint with createBackendPlugin({ pluginId, register(env) { env.registerInit({ deps: { ... }, async init({ ... }) { ... } }) } }) from @backstage/backend-plugin-api. Map old RouterOptions fields to core services: loggercoreServices.logger, configcoreServices.rootConfig, discoverycoreServices.discovery, plus database, auth, httpAuth, userInfo, scheduler, cache, urlReader, permissions, lifecycle, auditor. Register the router with httpRouter.use(router) instead of returning it. Keep createRouter internal, called by init, and mark the public export @deprecated; export { fooPlugin as default }. Delete src/run.ts / src/service/standaloneServer.ts and add dev/index.ts using createBackend() from @backstage/backend-defaults. Extensibility that used to be constructor options becomes createExtensionPoint declared in a sibling -node package, registered via env.registerExtensionPoint, and consumed by createBackendModule({ pluginId, moduleId, register }). Confirm every service ref and the createRouter signature against the installed types — these move.
  13. Material UI → @backstage/ui (BUI): separate change, separate PR, started only after the NFS migration is merged and green. yarn add @backstage/ui; import @backstage/ui/css/styles.css exactly once at the app root (packages/app/src/index.tsx) — never from a plugin. Navigating components (Link, ButtonLink, Tabs, Menu, TagGroup, Table) require a BUIProvider inside a React Router context; NFS apps get it from @backstage/plugin-app, legacy apps from the @backstage/core-app-api shell. Migrate one component tree at a time, use current tokens (--bui-bg-neutral-*; --bui-bg-surface-* and --bui-gray-* are deprecated), and read https://ui.backstage.io/ per component — prop APIs still move (Checkbox takes isSelected, Collapsible became Accordion). MUI and BUI coexist; a half-migrated component does not.
  14. Preserve tests. Move test files with their components; do not rewrite assertions. Legacy renderInTestApp/TestApiProvider come from @backstage/test-utils, their NFS equivalents plus createExtensionTester from @backstage/frontend-test-utils. Add one createExtensionTester test per migrated extension asserting rendered output and resolved path; backend tests use startTestBackend and mockServices from @backstage/backend-test-utils.

Read the full file on GitHub · 171 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 10d ago First seen · 171 lines · 41 tokens per session scan A 8cffe6be7101

Subscribe to this mod's changes

backstage-plugin-migrate is a skill published in the GitHub repository bendaamerahmed/backstage-idp-plugin (1 stars, last pushed 1mo ago), licensed MIT. It adds 41 tokens to every session and 3,032 once invoked, about $0.0002 per session on Opus 5. A static security scan graded it A with 1 finding (makes network calls). No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.