Writing catalog-info.yaml: the Backstage entity descriptor format

A practical guide to the catalog-info.yaml descriptor in Backstage: the fields every entity needs, the mistakes teams make, and how to validate files before registration.

On this page

Why catalog-info.yaml decides whether your Backstage catalog is trusted

A platform team with dozens or hundreds of repositories often opens its Backstage portal to find an empty catalog, or one full of duplicate, unowned and half-described entries. The fix starts with catalog-info.yaml, the descriptor file that declares each entity: what it is, what it is called, who owns it and how it fits with other entities. Its fields decide what the Software Catalog shows and who is accountable when something breaks. The Software Catalog is one of the core built-in features listed in the Backstage architecture overview, so the descriptor is effectively the contract between a team's repository and the portal.

Teams need this discipline most when they register components through Git rather than by hand. In that model, the descriptor committed alongside the code becomes the source of truth, and the catalog is only as accurate as the files it reads. A missing spec.owner, a metadata.name reused across two repositories or a spec.lifecycle value nobody agreed on will each surface later as a confusing catalog entry, a broken ownership view or a team that cannot find its own service. Decide early who may create and edit descriptors, which conventions for names, tags and lifecycle values are mandatory, and whether a file is validated before it is registered.

Before you roll out descriptors widely, confirm the schema and validation behavior for the Backstage release you run, since the catalog model and its checks evolve with each release line described in the Backstage release and versioning policy. Pilot the format on a small group of repositories, record the errors your teams hit, and turn the repeated ones into a checklist or automated check in CI. A catalog that teams trust is one where the descriptors are consistent, owned and validated before they reach production.

Descriptor fields and the kinds teams most often need

This table covers the descriptor fields and entity kinds that most catalog-info.yaml files use, with when each fits and the mistake teams make most often. Read it top to bottom when drafting a file, or jump to the field you are unsure about. The main takeaway is that most broken registrations come from a wrong kind, a reference to an entity that does not exist, or a name that collides with another entity.

Field or kindWhen it fitsUsual mistake
apiVersionAlways set to the catalog API version your Backstage instance expects, copied from current Backstage examples.Copying an old example file whose apiVersion no longer matches the catalog's accepted values, so the file is rejected at ingestion.
kind: ComponentA deployable or buildable piece of software such as a service, website or library.Using Component for a database or bucket, which belongs to Resource, or for an API contract, which belongs to API.
kind: APIA contract that one or more Components provide or consume, such as an OpenAPI or AsyncAPI definition.Registering the implementation as an API, so the same thing appears twice and relations point at the wrong node.
kind: SystemA group of Components and Resources that work together toward one function.Creating one System per team instead of per product function, so ownership and dependency views become hard to read.
kind: DomainA business area that groups several Systems, such as payments or onboarding.Leaving Domain out entirely, or modeling a Domain that has no Systems beneath it.
kind: ResourceInfrastructure a Component depends on, such as a database, queue or storage bucket.Describing the database inside the Component file, so it cannot be owned or referenced separately.
kind: Group and UserOwners and people, usually imported from an organizational data provider rather than hand-written.Hand-writing Group or User files that drift from the identity directory, or referencing a team name that was never ingested.
kind: LocationA pointer that tells the catalog which other files to read, used for registering files in bulk.Using Location as if it were an entity type for a service, then wondering why it does not show in the catalog graph.
metadata.nameUnique within its namespace and kind, lowercase, and stable for the life of the entity.Renaming a service, which creates a new entity while the old one becomes an orphan, or reusing a name already taken by another entity.
metadata.namespaceSet only when you need to separate entities that share a name, otherwise the default namespace is used.Assuming namespace provides access control or ownership, when it is only a naming boundary.
metadata.descriptionA short plain-language summary that helps people find and recognize the entity in search.Pasting a long README into the field or leaving it empty, which makes search results hard to scan.
metadata.tagsShort keywords for search and filtering, such as language or compliance scope.Using tags as a substitute for relations or ownership, which search cannot enforce.
metadata.annotationsLinks a catalog entry to external tools and enables plugin features, such as source location or on-call references.Copying annotation keys from an older plugin guide that has since been renamed or deprecated.
spec.typeA free-text subtype the catalog uses for display, such as service, website or library for a Component.Inventing values that no plugin or template recognizes, so filters and dashboards show them under unexpected categories.
spec.lifecycleShows where the entity sits in its life, such as production, experimental or deprecated, and should be reviewed when status changes.Setting it once at registration and never updating it, so deprecated services still appear as production.
spec.ownerReferences the Group or User accountable for the entity, using a name that exists in the catalog.Pointing at a team name spelled differently from the directory entry, which leaves the entity with no visible owner.
spec.systemLinks a Component, API or Resource to the System it belongs to.Referencing a System that is not registered, or placing the same Component in two Systems.
spec.dependsOnDeclares the Resources or Components this entity needs at runtime, so the graph shows the dependency.Listing only the databases and forgetting upstream services, which hides the blast radius of an outage.

