Skip to content

Backstage Catalog Integration

This guide explains how to connect SkillMeat's artifact repository to Backstage's Software Catalog. After completing this setup, Backstage will automatically discover SkillMeat scaffold templates as catalog entities, and every project scaffolded through SkillMeat's IDP integration will appear in the catalog with accurate deployment annotations.

Prerequisites

Before proceeding, ensure:

  1. Plugin installed — Complete Plugin Installation & Configuration first. Both @skillmeat/backstage-plugin (frontend) and @skillmeat/backstage-plugin-scaffolder-backend (backend) must be installed and running.

  2. SkillMeat API reachable — The SkillMeat API must be accessible from your Backstage backend. Verify with:

    curl http://<skillmeat-api-host>:8080/api/v1/health
    

    Expected response: {"status": "healthy"} or similar.

  3. Catalog admin access — You must have write access to your Backstage app-config.yaml and packages/backend/src/index.ts.

  4. IDP service token (for EntityProvider) — A bearer token configured for service-to-service calls from Backstage to SkillMeat. Set in your environment as SKILLMEAT_IDP_SERVICE_TOKEN. This is optional for unauthenticated internal deployments (demo mode) but required for production.


Entity Provider Overview

Beta

The SkillMeat EntityProvider for automatic template discovery is a v2 feature. The TypeScript backend module (@skillmeat/backstage-plugin-catalog-backend) is in development. The configuration contract documented here reflects the current API specification. Check your installed plugin version before configuring the provider.

The SkillMeat EntityProvider integrates with Backstage's catalog system to automatically discover scaffold templates from SkillMeat and register them as Template entities in the catalog. This eliminates manual template registration — new templates defined in SkillMeat appear in Backstage automatically.

What the Provider Does

On each polling interval, the provider:

  1. Calls GET /api/v1/integrations/idp/templates on the SkillMeat API
  2. Receives an array of Backstage-formatted Template entities (kind: Template, apiVersion: scaffolder.backstage.io/v1beta3)
  3. Emits those entities into the Backstage catalog under the skillmeat namespace
  4. Checks each entity for the skillmeat.io/template-stale: "true" annotation; if present, logs a warning indicating the upstream bundle has been updated since the entity was generated

Default Refresh Cadence

Interval Timeout Notes
Every 5 minutes 1 minute Templates are cached server-side; frequent polling is low-cost

Templates are cached by SkillMeat's API — a poll returning a cached response costs minimal compute. The 5-minute cadence balances responsiveness (stale annotations detected quickly) with efficiency.

Entity Kinds Emitted

Kind API Version When Emitted
Template scaffolder.backstage.io/v1beta3 One per scaffold-enabled SkillMeat bundle or composite

The provider does not emit Component, API, or custom kinds. Individual project entities (of kind Component) are registered separately during scaffolding via catalog:register actions — see Scaffold-Driven Entity Annotations.


Configure the Entity Provider in app-config.yaml

Add the skillmeat provider block under catalog.providers:

