backstage-catalog

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

A workflow for working with the Backstage Software Catalog, a system that records software entities such as services, components, and resources. It covers catalog definitions, imports, relationships, and missing or disappearing entries.

In plain words
What is it for?
Use it to model catalog entities, import them from other systems, debug entity providers and processors, and investigate orphaned or vanishing entities.
Why use it?
It helps diagnose what the catalog actually contains by checking the catalog API and the installed Backstage types before making changes.

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 model catalog entities, import them from other systems, debug entity providers and processors, and investigate orphaned or vanishing entities.

Compare 6 skills from other repositories ↓
Install with agentmods
npx agentmods add skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog
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-catalog
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-catalog

README.md
[![agentmods](https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog/github.svg)](https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog)
Your own site
<a href="https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog"><img src="https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog/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-catalog

Your own site · 80×15
<a href="https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog"><img src="https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog.svg" alt="Reviewed on agentmods" width="80" height="20"></a>
Per session 39 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 3,347 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 0 findings. 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.00039 $0.03347
Opus 5 $0.00019 $0.01673
Sonnet 5 $0.00008 $0.00669
Haiku 4.5 $0.00004 $0.00335

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

Security

Grade A, and why

backstage-catalog scanned grade A with 0 findings 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 11d 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.

Nothing flagged

None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.

plugins/backstage-idp/skills/backstage-catalog/SKILL.md · 95 lines

How it starts

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

Backstage Software Catalog

Model entities correctly, ingest them from external systems without destroying data, and diagnose what the catalog actually believes.

Preconditions

  • Release line from backstage.json; catalog packages resolved via yarn why @backstage/plugin-catalog-backend.
  • Backend generation: packages/backend/src/index.ts using createBackend() + backend.add(import('@backstage/plugin-catalog-backend')) is the new backend system. A CatalogBuilder in packages/backend/src/plugins/catalog.ts is the legacy backend — migrate it (backstage-plugin-migrate) before adding modules, or register through the builder and say so in your report.
  • Exact interface shapes (EntityProvider, EntityProviderConnection, CatalogProcessor, DeferredEntity, processingResult) read from the installed @backstage/plugin-catalog-node types, not from memory.
  • A running local backend or a reachable catalog base URL, plus a token if auth is enforced, before any debugging step.