Which fields teams get wrong and why it breaks the graph

The most damaging mistake in a catalog-info.yaml is treating metadata.name as a label that can change freely. Within a kind and namespace, metadata.name is the identity of the entity. The catalog keys entities on that identity, so renaming a component in the file does not rename the existing entity. It registers a new one and leaves the old one behind, where it becomes an orphan with its annotations, relations and ownership history still attached. Before you roll out a descriptor standard, decide who may rename entities, require that renames go through a reviewed change, and check for duplicate names across repositories, since two files claiming the same name in the same namespace will collide. Confirm the uniqueness rules and default namespace behavior against the official descriptor documentation for your Backstage version.

The spec.owner and spec.system fields are references, not labels. An owner must point to a Group or User entity that actually exists in the catalog, and a system must name a System entity. When a reference is misspelled or points to a group that was never ingested, Backstage does not reject the component. The entity still loads, but it drops out of the ownership views and team pages that filter on that owner, so the gap is easy to miss until someone asks why a team's services are missing. Lifecycle values deserve the same care. Treat spec.lifecycle as a governance signal that reporting and policy can act on, and restrict it to the small set of values your organization has agreed to. Confirm the exact accepted kinds and lifecycle values for your Backstage version in the official descriptor reference, and validate files in CI before they are registered.

How to write, validate and register a descriptor file

This sequence takes a repository from an undecided entity model to a catalog-info.yaml that validates, registers through the catalog configuration and shows the expected owner and relations in the Backstage catalog UI. Each step ends with a check so that errors are caught before the file reaches the portal.

  1. Agree the entity model with the platform team

    Before anyone edits YAML, the repository owner and the platform team agree which kinds the repository produces: usually one Component, sometimes an API or a Resource, and whether it belongs to a System. They also confirm the owner group name, the lifecycle value and the naming convention for metadata.name, because names must be unique within a kind and namespace. The output is a short written decision that the descriptor will follow. Check: the owner group exists or is planned as a Group entity in the catalog.

  2. Write catalog-info.yaml at the repository root

    A developer creates catalog-info.yaml at the root of the repository and fills in apiVersion, kind, metadata.name, metadata.description, spec.type, spec.lifecycle and spec.owner, using the values agreed in the previous step. The owner field should reference a Group or User by its name, and the lifecycle should use one of the values the platform team has approved. The output is a single-entity file in version control. Check: every required field is present and the owner reference matches an existing name in the catalog.

  3. Add extra entities as additional documents

    When one repository supplies several entities, such as a Component and its API, the developer adds each entity as a separate YAML document in the same file, separating documents with a line containing three hyphens (---). Each document needs its own kind and metadata.name, and names must not collide with entities already in the catalog. The output is a multi-document file that still reads as one descriptor. Check: each document parses on its own and its name is unique across the catalog.

  4. Validate the file before it is committed

    The developer runs the validation available in their repository or pipeline, such as a YAML linter for syntax and a schema check against the Backstage entity format, and fixes any reported field, indentation or kind errors. Validation confirms structure only, so the developer should also review names and references by hand. The output is a file that passes checks in CI or locally. Check: the validation job exits without errors on the branch that adds the file. Confirm the exact tooling your Backstage version expects in its documentation.

  5. Register the location in the catalog configuration

    A platform engineer adds the file's location to the catalog configuration, either as a static location in the app configuration or through a provider that discovers files in the organization's repositories. The team confirms which method the portal uses, since registration by hand and discovery behave differently when files are removed. The output is a configuration change merged and deployed to the Backstage backend. Check: the backend starts without configuration errors after the change is deployed.

  6. Confirm the entity in the catalog UI

    After the backend has processed the location, the developer opens the catalog and searches for the entity by name. They confirm the owner link opens the correct Group or User, the relations to Systems, APIs or dependencies appear as expected, and the entity's error panel shows no processor errors. If a processor reports an error, the developer fixes the descriptor in the repository and waits for the next processing cycle. The output is an entity whose owner and relations match the agreed model. Check: the entity page shows no errors and the owner and relations are correct.

One file or several: how multiple entities per file change ownership

A repository that holds one deployable service usually needs one entity, and a single catalog-info.yaml at the root is the simplest form. Multiple documents in one file help when the same team owns a small set of closely related entities, such as a Component and its API, and the file stays short enough to read in one review. The trade-off is that every entity in the file shares the same change history, so an edit to one document appears in the same pull request as the others.

  • Keep the default case simple: one Component document is enough when the repository has one owner and one deployable unit.
  • Add the API only when it has its own contract: a second document of kind API belongs in the same file when the service team publishes and versions that interface.
  • Check the owner on every document: each document needs its own spec.owner, even when the values match.
  • Watch the name scope: metadata.name must be unique for each kind within its namespace, so a Component and an API may share a name only if the catalog rules allow it for that combination.
  • Confirm the file location: confirm with your catalog configuration that the root path is the location the catalog reads.