app-config.yaml
catalog:
  import:
    entityFilename: catalog-info.yaml
    pullRequestBranchName: backstage-integration

  rules:
    - allow: [Component, System, API, Resource, Location, Template]

  providers:
    skillmeat:
      # Base URL of the SkillMeat API
      baseUrl: ${SKILLMEAT_API_URL:-http://skillmeat-api:8080}

      # IDP service token for service-to-service authentication.
      # Required when the SkillMeat API has RBAC enabled.
      # Leave unset or empty for unauthenticated internal deployments.
      token: ${SKILLMEAT_IDP_SERVICE_TOKEN:-}

      # Poll schedule — customize to match your refresh requirements
      schedule:
        frequency:
          minutes: 5
        timeout:
          minutes: 1

Configuration Keys

Key Type Required Description
catalog.providers.skillmeat.baseUrl string Yes SkillMeat API base URL. Use http://skillmeat-api:8080 for Docker Compose, http://localhost:8080 for host-local setups.
catalog.providers.skillmeat.token string No Bearer token for service-to-service auth. Resolved from SKILLMEAT_IDP_SERVICE_TOKEN env var. Omit entirely for unauthenticated deployments.
catalog.providers.skillmeat.schedule.frequency duration No How often the provider polls. Default: every 5 minutes.
catalog.providers.skillmeat.schedule.timeout duration No Maximum time allowed per poll. Default: 1 minute.

Proxy Configuration for the Frontend Plugin

The frontend plugin (@skillmeat/backstage-plugin) reads skillmeat.baseUrl directly from config. For production deployments where the frontend cannot reach the SkillMeat API directly, configure a backend proxy:

app-config.yaml
skillmeat:
  baseUrl: ${SKILLMEAT_API_URL:-http://skillmeat-api:8080}

proxy:
  endpoints:
    '/skillmeat':
      target: ${SKILLMEAT_API_URL:-http://skillmeat-api:8080}
      changeOrigin: true
      pathRewrite:
        '^/api/proxy/skillmeat': ''
      # Uncomment to add auth header on all proxied requests:
      # headers:
      #   Authorization: Bearer ${SKILLMEAT_IDP_SERVICE_TOKEN}

Telemetry Proxy (Optional)

If you are using the telemetry features (Platform Health Dashboard, Workflow Effectiveness Cards), add a separate proxy endpoint for the telemetry API. All telemetry endpoints require the telemetry:read scope:

app-config.yaml
proxy:
  endpoints:
    '/telemetry':
      target: ${SKILLMEAT_API_URL:-http://skillmeat-api:8080}
      changeOrigin: true
      allowedMethods: [GET]
      allowedHeaders: [Content-Type, Authorization]
      # Add bearer token if auth is enforced:
      # headers:
      #   Authorization: Bearer ${TELEMETRY_API_TOKEN}

Register the Provider in packages/backend/src/index.ts

Verify with your plugin version

The skillmeatCatalogModule export is part of @skillmeat/backstage-plugin-catalog-backend, which ships with the v2 plugin. Check whether this package is available in your installed version. If not yet released, use the manual location registration approach below.

For the new Backstage backend system (v1.24+), add the SkillMeat catalog module alongside the standard catalog plugins:

packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';

const backend = createBackend();

// Standard Backstage plugins
backend.add(import('@backstage/plugin-app-backend'));
backend.add(import('@backstage/plugin-proxy-backend'));

// Scaffolder — SkillMeat actions are registered via skillmeatScaffolderModule
backend.add(import('@backstage/plugin-scaffolder-backend'));
backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));

// SkillMeat scaffolder actions (context inject, deployment register, attest, BOM)
backend.add(import('@skillmeat/backstage-plugin-scaffolder-backend'));

// Catalog plugin with SkillMeat EntityProvider
backend.add(import('@backstage/plugin-catalog-backend'));
backend.add(
  import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
);

// SkillMeat entity provider — discovers scaffold templates from SkillMeat API
// and registers them as Template entities in the catalog.
// Reads configuration from catalog.providers.skillmeat in app-config.yaml.
backend.add(import('@skillmeat/backstage-plugin-catalog-backend'));

// Auth, permissions, search (no changes needed)
backend.add(import('@backstage/plugin-auth-backend'));
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
backend.add(import('@backstage/plugin-permission-backend'));
backend.add(
  import('@backstage/plugin-permission-backend-module-allow-all-policy'),
);

backend.start();

The @skillmeat/backstage-plugin-catalog-backend module self-registers using Backstage's extension point mechanism. No additional wiring is required — the module reads catalog.providers.skillmeat from app-config.yaml automatically.

Alternative: Manual Location Registration

If the EntityProvider module is not yet available in your plugin version, register template locations statically in app-config.yaml instead. This approach discovers templates only from explicitly listed URLs rather than polling the SkillMeat API dynamically:

app-config.yaml
catalog:
  locations:
    # Register a specific SkillMeat scaffold template by URL
    - type: url
      target: https://github.com/your-org/your-backstage-catalog/blob/main/templates/fin-serv-project.yaml
      rules:
        - allow: [Template]

    # Or register from a local file (useful in development)
    - type: file
      target: ../../backstage-templates/fin-serv-project/template.yaml
      rules:
        - allow: [Template]

This is the stable approach used by the demo stack today. Use it when @skillmeat/backstage-plugin-catalog-backend is not available.


Entity Schema for SkillMeat Artifacts

Template Entities (Emitted by EntityProvider)

Each scaffold-enabled bundle in SkillMeat becomes a Template entity in Backstage. The entity schema follows scaffolder.backstage.io/v1beta3:

apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: fin-serv-project
  title: "Financial Services Project with AI Context"
  description: "Scaffolds a new financial services project with compliance context pack"
  namespace: skillmeat
  annotations:
    skillmeat.io/bundle-id: "bundle:fin-serv"
    skillmeat.io/bundle-version: "2.1.0"
    skillmeat.io/generated-at: "2026-04-20T10:30:00Z"
    # Only present when the upstream bundle has been updated since generation:
    # skillmeat.io/template-stale: "true"
  tags:
    - financial-services
    - compliance
    - skillmeat
spec:
  owner: platform-team
  type: service
  parameters: [...]
  steps: [...]
  output: [...]

Annotations Reference

All annotations applied by SkillMeat use the skillmeat.io/ prefix:

Annotation Kind Source Description
skillmeat.io/bundle-id Template EntityProvider Identifies the SkillMeat bundle backing this template (e.g., bundle:fin-serv).
skillmeat.io/bundle-version Template EntityProvider Semantic version of the bundle when this entity was generated (e.g., 2.1.0).
skillmeat.io/generated-at Template EntityProvider ISO 8601 timestamp when the entity was generated by SkillMeat.
skillmeat.io/template-stale Template EntityProvider "true" when the upstream bundle has been updated since entity generation. EntityProvider logs a warning and triggers re-fetch on next poll.
skillmeat.io/source-artifact Component Scaffold skeleton Identifies the composite artifact used to seed the project (e.g., composite:fin-serv-compliance). Written into catalog-info.yaml in the scaffold skeleton.
skillmeat.io/deployment-set-id Component skillmeat:deployment:register UUID of the SkillMeat DeploymentSet record created when the project was scaffolded. Enables drift detection and compliance auditing.
skillmeat.io/project-id Component skillmeat:deployment:register SkillMeat project ID associated with the deployment set. Written automatically after skillmeat:deployment:register completes.
skillmeat.io/team Component Scaffold skeleton Team attribution metadata passed as a scaffold parameter (e.g., risk, compliance).

Artifact Type to Entity Mapping

SkillMeat artifacts map to Backstage entity fields as follows:

SkillMeat Artifact Type Backstage Entity Kind spec.type Notes
Composite / Bundle (scaffold-enabled) Template service, library, or website Emitted by EntityProvider. spec.type reflects the template's target project type.
Composite / Bundle (non-scaffold) Not emitted as catalog entities; visible through the SkillMeat plugin UI on entity pages.
Project (via scaffold) Component service Registered via catalog:register action in the scaffold template. Not emitted by EntityProvider directly.

Verify with your plugin version

If your SkillMeat API version differs from the one documented here, the annotation keys and entity shapes may vary. Check GET /api/v1/integrations/idp/templates directly to see the entity shape your API emits.


Scaffold-Driven Entity Annotations

When a developer scaffolds a new project using a SkillMeat template, the scaffolder automatically annotates the resulting catalog-info.yaml with SkillMeat deployment metadata. This happens through two mechanisms:

1. Skeleton catalog-info.yaml (Static Annotations)

The scaffold skeleton provides initial annotations that Backstage variable-substitutes at scaffold time:

skeleton/catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: ${{ values.projectName }}
  title: ${{ values.projectName | title }}
  description: ${{ values.description }}
  annotations:
    github.com/project-slug: ${{ values.repoUrl | parseRepoUrl | pick('owner') }}/${{ values.repoUrl | parseRepoUrl | pick('repo') }}
    backstage.io/techdocs-ref: dir:.
    # SkillMeat — tracks which composite artifact seeded this project
    skillmeat.io/source-artifact: "composite:fin-serv-compliance"
    skillmeat.io/deployment-set-id: ""   # filled in by skillmeat:deployment:register
    skillmeat.io/project-id: ""          # filled in by skillmeat:deployment:register
    skillmeat.io/team: ${{ values.team }}
  tags:
    - financial-services
    - skillmeat
    - ${{ values.team }}
spec:
  type: service
  lifecycle: experimental
  owner: ${{ values.owner }}
  system: financial-services-platform

2. skillmeat:deployment:register Action (Dynamic Annotations)

After publish:github completes, the skillmeat:deployment:register scaffolder action calls POST /api/v1/integrations/idp/register-deployment and then merges the returned deployment IDs back into catalog-info.yaml in the workspace before committing:

// Written automatically by skillmeat:deployment:register
annotations: {
  'skillmeat.io/deployment-set-id': '<uuid>',  // from API response
  'skillmeat.io/project-id': '<project-id>',   // from API response (if available)
}

This means the committed catalog-info.yaml has all four annotations populated, enabling the SkillMeat entity page cards to resolve the correct context and drift data without any manual configuration.


Import Verification

After configuring and restarting the Backstage backend, verify that entities are appearing correctly.

Step 1: Restart the Backend

# In your Backstage app directory
yarn workspace backend start
# or for Docker Compose setups:
docker compose restart backstage

Watch the backend logs for the EntityProvider initializing:

[catalog] Refreshing SkillMeat entity provider (skillmeat)
[catalog] skillmeat: fetching templates from http://skillmeat-api:8080/api/v1/integrations/idp/templates
[catalog] skillmeat: emitting 3 entities (kind=Template)

Step 2: Check the Catalog UI

  1. Navigate to your Backstage instance
  2. Click Catalog in the left sidebar
  3. In the Kind filter, select Template
  4. Look for templates tagged with skillmeat

You should see entries like "Financial Services Project with AI Context" listed under the skillmeat namespace.

Step 3: Verify via API

Query the catalog API to confirm entities are registered:

# List all Template entities in the skillmeat namespace
curl -s 'http://localhost:7007/api/catalog/entities?filter=kind=Template,metadata.namespace=skillmeat' \
  | jq '.[].metadata.name'

Expected output (example):

"fin-serv-project"
"microservices-starter"

To check a specific entity's annotations:

curl -s 'http://localhost:7007/api/catalog/entities/by-name/Template/skillmeat/fin-serv-project' \
  | jq '.metadata.annotations'

Expected output:

{
  "skillmeat.io/bundle-id": "bundle:fin-serv",
  "skillmeat.io/bundle-version": "2.1.0",
  "skillmeat.io/generated-at": "2026-04-20T10:30:00Z"
}

To find all Component entities with SkillMeat annotations (projects created via scaffold):

curl -s 'http://localhost:7007/api/catalog/entities?filter=kind=Component,metadata.annotations.skillmeat.io%2Fsource-artifact' \
  | jq '.[].metadata | {name, annotations: .annotations["skillmeat.io/deployment-set-id"]}'

Refresh Triggers

Automatic Schedule

The EntityProvider polls on the configured schedule (catalog.providers.skillmeat.schedule.frequency). By default, this is every 5 minutes. Templates added to SkillMeat will appear in the catalog within 5 minutes without any manual action.

Manual Refresh via Catalog UI

  1. Navigate to Catalog in Backstage
  2. Find the entity you want to refresh
  3. Click the entity name to open its detail page
  4. Click the Refresh button (circular arrow icon) in the entity's About card header

This triggers an immediate re-fetch of that entity's data from the registered source.

Cache Invalidation via API

To force SkillMeat to regenerate the entity for a specific bundle (clears the server-side cache):

# Replace 'fin-serv' with your bundle ID
curl -X POST \
  -H "Authorization: Bearer ${SKILLMEAT_IDP_SERVICE_TOKEN}" \
  'http://skillmeat-api:8080/api/v1/integrations/idp/templates/cache/invalidate?bundle_id=fin-serv'

Expected response: HTTP 204 No Content. The next EntityProvider poll (within 5 minutes) will fetch the freshly generated entity.

Stale Bundle Detection

When a bundle's source content is updated in SkillMeat, the API adds "skillmeat.io/template-stale": "true" to the entity's annotations. The EntityProvider logs a warning on the next poll:

[catalog] skillmeat: template 'fin-serv-project' is stale — upstream bundle updated since generation. Invalidate cache and re-poll to refresh.

To resolve:

  1. Invalidate the cache for the affected bundle (see command above)
  2. Wait for the next scheduled poll, or trigger a manual catalog refresh

Webhook-Driven Refresh

Verify with your plugin version

Webhook-triggered EntityProvider refresh is not implemented in the current v2 API contract. SkillMeat does not currently push notifications to Backstage when bundles are updated. The stale annotation mechanism and scheduled polling are the supported refresh patterns.


Troubleshooting

Entities Not Appearing in the Catalog

Symptom: After restarting the backend, no SkillMeat Template entities appear in the catalog.

Diagnosis steps:

  1. Check backend logs for EntityProvider startup:

    docker logs backstage 2>&1 | grep -i skillmeat
    

    If you see no skillmeat: fetching templates lines, the provider is not registering. Verify that @skillmeat/backstage-plugin-catalog-backend is correctly added to packages/backend/src/index.ts.

  2. Test the poll endpoint directly:

    curl -s \
      -H "Authorization: Bearer ${SKILLMEAT_IDP_SERVICE_TOKEN}" \
      'http://skillmeat-api:8080/api/v1/integrations/idp/templates'
    

    If this returns an empty array [], no scaffold-enabled bundles are configured in SkillMeat. Create or enable bundles via the SkillMeat API or web UI.

    If this returns a 404 or 500, the SkillMeat API version may not support the EntityProvider endpoint — check the API version.

  3. Verify catalog.rules allows the Template kind:

    catalog:
      rules:
        - allow: [Component, System, API, Resource, Location, Template]
    

    Without Template in the allowed list, entities of that kind are rejected by the catalog.

Authentication Errors

Symptom: EntityProvider logs show 401 Unauthorized or 403 Forbidden when polling.

Resolution:

  1. Confirm SKILLMEAT_IDP_SERVICE_TOKEN is set in your environment:

    echo $SKILLMEAT_IDP_SERVICE_TOKEN
    
  2. Test the token directly:

    curl -s -o /dev/null -w "%{http_code}" \
      -H "Authorization: Bearer ${SKILLMEAT_IDP_SERVICE_TOKEN}" \
      'http://skillmeat-api:8080/api/v1/integrations/idp/templates'
    

    Expected: 200. If 401: token is invalid or expired. If 403: token is valid but lacks the required scope — contact your SkillMeat administrator to grant the idp:templates:read scope.

  3. For demo / development environments, the SkillMeat API can run without auth. Leave token unset in app-config.yaml and unset SKILLMEAT_IDP_SERVICE_TOKEN. The Authorization header is omitted entirely when no token is configured.

Schema Validation Failures

Symptom: Entities appear in backend logs as received but the catalog rejects them with a schema validation error.

Diagnosis:

  1. Check the catalog logs for the specific validation message:

    docker logs backstage 2>&1 | grep -i "validation\|schema\|rejected"
    
  2. The most common cause is a missing required field in the entity. Fetch the raw entity from SkillMeat and compare it against the Backstage entity schema:

    curl -s \
      -H "Authorization: Bearer ${SKILLMEAT_IDP_SERVICE_TOKEN}" \
      'http://skillmeat-api:8080/api/v1/integrations/idp/templates/fin-serv' \
      | jq '{apiVersion, kind, metadata: {name: .metadata.name, namespace: .metadata.namespace}, spec_owner: .spec.owner}'
    

    Required fields for Template kind: apiVersion, kind, metadata.name, spec.owner, spec.type, spec.steps.

  3. If metadata.namespace is absent, Backstage defaults the entity to default namespace. Entities without a namespace may appear under default instead of skillmeat in the catalog. This is not an error but may affect filtering.

catalog-info.yaml Annotations Not Populated

Symptom: Scaffolded projects appear in the catalog but skillmeat.io/deployment-set-id is empty.

Cause: The skillmeat:deployment:register step either did not run, returned an error, or the annotation merge step failed.

Resolution:

  1. Open the scaffold run log in Backstage (via CreateTask activity) and check the output of the register-deployment step.

  2. If the step shows a non-200 response, verify skillmeat.baseUrl is correct in app-config.yaml and the SkillMeat API is reachable from the Backstage backend.

  3. The skillmeat:deployment:register action writes annotations as a best-effort operation — if catalog-info.yaml cannot be written, the step logs a warning but does not fail the scaffold. In this case, manually add the annotations to the repository's catalog-info.yaml.

  4. For projects scaffolded before this guide was applied, populate the annotation manually using the deployment set ID from:

    curl -s 'http://skillmeat-api:8080/api/v1/integrations/idp/register-deployment' \
      -X POST \
      -H "Content-Type: application/json" \
      -d '{"repo_url": "https://github.com/your-org/your-repo", "target_id": "composite:fin-serv-compliance", "metadata": {}}'
    

Next Steps

With catalog integration configured, you can proceed to:

  • Developer Portal Integration — Surface artifact browsing, scaffolding templates, and deployment workflows directly in your developer portal
  • Governance & Permissions Guide — Define organization policies, enforce version constraints, and audit artifact deployments through Backstage's RBAC layer