Skip to content

Backstage Software Catalog export

Generate Backstage catalog YAML from a local folder scan. CCE combines:

  1. Repository discovery — ownership candidates, README/TechDocs, OpenAPI/Swagger specs
  2. Cloud entitlement scan — SDK-proven dependencies as Resource entities

The result is multi-document YAML you can commit to a central catalog repo or register with catalog-info.yaml.

CCE is a standalone CLI — install with Homebrew, point it at a checkout, and write YAML to a file. Explicit CLI flags and .cce/backstage.yaml always win over discovery.

What you get

Output Purpose
Component One service/repo — description, owner, lifecycle, SCM/TechDocs annotations, README link, providesApis, dependsOn
API (one per valid OpenAPI/Swagger file) Discoverable contracts with inline spec.definition and Component providesApis links
Resource (one per deduplicated entitlement) Cloud dependency referenced from Component dependsOn

Ownership, docs, and APIs are the majority of Backstage value. Cloud resources remain useful when SDK usage exists, but repos without cloud clients still produce a useful Component (+ APIs/docs when present).

Install

brew tap stackgenhq/homebrew-stackgen
brew install stackgenhq/homebrew-stackgen/cce
cce -version

See Get started for container and source builds.

Prerequisites

  • cce on your PATH (Homebrew or downloaded binary).
  • A local checkout of the repository to scan (--folder . from the repo root, or any path).
  • Optional: .cce/backstage.yaml with owner_mappings so CODEOWNERS / git authors become registered Backstage Group/User refs.

Quick start

From your machine, in an existing clone:

cd ./checkout-api

cce --folder . \
  --language GO \
  --filter cloud \
  --format backstage \
  --output catalog.yaml \
  --backstage-name checkout-api \
  --classification live-service

Discovery fills description, SCM URL/slug, commit SHA, TechDocs (when mkdocs.yml + docs exist), OpenAPI APIs, and mapped ownership. Override any field explicitly when needed:

cce --folder . --language GO --filter cloud --format backstage --output catalog.yaml \
  --scm github \
  --source-url https://github.com/acme/checkout-api \
  --project-slug acme/checkout-api \
  --backstage-owner group:default/payments-team

Print to stdout:

cce --folder . --language AUTO --filter cloud --format backstage --output -

Use double-dash flags

Always write --folder, --language, --format. A single -folder is parsed incorrectly by the CLI.

What CCE discovers automatically

Signal Behavior
Git remote / HEAD Source URL, SCM host, project slug, last commit (via go-git; no system git binary required)
Default branch Remote origin/HEAD when present (so TechDocs/README URLs stay on main/master even if you scan a feature branch)
README First meaningful paragraph → metadata.description (reads up to 64 KiB); README link when Source URL is known
MkDocs If mkdocs.yml/mkdocs.yaml and a docs index exist → backstage.io/techdocs-ref (url:… when Source URL known, else dir:.)
OpenAPI / Swagger Tracked openapi|swagger .yaml/.yml/.json files (skips vendor, node_modules, …) → API entities + providesApis. Specs larger than 2 MiB are skipped with a warning
CODEOWNERS Root/general owners mapped via owner_mappings
Git history Recent authors (bounded) mapped via owner_mappings when CODEOWNERS did not resolve

Precedence for every field: CLI flags > .cce/backstage.yaml > discovery > defaults.

Ownership mapping

Backstage spec.owner must reference a registered Group/User. CCE never invents org entities. Configure mappings in .cce/backstage.yaml:

owner_mappings:
  "@acme/payments-team": group:default/payments-team
  "Jane Doe": group:default/payments-team

Resolution order when owner is unset:

  1. Mapped CODEOWNERS candidate (* first, then other patterns)
  2. Highest-frequency mapped recent commit author
  3. unknown (with a stderr diagnostic when candidates existed but none mapped)

Only a non-sensitive cce.stackgen.com/owner-inference annotation (codeowners or git-history) is written — never emails.

APIs (OpenAPI / Swagger only)

This increment discovers spec files, not framework route annotations (Spring/FastAPI). Annotation-derived contracts cannot reliably fill Backstage’s required spec.definition across languages; that remains a later increment.

Duplicate titles get collision-safe names using the parent directory (for example store-api-user-service).

Choose language and filter

Repo profile Recommended --language --filter
Docs/API catalog with little cloud usage AUTO or primary language cloud (Resources may be empty — that is fine)
Go microservice (SDK in internal/) GO cloud
Python service (boto3 / azure SDK) AUTO or PYTHON cloud
Go + large frontend monorepo GO (not AUTO alone) cloud

Mixed-language monorepos

--language AUTO can miss Go SDK calls when the tree is dominated by TypeScript or other languages. For Go+React repos, pass --language GO explicitly if the service’s cloud usage is in Go.

Use cce plan --folder . --language GO to confirm which source files will be parsed before a long scan.

Workflow

flowchart LR
  checkout[Local git checkout]
  discover[Discover ownership docs APIs]
  scan[Scan cloud entitlements]
  yaml[Multi-doc catalog YAML]
  pr[Open PR to catalog repo]
  bs[Backstage ingests entities]

  checkout --> discover --> scan --> yaml --> pr --> bs
  1. Check out the service repo.
  2. Run cce --format backstage (optional identity flags / owner_mappings).
  3. Review owner, description, API count, TechDocs annotation, and dependsOn.
  4. Commit under your catalog repo layout.
  5. Register via Backstage catalog locations.

Scan the current directory

cd ~/src/checkout-api

cce --folder . --language GO --filter cloud --format backstage \
  --output catalog.yaml \
  --backstage-name checkout-api