Before you merge, confirm that every document has a distinct kind and name pair and that the owner reference points to a group your organization actually maintains.

Common validation failures before registration

Most descriptor problems surface as entities that never appear, or appear with errors, in the catalog. The cards below cover the failures teams hit most often, the symptom each one produces, and the first thing to check when a file does not register as expected.

Invalid apiVersion or kind

A descriptor with a misspelled or outdated apiVersion, or a kind the catalog does not recognize, is rejected during processing. The symptom is usually a validation error in the catalog errors view rather than a missing entity in the list. Check that apiVersion is backstage.io/v1alpha1 or the version your Backstage release documents, and that kind matches a built-in kind such as Component, API, Resource, System, Domain, Group or User, with exact capitalization.

Duplicate metadata.name

Two entities of the same kind and namespace with the same metadata.name collide, and the catalog keeps only one of them. The symptom is an entity whose content changes depending on which repository was processed last, or a component that disappears after a second repository is registered. Check the entity reference in the catalog errors and search every repository you ingest for the same name under the same kind and namespace.

Owner references to missing groups

A spec.owner value that points to a Group or User the catalog has not ingested stays unresolved, so the entity shows no owner and ownership-based views miss it. The symptom is an entity that registers without errors but appears under no team. Check that the owner reference uses the correct kind:namespace/name form, that the Group exists in your organizational data source, and that the Group has been ingested before you judge the entity.

Unsupported lifecycle values

The spec.lifecycle field accepts a set of values, and a free-text value such as prod or live fails validation for the entity. The symptom is a registration error that names the field, and the entity stays out of the catalog until the value is fixed. Check the value against the lifecycle list your organization has agreed on, for example production or experimental, and confirm that your Backstage release accepts it before you standardize on it.

YAML indentation and quoting errors

A single misplaced space changes the structure of a descriptor, so a field silently moves under the wrong parent, or a value such as yes, no or 1.10 is parsed as a boolean or number. The symptom is a field that appears empty in the entity view, or a descriptor that fails to parse at all. Check the indentation of each level, quote values that look like booleans or versions, and run the file through a YAML linter before committing.

Entities in a file the catalog does not read

A descriptor named catalog-info.yaml only enters the catalog when a location points to it, through a registered URL, a discovery provider or a rule that matches the path. A file in a subfolder, or one with a different name, is silently ignored. The symptom is a repository with no entity at all and no error. Check the location configuration for the repository, confirm the file path matches what the provider scans, and verify that the file sits where the location expects it.

Who can change the catalog and what a bad descriptor exposes

A catalog-info.yaml file is code that the portal reads and displays to every signed-in user. Who can merge changes to these files, what they put in metadata and annotations, and how the portal learns about owners all determine what a bad descriptor exposes.

Repository write access as the control point

Anyone who can push to a repository that the catalog reads can change what the portal shows for that entity. Treat write access to the repository as write access to the catalog. Check which teams hold write or maintain roles on each repository, and confirm that a descriptor cannot be changed by someone outside the owning team without review.

Pull request review of descriptor changes

Require review on any change to catalog-info.yaml, including changes to owner, lifecycle, and tags. Reviewers should confirm that the owner is a real group, that the name is unique, and that annotations point to tools the team actually uses. Run the same validation in CI so malformed files fail before merge rather than after registration.

Secrets and internal hostnames in annotations

Annotations are displayed in the portal and are often readable by every user who can sign in. Keep credentials, tokens, and internal hostnames out of metadata and annotation values. Reference secrets by name only, and check during review that URLs in annotations point to pages that users with portal access should be able to reach.

How ownership data reaches the portal

The spec.owner field is a reference, and the group or user it names must exist in the catalog from an organization data source. Confirm which provider loads Group and User entities, and who can change those records. A descriptor that names a group the portal cannot resolve creates an ownerless entity that is hard to trace.

Sign-in resolvers and identity mapping

Sign-in resolvers decide which catalog identity a signed-in user receives. Follow the authentication guidance to configure resolvers so each provider resolves to the same identity, and do not offer guest access in production. Otherwise a user may act under an identity that does not match the owner groups they are meant to hold.

Audit trail through Git history

Git history records who changed a descriptor, when, and in which review. Use it as the audit trail for catalog changes rather than building a separate log. Confirm that merge commits are retained, that force pushes to protected branches are blocked, and that the history can answer who owned a component on a given date.

Questions teams ask before writing their first descriptor

