Backstage Collector
Parses and lints Backstage catalog-info.yaml files. Writes the raw Backstage descriptor (apiVersion, kind, metadata, spec) to .catalog.native.backstage, preserving annotations as-is. Optionally verifies the referenced domain and system exist in a live Backstage catalog.
backstage to your lunar-config.yml:uses: github://earthly/lunar-lib/collectors/backstage@v1.0.5
What This Integration Collects
This integration includes 1 collector that gather metadata from your systems.
catalog-info
Scans the repository for a catalog-info.yaml file (configurable paths)
and parses its contents as-is into .catalog.native.backstage
(apiVersion, kind, metadata, spec). Annotations are preserved verbatim
with their backstage.io/ or vendor prefixes. Lints the file for
schema/syntax issues and writes any findings to
.catalog.native.backstage.valid and .catalog.native.backstage.errors[].
When backstage_url is configured, it additionally performs a
referential-integrity check: for each grouping reference declared in the
file (spec.domain, spec.system) it queries the Backstage catalog API
(GET /api/catalog/entities/by-name/<kind>/<namespace>/<name>) and
records the outcome under .catalog.native.backstage.refs. Whenever
backstage_url is set the collector writes .refs.checked: true as the
"referential integrity ran" signal, plus a per-reference entry: a
definitive lookup records .refs.<kind> = {name, exists} (exists: true
on a 200, false on a 404), while a transient failure (timeout/5xx)
records .refs.<kind> = {name, error} so an outage stays distinguishable
from a real miss. The <namespace> is derived from the reference itself —
a qualified ref (ns/name) carries its own, otherwise the component's own
metadata.namespace is used, falling back to default — so no namespace
configuration is required. When backstage_url is empty (the default) no
lookups run and .refs is not written at all — behavior is identical to
today, and the policy's referential-integrity checks skip (pass) rather
than fail.
How Collectors Fit into Lunar
Lunar watches your code and CI/CD systems to collect SDLC data from config files, test results, IaC, deployment configurations, security scans, and more.
Collectors are the automatic data-gathering layer. They extract structured metadata from your repositories and pipelines, feeding it into Lunar's centralized database where guardrails evaluate it to enforce your engineering standards.
Learn How Lunar Works →Example Collected Data
This collector writes structured metadata to the Component JSON. Here's an example of the data it produces:
{
"catalog": {
"native": {
"backstage": {
"valid": true,
"errors": [],
"path": "catalog-info.yaml",
"apiVersion": "backstage.io/v1alpha1",
"kind": "Component",
"metadata": {
"name": "payment-api",
"description": "Payment processing API",
"annotations": {
"backstage.io/techdocs-ref": "dir:.",
"pagerduty.com/integration-key": "PXXXXXX",
"grafana/dashboard-selector": "https://grafana.example.com/d/abc123",
"backstage.io/source-location": "url:https://github.com/acme/payment-api"
},
"tags": ["payments", "api", "tier1"]
},
"spec": {
"type": "service",
"owner": "team-payments",
"lifecycle": "production",
"domain": "payments",
"system": "payment-platform",
"providesApis": ["payment-api"],
"consumesApis": ["user-api", "notification-api"],
"dependsOn": ["resource:database-payments", "resource:cache-redis"]
},
"refs": {
"checked": true,
"domain": { "name": "payments", "exists": true },
"system": { "name": "payment-platform", "exists": true }
}
}
}
}
}
Configuration
Configure this collector in your lunar-config.yml.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
paths
|
Optional |
catalog-info.yaml,catalog-info.yml
|
Comma-separated list of catalog file paths to check (first match wins) |
backstage_url
|
Required | — | Base URL of the Backstage instance (e.g. https://backstage.example.com). When set, the collector cross-checks each declared spec.domain and spec.system against the Backstage catalog API and records the result under `.catalog.native.backstage.refs`. Empty (the default) disables referential-integrity lookups — the collector only parses and lints the local file, exactly as before. |
Secrets
This collector requires the following secrets to be configured in Lunar:
| Secret | Description |
|---|---|
BACKSTAGE_TOKEN
|
Bearer token for the Backstage catalog API, sent as an `Authorization: Bearer` header. Only used when `backstage_url` is set; required if the Backstage instance needs authentication (many internal deployments do). |
Documentation
View on GitHubBackstage Collector
Parses and lints Backstage catalog-info.yaml files.
Overview
This collector scans the repository for a Backstage catalog definition file (catalog-info.yaml or catalog-info.yml), parses it, and lints it for schema/syntax issues. The raw Backstage descriptor (apiVersion, kind, metadata, spec) is written to the .catalog.native.backstage Component JSON path as-is — annotations keep their original backstage.io/ or vendor prefixes. The search paths are configurable via the paths input.
Optionally, when a backstage_url is configured, it also cross-checks the domain and system referenced in catalog-info.yaml against the live Backstage catalog and records whether those entities exist under .catalog.native.backstage.refs.
Collected Data
When a catalog-info file is found, this collector writes to the following Component JSON paths. When no file is found, the .catalog.native.backstage namespace is not written — absence of the namespace is the signal.
| Path | Type | Description |
|---|---|---|
.catalog.native.backstage.valid |
boolean | Whether the catalog-info file passed lint/schema checks |
.catalog.native.backstage.errors[] |
array | Lint findings (each with line, message, severity) |
.catalog.native.backstage.path |
string | Relative path to the file that was parsed |
.catalog.native.backstage.apiVersion |
string | Backstage API version (e.g. backstage.io/v1alpha1) |
.catalog.native.backstage.kind |
string | Entity kind (e.g. Component, System, API) |
.catalog.native.backstage.metadata |
object | Raw metadata block (name, description, annotations, tags, etc.) |
.catalog.native.backstage.spec |
object | Raw spec block (type, owner, lifecycle, system, providesApis, consumesApis, dependsOn, etc.) |
.catalog.native.backstage.refs |
object | Referential-integrity results; written (as an object) only when backstage_url is configured |
.catalog.native.backstage.refs.checked |
boolean | true whenever backstage_url is set — the "referential integrity ran" signal the policy keys off to distinguish configured from not configured |
.catalog.native.backstage.refs.domain |
object | For the declared spec.domain: { name, exists } when the lookup resolved (200/404), or { name, error } on a transient failure. Absent when no domain is declared |
.catalog.native.backstage.refs.system |
object | For the declared spec.system — same semantics as refs.domain |
Referential integrity. When backstage_url is set, the collector resolves each declared grouping reference against the Backstage catalog API (GET /api/catalog/entities/by-name/<kind>/<namespace>/<name>) and records the outcome under .refs. The <namespace> is taken from the reference itself — a qualified ref (ns/name) carries its own, otherwise the component's own metadata.namespace is used, falling back to default, so there is no namespace input to configure:
.refs.checked = true— always written whenbackstage_urlis set, regardless of what (if anything) is declared. This is the signal the policy uses to tell "collector configured" from "not configured."spec.domain→.refs.domain = { "name": "<value>", "exists": <bool> }on a definitive lookup, or{ "name": "<value>", "error": "<reason>" }on a transient failure.spec.system→.refs.system— same shape and semantics asrefs.domain.
exists is true on a 200 (the entity was found) and false on a 404 (declared but missing). A per-reference entry is written only when that reference is declared; an undeclared ref has no entry. On a transient error (timeout, 5xx) the entry is written with an error field instead of exists, so a Backstage outage stays distinguishable from a real miss — the policy skips (passes) an errored ref rather than failing it. When backstage_url is unset, .refs is not written at all (no checked marker), and the policy's referential-integrity checks skip (pass) because there is nothing to verify. The backstage policy's domain-exists and system-exists checks consume these fields.
Backstage entity model. In Backstage,
spec.systemlives onComponententities (a component belongs to a system) whilespec.domainlives onSystementities (a system belongs to a domain). Sosystem-existsis the check that fires for the common one-Component-per-repo case, anddomain-existsapplies to repos whosecatalog-info.yamlis itself akind: System(or aComponentthat carries a customspec.domain). Each check only does work when its reference is actually declared.
Collectors
This plugin provides the following collectors (use include to select a subset):
| Collector | Hook Type | Description |
|---|---|---|
catalog-info |
code | Parses and lints catalog-info.yaml; writes parsed metadata and lint results |
Installation
Add to your lunar-config.yml:
collectors:
- uses: github://earthly/lunar-lib/collectors/backstage@v1.0.0
on: ["domain:your-domain"]
# with:
# paths: "catalog-info.yaml,catalog-info.yml" # Customize search paths
Referential integrity (optional)
To cross-check the domain and system declared in catalog-info.yaml against a live Backstage catalog, set backstage_url:
collectors:
- uses: github://earthly/lunar-lib/collectors/backstage@v1.0.0
on: ["domain:your-domain"]
with:
backstage_url: "https://backstage.example.com"
Most internal Backstage deployments require a bearer token. Configure it as a Lunar secret:
lunar secret set BACKSTAGE_TOKEN <your-token>
The collector reads LUNAR_SECRET_BACKSTAGE_TOKEN automatically — no extra with: is needed. Pair this with the backstage policy's domain-exists / system-exists checks to enforce the results. With backstage_url unset (the default), the collector makes no network calls and behaves exactly as the parse-and-lint default above.
Open Source
This collector is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.
Common Use Cases
Explore guardrails that use data from Backstage Collector.
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.