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:
-
Plugin installed — Complete Plugin Installation & Configuration first. Both
@skillmeat/backstage-plugin(frontend) and@skillmeat/backstage-plugin-scaffolder-backend(backend) must be installed and running. -
SkillMeat API reachable — The SkillMeat API must be accessible from your Backstage backend. Verify with:
Expected response:
{"status": "healthy"}or similar. -
Catalog admin access — You must have write access to your Backstage
app-config.yamlandpackages/backend/src/index.ts. -
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:
- Calls
GET /api/v1/integrations/idp/templateson the SkillMeat API - Receives an array of Backstage-formatted
Templateentities (kind:Template, apiVersion:scaffolder.backstage.io/v1beta3) - Emits those entities into the Backstage catalog under the
skillmeatnamespace - 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:
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:
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:
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:
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:
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:
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¶
- Navigate to your Backstage instance
- Click Catalog in the left sidebar
- In the Kind filter, select Template
- 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):
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¶
- Navigate to Catalog in Backstage
- Find the entity you want to refresh
- Click the entity name to open its detail page
- 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:
- Invalidate the cache for the affected bundle (see command above)
- 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:
-
Check backend logs for EntityProvider startup:
If you see no
skillmeat: fetching templateslines, the provider is not registering. Verify that@skillmeat/backstage-plugin-catalog-backendis correctly added topackages/backend/src/index.ts. -
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.
-
Verify
catalog.rulesallows theTemplatekind:Without
Templatein 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:
-
Confirm
SKILLMEAT_IDP_SERVICE_TOKENis set in your environment: -
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. If401: token is invalid or expired. If403: token is valid but lacks the required scope — contact your SkillMeat administrator to grant theidp:templates:readscope. -
For demo / development environments, the SkillMeat API can run without auth. Leave
tokenunset inapp-config.yamland unsetSKILLMEAT_IDP_SERVICE_TOKEN. TheAuthorizationheader 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:
-
Check the catalog logs for the specific validation message:
-
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
Templatekind:apiVersion,kind,metadata.name,spec.owner,spec.type,spec.steps. -
If
metadata.namespaceis absent, Backstage defaults the entity todefaultnamespace. 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:
-
Open the scaffold run log in Backstage (via Create → Task activity) and check the output of the
register-deploymentstep. -
If the step shows a non-200 response, verify
skillmeat.baseUrlis correct inapp-config.yamland the SkillMeat API is reachable from the Backstage backend. -
The
skillmeat:deployment:registeraction writes annotations as a best-effort operation — ifcatalog-info.yamlcannot be written, the step logs a warning but does not fail the scaffold. In this case, manually add the annotations to the repository'scatalog-info.yaml. -
For projects scaffolded before this guide was applied, populate the annotation manually using the deployment set ID from:
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