How much effort does it take to describe a large software estate?

Effort scales with the number of repositories, how many of them already have a clear owner and how much metadata each service needs. Most of the work is not writing YAML. It is deciding which components, systems and resources exist, agreeing who owns each one and confirming that the names are unique. Teams with a current service inventory spend less time on discovery. Teams that start from nothing should expect a first pass to surface ownership gaps that take longer to close than the file itself. Ask any partner to size the work from a sample of repositories, not from a headcount guess.

Can our engineers write catalog-info.yaml files themselves?

Yes. A descriptor is a small YAML file kept in the same repository as the code it describes, so the team that owns a service can usually write and maintain it. The effort rises when teams need to choose a kind, pick a valid lifecycle value and reference owners that exist in the catalog. A short internal guide with two or three worked examples, plus a validation step in CI, lets most teams succeed without help. The platform team should review the first few files from each group to catch patterns that will be copied across the estate.

What must the platform team provide before teams start writing descriptors?

Teams need three things before they begin. First, a template file with the required fields and comments that explain each one. Second, naming rules that define how metadata.name values are formed and how they stay unique across the catalog. Third, a single agreed source of ownership, such as Group and User entities loaded from your identity directory, so that spec.owner values point to real records. Without these, teams invent their own conventions and the catalog accumulates duplicates and dangling references that are hard to clean up later.

How long does the first registration take before the catalog is usable?

The duration depends on how the descriptors are registered and how many sources the catalog reads. Registering a few repositories through a location is usually a short exercise, while a large estate fed by a discovery provider takes longer because the first full ingestion has to finish before entities appear. Processing intervals also set how quickly changes show up after the first load. Ask your delivery partner for a timeline based on your repository count, ownership data and provider choice, and confirm which steps depend on your own identity directory or access approvals.

Do we need a partner to get our first descriptors right?

Not always. A small team with one or two Backstage administrators and a clear ownership model can often write the first descriptors internally. A partner adds value when the estate is large, when ownership data is spread across several systems, or when the team lacks people who have worked with catalog kinds, relations and processors. A partner can also set up validation and review so that errors do not spread. If you engage one, ask for the scope, deliverables and handover in writing before work starts, since most engagements are fixed-scope.

What changes in descriptors when we upgrade Backstage?

Most descriptor files do not change when Backstage is upgraded, because the format is defined by the entity model rather than the framework version. Changes usually come from new kinds, deprecated annotations or validation rules that the catalog now enforces. The Backstage upgrade guidance recommends moving all @backstage packages together with yarn backstage-cli versions:bump and reviewing template changes in the Backstage Upgrade Helper. Keep descriptors under version control and run your validation step against every upgrade branch so that breaking rules surface before production.

Which parts of a descriptor create the most problems later?

Three areas cause most rework. Duplicate metadata.name values in the same namespace cause one entity to overwrite another. Owner references that point to groups nobody has loaded leave entities without a team. Lifecycle values chosen inconsistently make it hard to report on experimental or deprecated services. Each problem is cheap to prevent with a template and a validation step, and expensive to fix after hundreds of files have been registered. Check the naming rules and owner sources first, then review the lifecycle vocabulary your organization will use.

Should we validate descriptor files before registering them?

Yes. Validation before registration catches syntax errors, missing required fields and invalid kinds before they reach the catalog. Run the check in the same pipeline that merges the file, so that a broken descriptor blocks its own change rather than silently failing later. Pair the syntax check with a review of owner references and naming, because a file can be valid YAML and still point to a team that does not exist. Ask your team to document which checks are mandatory and which are advisory.

How DeployOpen works on this

DeployOpen scopes catalog descriptor work against the systems and repositories you already run, then builds and validates the descriptors in your own Git environment. Every engagement is fixed-scope, with deliverables and timelines agreed in writing before work starts.

How we scope

  • We inventory the repositories, services and owners that will carry a catalog-info.yaml file.
  • We agree which entity kinds, lifecycle values and naming rules the descriptors will follow.
  • We define the written scope, deliverables and timeline before any descriptor is committed.

How we build

  • We write descriptor templates and validate each file against the schema before registration.
  • We test uniqueness of metadata.name and owner references against your real organizational data.
  • We set up CI checks so malformed descriptors fail in review rather than during catalog ingestion.

How we hand over

  • We document the descriptor conventions, field ownership and naming rules for your platform team.
  • We deliver validation scripts and pipeline configuration in your repositories under your control.
  • We walk your engineers through adding, changing and retiring entities so the process continues without us.

How we support

  • Ongoing support, upgrades and continuous testing are available as a retainer.
  • We can review new descriptors and kinds as teams onboard and as Backstage releases change expectations.
  • We confirm with you which support scope, response expectations and review cadence fit your environment before work starts.

Sources and further reading

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.