GitLab or other SCM hosts

CCE clones GitHub with --repo. For GitLab, clone locally then scan; discovery reads origin when present:

git clone https://gitlab.example.com/acme/checkout-api.git ~/src/checkout-api

cce --folder ~/src/checkout-api --language GO --filter cloud --format backstage \
  --output checkout-api.yaml \
  --backstage-name checkout-api \
  --backstage-owner group:default/platform-team

Repo-local metadata (.cce/backstage.yaml)

# .cce/backstage.yaml
name: checkout-api
description: Payment checkout microservice
owner: group:default/payments-team
type: service
lifecycle: production
system: commerce
tags:
  - payments
  - go
owner_mappings:
  "@acme/payments-team": group:default/payments-team

CLI flags override file values. See example/backstage-config.yaml.

Defaults when unset: owner: unknown, type: service, lifecycle: production. --classification dormant or junk sets lifecycle: experimental unless lifecycle is already set.

CLI flags reference

Scan flags (same as JSON/SARIF)

Flag Default Notes
--folder . Path to local checkout
--repo GitHub owner/repo clone + scan (GitHub only)
--ref main Branch/tag with --repo
--language GO GO, PYTHON, JAVA, JAVASCRIPT, AUTO
--filter all Use cloud for cloud Resource lists
--format text Set to backstage
--output stdout File path or -
--mapper-file Optional custom lens (with --filter all)

Backstage identity flags

Flag Purpose
--backstage-config Path to .cce/backstage.yaml (default: <folder>/.cce/backstage.yaml)
--backstage-name metadata.name (required; default from project slug or folder basename)
--backstage-description Component description (else README discovery)
--backstage-owner spec.owner (else mapped discovery / unknown)
--backstage-type spec.type (default service)
--backstage-lifecycle spec.lifecycle (default production)
--backstage-system spec.system
--backstage-namespace Entity namespace (default default)
--backstage-language Language tag annotation
--source-url Canonical repo URL (else git remote)
--scm github or gitlab (else inferred from remote)
--project-slug org/repo (else inferred from remote)
--last-commit Commit SHA (else git rev-parse HEAD)
--classification live-service, dormant, or junk

Example output

apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: checkout-api
  description: Handles payment checkout for the commerce platform.
  annotations:
    cce.stackgen.com/scm: github
    cce.stackgen.com/source-url: https://github.com/acme/checkout-api
    backstage.io/source-location: url:https://github.com/acme/checkout-api/
    backstage.io/techdocs-ref: url:https://github.com/acme/checkout-api/tree/main/
    github.com/project-slug: acme/checkout-api
    cce.stackgen.com/owner-inference: codeowners
  links:
    - url: https://github.com/acme/checkout-api/blob/main/README.md
      title: README
      icon: docs
spec:
  type: service
  lifecycle: production
  owner: group:default/payments-team
  providesApis:
    - checkout-api
  dependsOn:
    - resource:default/aws-s3
---
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
  name: checkout-api
  title: Checkout API
spec:
  type: openapi
  lifecycle: production
  owner: group:default/payments-team
  definition: |
    openapi: 3.0.3
    info:
      title: Checkout API
      version: 1.0.0
    paths: {}
---
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
  name: aws-s3
  tags: [aws, s3]
spec:
  type: storage
  owner: group:default/payments-team

Full entity field reference: Backstage catalog entity spec.

Register with Backstage

  1. Add the generated YAML to your catalog repository.
  2. Point a Backstage Location at the file or directory.
  3. Validate in the UI — Component should show APIs, docs, and resource dependencies.

Keep the JSON entitlement report alongside YAML when auditors need file/line proof:

cce --folder . --language GO --filter cloud --format json --output entitlements.json
cce --folder . --language GO --filter cloud --format backstage --output catalog.yaml

CI example

- name: Generate Backstage catalog fragment
  run: |
    cce --folder . --language GO --filter cloud --format backstage \
      --output catalog-fragment.yaml \
      --backstage-name "${{ github.event.repository.name }}" \
      --last-commit "${{ github.sha }}"

Troubleshooting

Symptom Likely cause Fix
owner: unknown No mappings for CODEOWNERS/authors Add owner_mappings or --backstage-owner
No API entities Spec files missing, invalid, or oversized Ensure OpenAPI/Swagger root openapi/swagger + info; keep specs ≤ 2 MiB
Warning: OpenAPI skipped (size) Spec exceeds the 2 MiB embed limit Split or host the definition elsewhere; CCE embeds inline for central catalogs
TechDocs URL points at wrong branch Missing origin/HEAD in shallow clones Ensure the clone records the remote default branch, or set Source URL / branch via metadata
No TechDocs annotation Incomplete MkDocs layout Need mkdocs.yml and docs/index.md (or configured docs_dir)
Empty dependsOn No cloud SDK usage / wrong language Expected for many repos; use --language appropriately
--repo fails on GitLab GitHub-only clone helper git clone locally, use --folder

Limitations

  • Proves static SDK entitlements, not live ARNs or runtime traffic.
  • Ownership requires configured mappings to registered Backstage identities.
  • API discovery is spec-file based (OpenAPI/Swagger), not framework annotations.
  • Oversized OpenAPI files (>2 MiB) are omitted from the catalog with a diagnostic.
  • Does not emit System, Domain, User, or Group entities.
  • Relations are written once: Component dependsOn / providesApis (no reverse dependencyOf edges).
Topic Page
Release history Changelog
Entity schema & annotations Backstage catalog entity spec
Cloud entitlement JSON Cloud entitlements
As-built dependency map As-built dependency map
Enterprise catalogs Enterprise lenses & catalogs