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.
npx skills add bendaamerahmed/backstage-idp-plugin --skill backstage-plugin-migrategit clone --depth 1 https://github.com/bendaamerahmed/backstage-idp-pluginWrote 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.
[](https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-plugin-migrate)<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.
<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>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.
| Model | Per session | Once 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 |
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 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>/createAppfrom@backstage/app-defaults= legacy frontend;createFrontendPlugin/createAppfrom@backstage/frontend-defaults//alphaimports = NFS.createBackend+backend.add()= new backend;createRouter+plugins/*.tsenv wiring inpackages/backend/src= legacy backend. backstage.jsonrelease line recorded, workspace build and tests green onmainbefore 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,
yarnat the repo root.
Procedure
- 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/uirewrite into the same change (step 13). - Add the
/alphaentrypoint, do not rewritesrc/index.ts. Createsrc/alpha.tsxand add topackage.json:"exports": { ".": "./src/index.ts", "./alpha": "./src/alpha.tsx", "./package.json": "./package.json" }plus a matchingtypesVersionsmap with"alpha": ["src/alpha.tsx"]. Runyarn backstage-cli migrate package-exportsto sync subpath export config rather than hand-editing every field. Legacy consumers keep importing the root entrypoint. - Extract route refs to a shared module. In NFS, refs come from
@backstage/frontend-plugin-api(createRouteRef,createSubRouteRef,createExternalRouteRef) and carry noid— identity comes from theroutes/externalRouteskeys on the plugin. To avoid two incompatible ref objects for the same route, keep the single legacy ref and wrap it withconvertLegacyRouteRef/convertLegacyRouteRefsfrom@backstage/core-compat-apiinalpha.tsx. createPlugin→createFrontendPlugin. Inalpha.tsx, export default acreateFrontendPlugin({ pluginId, extensions: [...], routes, externalRoutes }).idbecomespluginId; theapisoption no longer exists (APIs are extensions). Extensions are not exported individually — only the plugin is.- Pages:
createRoutableExtension→PageBlueprint.PageBlueprint.make({ params: { path, routeRef, loader: () => import('./components').then(m => <m.Page />) } }). Thepathmoves out of the app's<Route>into the plugin — copy the exact path the app used, or every existing bookmark and<RouteRef>link breaks. - Sidebar entries. This is version-sensitive:
NavItemBlueprintexisted through v1.50 and is gone from v1.51 onward, wherePageBlueprint's optionaltitleandiconparams produce the nav entry. Check the installed@backstage/frontend-plugin-apitypes before writing either. App-side sidebar layout is overridden withNavContentBlueprintfrom@backstage/plugin-app-react, which receivesnavItems(take(id),rest(),withComponent()). - Entity page tabs →
EntityContentBlueprintfrom@backstage/plugin-catalog-react/alpha: paramspath,title,loader, optionalgroup,icon,routeRef,filter(an annotation expression string or an(entity) => boolean). Replace theisFooAvailable(entity)guard the app used inEntityLayout.Route if=withfilter.defaultPath/defaultTitleare errors on current lines — usepath/title. - Entity cards →
EntityCardBlueprintfrom the same package: paramsloader,filter, andtype('info' | 'content'). The oldvariantandgridSizesprops no longer exist; card placement is now the layout's concern. Context menu items useEntityContextMenuItemBlueprint. - Utility APIs →
ApiBlueprint.ApiBlueprint.make({ params: defineParams => defineParams({ api: fooApiRef, deps: { ... }, factory: deps => new FooClient(deps) }) }). Move thecreateApiRefdeclaration to the-reactpackage (or wherever legacy consumers already import it from) and re-export, so both systems share one ref object. Add the blueprint toextensions; delete theapisarray only once no legacy app installs the same factory, or the app fails on a duplicate API. - 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 tocreateExtensionwithcreateExtensionInputandcreateExtensionDataRef. - 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 withconvertLegacyPageExtensionandconvertLegacyEntityContentExtension/convertLegacyEntityCardExtension(@backstage/plugin-catalog-react/alpha). App-level leftovers useconvertLegacyAppOptions(apis, icons, components, themes, featureFlags) andconvertLegacyAppRoot(the JSX root), both passed tocreateApp({ features: [...] })from@backstage/frontend-defaults. - Backend migration. Replace the exported
createRouterentrypoint withcreateBackendPlugin({ pluginId, register(env) { env.registerInit({ deps: { ... }, async init({ ... }) { ... } }) } })from@backstage/backend-plugin-api. Map oldRouterOptionsfields to core services:logger→coreServices.logger,config→coreServices.rootConfig,discovery→coreServices.discovery, plusdatabase,auth,httpAuth,userInfo,scheduler,cache,urlReader,permissions,lifecycle,auditor. Register the router withhttpRouter.use(router)instead of returning it. KeepcreateRouterinternal, called byinit, and mark the public export@deprecated;export { fooPlugin as default }. Deletesrc/run.ts/src/service/standaloneServer.tsand adddev/index.tsusingcreateBackend()from@backstage/backend-defaults. Extensibility that used to be constructor options becomescreateExtensionPointdeclared in a sibling-nodepackage, registered viaenv.registerExtensionPoint, and consumed bycreateBackendModule({ pluginId, moduleId, register }). Confirm every service ref and thecreateRoutersignature against the installed types — these move. - 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.cssexactly once at the app root (packages/app/src/index.tsx) — never from a plugin. Navigating components (Link,ButtonLink,Tabs,Menu,TagGroup,Table) require aBUIProviderinside a React Router context; NFS apps get it from@backstage/plugin-app, legacy apps from the@backstage/core-app-apishell. Migrate one component tree at a time, use current tokens (--bui-bg-neutral-*;--bui-bg-surface-*and--bui-gray-*are deprecated), and readhttps://ui.backstage.io/per component — prop APIs still move (CheckboxtakesisSelected,CollapsiblebecameAccordion). MUI and BUI coexist; a half-migrated component does not. - Preserve tests. Move test files with their components; do not rewrite
assertions. Legacy
renderInTestApp/TestApiProvidercome from@backstage/test-utils, their NFS equivalents pluscreateExtensionTesterfrom@backstage/frontend-test-utils. Add onecreateExtensionTestertest per migrated extension asserting rendered output and resolved path; backend tests usestartTestBackendandmockServicesfrom@backstage/backend-test-utils.
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.
- 10d ago First seen · 171 lines · 41 tokens per session scan A 8cffe6be7101
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.
Other skills, from other repositories
liveview-patterns
Build LiveView: async data (assignasync), PubSub (check connected?), phx-change events, form components/modals/uploads, streams for lists, livepatch. Use when handling interactions, debugging events, or tracking Presence.
fusecore
Use when creating modules, understanding FuseCore structure, or implementing features in a FuseCore modular-monolith Laravel project.
nextjs-tanstack-form
Use when building forms in Next.js 16 with TanStack Form v1 — Server Actions, Zod validation, multi-step wizards, field arrays.
nextjs-stack
Use as the master reference for a full Next.js 16+ stack — Prisma 7, Better Auth, shadcn/ui, TanStack Form, Zustand. Not for core framework API details (use nextjs-16).
nextjs-server-components
Use when deciding Server vs Client Component boundaries in Next.js 16, fetching data directly in components, or streaming with Suspense.
expert-web-developer
Web development specialist for frontend and backend work — components, APIs, routing, state, styling, tests, refactors. Use for any task touching HTML/CSS/JS, React/Vue/Svelte, Node/Python web frameworks, REST/GraphQL endpoints, or web performance. Normally invoked by model-router.