Surfacing PagerDuty On-Call on Entity Pages
The single most useful thing a portal can tell a responder is who to wake up. This how-to wires PagerDuty into Backstage so every service’s entity page shows its current on-call engineer and any open incidents, resolved through the service’s owner. It is the read-live-state half of Incident & On-Call Workflows within Developer Experience & Self-Service Platforms.
Prerequisites
- Backstage 1.20+ with the catalog populated and service ownership accurate — the integration is only as correct as
spec.owner. - A PagerDuty account with an API token that can read schedules and incidents.
- Each service mapped to a PagerDuty service or integration key, so the portal knows which schedule to read.
- The
@pagerduty/backstage-plugin(frontend) and its backend module installed. - The API token injected at runtime as
${PAGERDUTY_TOKEN}, never committed.
Exact Configuration
-
Annotate each service with its PagerDuty integration key so the plugin knows which schedule to read.
# catalog-info.yaml apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: payments annotations: pagerduty.com/integration-key: ${PD_PAYMENTS_KEY} spec: type: service owner: team-payments -
Provide the API token to the backend module.
# app-config.yaml # Requires @pagerduty/backstage-plugin-backend >= 0.5.0 pagerduty: apiToken: ${PAGERDUTY_TOKEN} -
Add the card to the entity page so on-call and incidents render on the overview.
// packages/app/src/components/catalog/EntityPage.tsx import { EntityPagerDutyCard } from '@pagerduty/backstage-plugin'; const overviewContent = ( <Grid item md={6}> <EntityPagerDutyCard /> </Grid> );
Validation
# Requires curl, jq
# 1. The backend can read PagerDuty at all
curl -s "${PORTAL_URL}/api/pagerduty/services" -H "Authorization: Bearer ${PORTAL_TOKEN}" \
| jq '.services | length'
# Expected: > 0
# 2. A specific service resolves an on-call person
curl -s "${PORTAL_URL}/api/pagerduty/oncall?serviceId=${PD_SERVICE_ID}" | jq '.oncall[0].user.name'
# Expected: the engineer currently on call
# 3. Open incidents surface for the service
curl -s "${PORTAL_URL}/api/pagerduty/incidents?serviceId=${PD_SERVICE_ID}" | jq '[.incidents[] | select(.status=="triggered")] | length'
# Expected: the count of currently-triggered incidents
Edge Cases & Troubleshooting
| Symptom | Root Cause | Resolution |
|---|---|---|
| Card shows “no on-call” | Annotation missing or wrong integration key | Confirm pagerduty.com/integration-key matches a real PD service |
| Wrong person shown | Service owner points at the wrong team | Fix spec.owner; on-call resolves through ownership |
| 401 from the backend | API token invalid or lacks read scope | Reissue a token with read access to schedules and incidents |
| On-call is stale during a handoff | Response cached too long | Lower the plugin’s cache TTL for on-call reads |
| Rate-limited under load | Every page view hits the PD API | Cache on the backend and share across viewers |
Frequently Asked Questions
Can responders acknowledge incidents from the portal?
Some plugin versions support acknowledge and escalate actions, but treat write access carefully: it needs a token with write scope and a clear audit trail. Many teams keep the portal read-only for incidents and drive acknowledgement from the paging tool, using the portal purely to shorten the “who and what” lookup.
How fresh is the on-call data?
As fresh as your cache TTL. On-call rarely changes mid-shift, so a short cache — a minute or two — is a good trade between freshness and API load. During a handoff you may briefly see the outgoing engineer; shortening the TTL narrows that window at the cost of more API calls.