Procedure

  1. Read the catalog's current belief before changing anything. Query the API rather than guessing:
    • GET /api/catalog/entities/by-query?filter=kind=component&fields=metadata.name,metadata.annotations — what exists and where it came from. POST to the same path for $all/$any/$not/$exists/$in predicates.
    • GET /api/catalog/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true — the orphan set.
    • GET /api/catalog/entity-facets?facet=kind — a kind census, fastest way to spot a whole integration that stopped ingesting.
    • GET /api/catalog/locations — registered roots. Static catalog.locations entries cannot be removed through this API.
  2. Model the entity before writing ingestion code. Envelope is apiVersion + kind + metadata + spec. metadata.name is 1–63 chars of alphanumerics separated by [-_.], unique per kind per namespace; metadata.namespace defaults to default. Kinds: Component, API, Resource, System, Domain, Group, User, Location, Template. Use metadata.title for display strings that cannot be a valid name. metadata.uid is output-only — never reference entities by uid.
  3. Write entity refs as [<kind>:][<namespace>/]<name>, lowercased. Kind and namespace default from context (spec.owner defaults to Group-ish org kinds, providesApis to api, namespace to the referring entity's). Produce refs with stringifyEntityRef from @backstage/catalog-model and parse with parseEntityRef; compare case-insensitively. Never hand-build refs with string concatenation across namespaces.
  4. Express relations through spec fields, never by hand. Processors emit relations from the spec; stitching merges incoming and outgoing edges into the final entity. relations and status written into a descriptor are discarded.
    • spec.ownerownedBy / ownerOf. This is the whole of ownership resolution: one owner ref per entity, normally a Group.
    • spec.system, spec.domain, spec.subcomponentOfpartOf / hasPart.
    • spec.providesApis, spec.consumesApisprovidesApi / apiProvidedBy, consumesApi / apiConsumedBy.
    • spec.dependsOndependsOn / dependencyOf; spec.memberOfmemberOf / hasMember; spec.parent, spec.childrenparentOf / childOf.
  5. Choose the ingestion mechanism deliberately.
    • External system, scheduled or webhook-driven, fits in memory → EntityProvider.
    • Enrichment, custom-kind validation, or a custom file format already inside the processing loop → CatalogProcessor.
    • Paginated source too large to hold in memory (100k+ records) → incremental entity provider from @backstage/plugin-catalog-backend-module-incremental-ingestion. Processors cannot delete entities; providers can, eagerly. That asymmetry decides most cases. Before writing anything, check whether a built-in or @backstage-community/plugin-catalog-backend-module-* provider already covers the source (backstage-repo-discovery).
  6. Scaffold rather than hand-roll: yarn new --select catalog-provider-module or yarn new --select catalog-processor-module. Both generate a plugins/catalog-backend-module-<id>-* package with the class, readProviderConfigs, schedule wiring, config.d.ts, tests, and a module.ts registered from packages/backend/src/index.ts.
  7. Wire the module against the right extension point in createBackendModule({ pluginId: 'catalog', moduleId: ... }):
    • catalogProcessingExtensionPoint (@backstage/plugin-catalog-node) → addEntityProvider(...), addProcessor(...).
    • catalogModelExtensionPoint (@backstage/plugin-catalog-node/alpha) → setEntityDataParser(...) for non-catalog-info.yaml formats, setFieldValidators(...) for envelope/metadata rules.
    • incrementalIngestionProvidersExtensionPointaddProvider({ provider, options }). Confirm the method names against the installed package's .d.ts before writing the call.
  8. Make the provider identity stable. getProviderName() names the provider's private entity bucket in the database and must be unique and unchanged across restarts and deploys. Renaming it abandons the old bucket; with the default orphanProviderStrategy those entities are deleted.
  9. Stamp every emitted entity with ANNOTATION_LOCATION (backstage.io/managed-by-location) and ANNOTATION_ORIGIN_LOCATION (backstage.io/managed-by-origin-location), both in <type>:<target> form (targets may contain colons — never split on the first one). Entities missing these are dropped at ingestion with only a warning log.
  10. Pick the mutation type. type: 'full' replaces the whole bucket — correct when you can batch-fetch the complete set, and only then. type: 'delta' with added/removed is correct for webhook and event streams, where you never see the whole set. Do not emit a full mutation built from a partially successful fetch; let the task throw and retry on the next schedule instead.
  11. Set locationKey on every DeferredEntity to a string identifying the provider instance (e.g. frobs-provider:${id}), and keep it constant. On a duplicate entity ref the catalog resolves:
    • existing entity has no location key → the incoming entity wins and takes it over;
    • existing key matches the incoming key → update;
    • existing key differs → the incoming entity is discarded, silently. This is the only defence against one provider taking over another's entities, so an entity emitted without a locationKey is permanently up for grabs.
  12. Handle upstream pagination and rate limits in the provider, not the processor. Schedule via scheduler.createScheduledTaskRunner with a frequency/timeout read from catalog.providers.<name>.schedule. For incremental providers, tune burstLength, burstInterval, restLength, backoff, and set rejectEmptySourceCollections: true plus rejectRemovalsAbovePercentage so a degraded upstream cannot delete the catalog.
  13. In processors, do no network I/O. Every processor runs on every entity every cycle. If you must call out, use the CatalogProcessorCache passed into preProcessEntity/postProcessEntity with an ETag and If-None-Match, and bump the cache key string whenever the cached shape or the processor logic changes.
  14. Implement processor methods for their actual stage, all of which run on every entity on every cycle:
    • preProcessEntity — enrichment, before validation. Filter by kind first; skip when the field already has a value so a catalog-info.yaml can override you.
    • validateEntityKindtrue for a kind you own and validated, false for a kind you do not recognise (passing it to other processors), throw to mark the entity invalid. Build it from entityKindSchemaValidator(schema) over a JSON schema exported from an isomorphic *-common package so frontend and backend share it.
    • postProcessEntity — emit relations and child entities via processingResult.relation / .entity / .location, errors via .generalError / .inputError / .notFoundError.
    • readLocation — only for genuinely new location types; prefer a provider.
  15. Register new kinds in config. If catalog.rules has an allow list, add the kind or nothing will be ingested. Use catalog.processorOptions.<processorName>.priority (default 20, lower runs earlier) when order matters — registration order is only guaranteed within a single module.
  16. Run locally and prove the loop: yarn start-backend, then trigger the provider's schedule and re-query the endpoints from step 1.

Read the full file on GitHub · 95 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. 11d ago First seen · 95 lines · 39 tokens per session scan A aa6078684b7c

Subscribe to this mod's changes

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

Related

Other skills, from other repositories

ash-framework

Ash Framework — resources, actions, policies, aggregates, calculations, AshPhoenix.Form, LiveView, migrations. Use when generating resources via mix ash.codegen, editing changes, checks, types, validations, or domain code interfaces.

oliver-kriska/claude-elixir-phoenix · 48 tokens

deploy

Elixir/Phoenix deployment patterns — Dockerfile, fly.toml, runtime.exs, mix release, rel/ overlays. Use when configuring Fly.io, Docker, CI/CD, health checks, or production migrations.

oliver-kriska/claude-elixir-phoenix · 46 tokens

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.

oliver-kriska/claude-elixir-phoenix · 51 tokens

tidewave-integration

Tidewave MCP runtime tools — debugging, smoke testing, live state inspection, SQL queries, hex docs. Use when evaluating code in a running Phoenix app.

oliver-kriska/claude-elixir-phoenix · 38 tokens

oban

Oban job processing — workers, perform/1 (OSS) and process/1 (Pro), queues, cron, retries, unique jobs, idempotency, Oban Pro (Workflow, Batch, Chunk, Smart Engine), Testing. Use when writing Oban workers, queue config, or debugging jobs.

oliver-kriska/claude-elixir-phoenix · 64 tokens

perf

Analyze Elixir/Phoenix performance — N+1 queries, assign bloat, ecto optimization, genserver bottlenecks. Use when slowness, timeouts, or high memory reported.

oliver-kriska/claude-elixir-phoenix · 43 tokens