Backstage Cataloger
Sync components and domains from a Backstage software catalog into Lunar. Maps Backstage entities to Lunar catalog with owner, domain, and tags, using the Backstage REST API.
backstage to your lunar-config.yml:uses: github://earthly/lunar-lib/catalogers/backstage@v1.0.5
What This Integration Syncs
This integration includes 1 cataloger that sync data from your systems.
sync
Fetches entities from the Backstage catalog REST API and writes
them to the Lunar catalog. Component entities populate
.components with owner, domain, and tags; Domain entities
populate .domains with description and owner. Components are
keyed by a configurable annotation (defaults to
github.com/project-slug) so they line up with components
discovered by the github-org cataloger or by repo-based
collectors.
0 2 * * *
How Catalogers Fit into Lunar
Lunar catalogers sync component metadata into your Lunar catalog from external systems or source code. They can run on a schedule or be triggered by code changes to keep your service registry up-to-date.
By automatically discovering components from GitHub organizations, service registries, or by detecting technology usage in source code, catalogers ensure your guardrails apply to all relevant services without manual configuration.
Learn How Lunar Works →Example Catalog Entry
This cataloger syncs component metadata into your Lunar catalog. Here's an example of a catalog entry it creates:
{
"components": {
"github.com/acme/payment-api": {
"owner": "group:default/team-payments",
"domain": "platform.payments",
"tags": ["bs-payments", "bs-tier1", "bs-type-service", "bs-lifecycle-production"]
},
"github.com/acme/web-app": {
"owner": "group:default/team-web",
"domain": "platform.frontend",
"tags": ["bs-frontend", "bs-type-website", "bs-lifecycle-production"]
}
},
"domains": {
"platform.payments": {
"description": "Payment processing and billing",
"owner": "group:default/platform-leads"
},
"platform.frontend": {
"description": "Customer-facing web surfaces",
"owner": "group:default/platform-leads"
}
}
}
Configuration
Configure this cataloger in your lunar-config.yml.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
backstage_url
|
Required | — | Base URL of the Backstage instance (e.g. https://backstage.example.com). The cataloger appends `/api/catalog/entities` to this URL. Required. |
entity_kinds
|
Optional |
Component,Domain
|
Comma-separated list of Backstage entity kinds to sync. Each kind is routed to the appropriate Catalog JSON path: - Component, API, Resource → `.components` - Domain, System → `.domains` Other kinds (User, Group, Location) are ignored. |
namespace
|
Optional |
default
|
Backstage namespace to query. Use `*` to query all namespaces. |
component_id_annotation
|
Optional |
github.com/project-slug
|
Annotation key on a Backstage Component whose value identifies the underlying repo. The cataloger reads this annotation and prepends `component_id_prefix` to build the Lunar component ID. Typical Backstage convention: `github.com/project-slug` (value `owner/repo`) or `backstage.io/source-location` (value `url:https://github.com/owner/repo`). |
component_id_prefix
|
Optional |
github.com/
|
String prepended to the value of `component_id_annotation` to form the Lunar component ID. For the default annotation `github.com/project-slug` the value is already `owner/repo`, so the prefix is `github.com/` to produce `github.com/owner/repo`. |
tag_prefix
|
Optional |
bs-
|
Prefix added to Backstage `metadata.tags` when mapped to Lunar tags. Also applied to derived tags like `type-<spec.type>` and `lifecycle-<spec.lifecycle>`. Empty string disables the prefix. |
include_derived_tags
|
Optional |
true
|
When `true`, emits derived tags from `spec.type` (e.g. `bs-type-service`) and `spec.lifecycle` (e.g. `bs-lifecycle-production`) in addition to `metadata.tags`. |
owner_format
|
Optional |
as-is
|
How to write `spec.owner` from Backstage into the Lunar `owner` field. Backstage entity refs typically look like `group:default/team-payments` or `user:default/jane`. - `as-is` — pass the Backstage value through verbatim. Matches what the existing `policies/backstage/*` checks accept (`team-payments`, `group:infra`, `user:alice` are all valid). - `bare-name` — strip the `<kind>:<namespace>/` prefix and write only the trailing name (e.g. `team-payments`). Useful when downstream systems want plain names. Email resolution via Backstage User/Group entities is out of scope for v1. |
default_owner
|
Required | — | Fallback owner applied (verbatim) to components and domains that have no `spec.owner` in Backstage. Format is whatever you want — entity ref, email, plain string — Lunar stores it as-is. Leave empty to skip entities without an owner. |
domain_default_description
|
Required | — | Fallback description for domains that have no `metadata.description` set in Backstage. |
filter
|
Required | — | Additional raw Backstage filter expression (passed through to the `?filter=` query parameter). Use to restrict the sync to a subset of entities (e.g. `metadata.annotations.team=platform`). Empty means no extra filter. |
Secrets
This cataloger requires the following secrets to be configured in Lunar:
| Secret | Description |
|---|---|
BACKSTAGE_TOKEN
|
Bearer token for the Backstage API. Required if the Backstage instance requires authentication; many internal deployments do. |
Documentation
View on GitHubBackstage Cataloger
Syncs components and domains from a Backstage software catalog into Lunar.
Overview
This cataloger reads entities from a Backstage instance via its REST API (/api/catalog/entities) and writes them into Lunar. Component entities populate .components (with owner, domain, tags); Domain entities populate .domains (description, owner). Use this when you run a Backstage instance and want Lunar to inherit its ownership/domain/tag metadata. Pair with backstage-catalog-info for per-repo catalog-info.yaml augmentation (component-cron, layerable). The per-repo backstage collector is a different shape entirely — it writes .catalog.native.backstage during local / CI Lunar runs.
Synced Data
This cataloger writes to the following Catalog JSON paths:
| Path | Type | Description |
|---|---|---|
.components[*].owner |
string | spec.owner of the Backstage Component (or default_owner fallback) |
.components[*].domain |
string | spec.domain of the Backstage Component |
.components[*].tags[] |
array | metadata.tags plus derived type-* / lifecycle-* tags, all with tag_prefix |
.domains[*].description |
string | metadata.description of the Backstage Domain |
.domains[*].owner |
string | spec.owner of the Backstage Domain |
Example Catalog JSON output
{
"components": {
"github.com/acme/payment-api": {
"owner": "group:default/team-payments",
"domain": "platform.payments",
"tags": ["bs-payments", "bs-tier1", "bs-type-service", "bs-lifecycle-production"]
},
"github.com/acme/web-app": {
"owner": "group:default/team-web",
"domain": "platform.frontend",
"tags": ["bs-frontend", "bs-type-website", "bs-lifecycle-production"]
}
},
"domains": {
"platform.payments": {
"description": "Payment processing and billing",
"owner": "group:default/platform-leads"
},
"platform.frontend": {
"description": "Customer-facing web surfaces",
"owner": "group:default/platform-leads"
}
}
}
Catalogers
This integration provides the following catalogers:
| Cataloger | Description |
|---|---|
sync |
Fetches entities from the Backstage catalog API and writes Components, Domains (and optionally Systems, APIs, Resources) to the Lunar catalog |
Hook Type
| Hook | Schedule | Description |
|---|---|---|
cron |
0 2 * * * |
Runs daily at 02:00 UTC |
Daily is the conservative default because a full /api/catalog/entities walk paginates through every entity in the Backstage instance — at thousands of components this is a non-trivial fetch against both the Backstage server and the Lunar Runner. Ownership, domain, and tag metadata also change on the order of hours-to-days, not minutes, so a nightly cycle covers the data velocity for almost every catalog. Smaller catalogs are free to tighten the cadence by overriding hook.schedule in their forked copy of lunar-cataloger.yml — promoting schedule to a with: input is a candidate v2 if anyone needs per-deployment tunability without a fork.
Installation
Add to your lunar-config.yml:
catalogers:
- uses: github.com/earthly/lunar-lib/catalogers/backstage@v1.0.0
with:
backstage_url: "https://backstage.example.com"
Authenticated Backstage
Most internal Backstage deployments require a bearer token. Configure it as a Lunar secret:
lunar secret set BACKSTAGE_TOKEN <your-token>
The cataloger reads LUNAR_SECRET_BACKSTAGE_TOKEN automatically — no extra with: is needed.
Layering with the GitHub Org Cataloger
For organisations that already run github-org to enumerate repos, run Backstage after it so its owner/domain/tag values override the GitHub defaults:
catalogers:
- uses: github.com/earthly/lunar-lib/catalogers/github-org@v1.0.0
with:
org_name: "acme"
- uses: github.com/earthly/lunar-lib/catalogers/backstage@v1.0.0
with:
backstage_url: "https://backstage.example.com"
Per Lunar's merge precedence, catalogers declared later override earlier ones.
Mapping Components to Repos
Backstage components are matched to Lunar components by reading an annotation on each Backstage Component entity. Defaults assume the standard github.com/project-slug annotation:
catalogers:
- uses: github.com/earthly/lunar-lib/catalogers/backstage@v1.0.0
with:
backstage_url: "https://backstage.example.com"
component_id_annotation: "github.com/project-slug" # value: "acme/payment-api"
component_id_prefix: "github.com/" # → "github.com/acme/payment-api"
For GitLab or other forges, point at the appropriate annotation:
with:
component_id_annotation: "gitlab.com/project-slug"
component_id_prefix: "gitlab.com/"
Restricting Synced Kinds
By default, Component and Domain entities are synced. Include other kinds explicitly:
with:
entity_kinds: "Component,Domain,System,API"
| Backstage kind | Synced to |
|---|---|
Component, API, Resource |
.components |
Domain, System |
.domains |
Other kinds (User, Group, Location, …) |
Ignored |
Filtering Entities
Pass a raw Backstage filter expression through filter:
with:
filter: "metadata.annotations.team=platform"
Owner Format
Backstage spec.owner is typically an entity reference like group:default/team-payments or user:default/jane, not an email. By default this cataloger passes the value through verbatim — matching what the existing policies/backstage/owner-set policy already accepts (team-payments, group:infra, user:alice are all valid).
If you'd rather store bare names, set owner_format: bare-name to strip the <kind>:<namespace>/ prefix. Resolving entity refs to emails by looking up the User/Group entity is intentionally out of scope for v1 — it adds API calls and only works when User/Group entities carry spec.profile.email.
default_owner is also written verbatim, so you can use whatever convention you prefer (entity ref, email, plain string).
Source System
This cataloger calls the Backstage Catalog REST API — specifically the /api/catalog/entities endpoint. It requires:
- Network reach from the Lunar Runner to the Backstage instance
- A bearer token (
LUNAR_SECRET_BACKSTAGE_TOKEN) if the instance enforces authentication - Read access to the kinds configured in
entity_kinds
Pagination is handled automatically; the cataloger streams pages until all matching entities are fetched.
Open Source
This cataloger is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.
Ready to Automate Your Standards?
See how Lunar can turn your AGENTS.md, engineering wiki, compliance docs, or postmortem action items into automated guardrails with our 200+ built-in guardrails.