Organization-wide third-party dependency visibility — versions, drift, licenses, and unused dependencies — across every component in your catalog.
backstage-plugin-library-tracker resolves each Component's repository from its catalog
backstage.io/source-location annotation and scans it via VCS APIs only — no cloning, built
entirely on Backstage's UrlReaderService, so it works uniformly across GitHub, GitLab,
Bitbucket and Azure DevOps.
It answers questions you can't easily get from any single registry or repo:
- 📦 What does each component depend on, and at which versions?
- 🔍 Which components depend on library X? — reverse search across the whole org.
- 📈 How outdated is each dependency? — severity-graded drift (major / minor / patch) plus license.
- 🧹 Which declared dependencies are never imported? — conservative, confidence-scored detection.
- 📍 Where is a library actually imported? — cross-provider code-occurrence viewer.
⚠️ Where do versions conflict across the org? — duplicate-version report.
Scans run on a schedule and on demand, and are incremental: the reader's ETag
(NotModifiedError) skips repositories with no new commits since the last sync, so re-scans are
cheap.
- Screenshots
- Supported ecosystems
- How it works
- Install
- Configuration
- Views
- REST API
- Local development
- Accuracy & limitations
- Project layout
The Unused and Duplicate versions tabs present the same data filtered for each report.
| Ecosystem | Manifests | Registry (latest + license) | Unused detection |
|---|---|---|---|
| npm / Node | package.json |
registry.npmjs.org | ✅ high confidence |
| Python | requirements*.txt, pyproject.toml, Pipfile |
PyPI | ✅ high confidence |
| Maven / Java | pom.xml |
Maven Central | |
| NuGet / C# | *.csproj, packages.config |
api.nuget.org |
New ecosystems plug in behind a single EcosystemParser
interface.
flowchart TD
A([Catalog components]) --> B[Resolve source-location annotation]
B --> C{"UrlReader.readTree(repo, etag)"}
C -- "no new commits<br/>(NotModifiedError)" --> SKIP([Skip · incremental])
C -- "new commits" --> T[Repo tree]
subgraph scan ["Per changed repository — one readTree fetch"]
direction TB
T --> M[Manifests]
T --> S[Source files]
M --> P[Parse declared dependencies]
S --> X[Extract imports]
P --> R["Registry lookup, cached:<br/>latest version · license · drift severity"]
P --> U["Match imports → used / unused<br/>+ confidence + occurrences"]
X --> U
end
R --> DB[(Store · SQLite / Postgres)]
U --> DB
DB --> API[REST API] --> UI([UI · org page + entity tab])
A single readTree per changed repo yields both the manifests and the source files, so
manifest parsing and unused-dependency analysis share one fetch. The ETag check skips unchanged
repos, and everything downstream is concurrency-limited.
# backend
yarn --cwd packages/backend add backstage-plugin-library-tracker-backend
# frontend
yarn --cwd packages/app add backstage-plugin-library-trackerBackend — packages/backend/src/index.ts:
backend.add(import('backstage-plugin-library-tracker-backend'));Import from the /alpha subpath. The plugin self-registers its page, sidebar nav item, and entity tabs — no manual wiring needed.
packages/app/src/App.tsx:
import libraryTrackerPlugin from 'backstage-plugin-library-tracker/alpha';
const app = createApp({
features: [
libraryTrackerPlugin,
// ... other plugins
],
});That's it. The plugin mounts the org-wide page at /library-tracker, adds itself to the sidebar, and attaches dependency tabs to Component and System entity pages automatically.
Use this if your host app has not migrated to the new Backstage frontend system yet.
Org-wide page — packages/app/src/App.tsx:
import { LibraryTrackerPage } from 'backstage-plugin-library-tracker';
<Route path="/library-tracker" element={<LibraryTrackerPage />} />;Entity tab — packages/app/src/components/catalog/EntityPage.tsx:
import {
EntityLibraryTrackerContent,
SystemLibraryTrackerContent,
LibraryTrackerIcon,
} from 'backstage-plugin-library-tracker';
// Component entity page
<EntityLayout.Route path="/dependencies" title="Dependencies">
<EntityLibraryTrackerContent />
</EntityLayout.Route>
// System entity page
<EntityLayout.Route path="/dependencies" title="Dependencies">
<SystemLibraryTrackerContent />
</EntityLayout.Route>Sidebar icon — packages/app/src/components/Root/Root.tsx:
import { LibraryTrackerIcon } from 'backstage-plugin-library-tracker';
<SidebarItem icon={LibraryTrackerIcon} to="library-tracker" text="Library Tracker" />💡 Add SCM
integrationstoapp-config.yamlso the reader can reach private repos and enjoy higher rate limits. Components are scanned only if they carry abackstage.io/source-location(orbackstage.io/managed-by-location) annotation.
All keys are optional; defaults are shown.
libraryTracker:
schedule: # how often the org-wide scan runs
frequency: { hours: 6 }
timeout: { minutes: 30 }
initialDelay: { seconds: 30 }
scan:
concurrency: 5 # repositories scanned in parallel
excludePaths: [node_modules, target, dist, .venv, vendor] # defaults cover more
registry:
enableVersionCheck: true # look up latest versions + licenses (disable to skip all registry calls)
cacheTtl: { hours: 24 } # how long registry lookups are cached| Key | Default | Description |
|---|---|---|
schedule |
every 6h | Standard Backstage task schedule for the org-wide scan |
scan.concurrency |
5 |
Max repositories processed in parallel |
scan.excludePaths |
build/vendor dirs | Directory names skipped while scanning |
registry.enableVersionCheck |
true |
Fetch latest version + license from public registries |
registry.cacheTtl |
24h |
TTL for cached registry lookups |
The org-wide page (LibraryTrackerPage) is tabbed:
| Tab | What it shows |
|---|---|
| Overview | Stat cards, colour-coded drift breakdown, ecosystem distribution, top-affected-by-major-drift table |
| All dependencies | Every dependency org-wide, filterable by ecosystem / owner / drift / usage |
| Package search | Reverse lookup: which components use a library, at which versions |
| Outdated | Dependencies behind latest, graded major / minor / patch |
| Unused | Declared-but-never-imported dependencies |
| Duplicate versions | Same library pinned to conflicting versions across the org |
| Scan status | Per-component scan state + a Refresh all button |
The entity tab (EntityLibraryTrackerContent) shows one component's dependencies with a
Refresh now button. Every library name opens a code-occurrence dialog with the exact files
and lines where it's imported, each with a direct link to the source file in the VCS.
The system tab (SystemLibraryTrackerContent) aggregates all dependencies for every component
owned by the system's team, with a Manifest column that appears automatically when multiple
manifest files are present:
import { SystemLibraryTrackerContent } from 'backstage-plugin-library-tracker';
<EntityLayout.Route path="/dependencies" title="Dependencies">
<SystemLibraryTrackerContent />
</EntityLayout.Route>Mounted at /api/library-tracker:
| Method & path | Purpose |
|---|---|
GET /dependencies?ecosystem=&owner=&severity=&unused=&outdated=&offset=&limit= |
Filtered, paginated list |
GET /entities/:namespace/:kind/:name/dependencies |
One component's dependencies |
GET /libraries/lookup?name= |
Reverse search (components + versions) |
GET /libraries/occurrences?name=&entityRef= |
Files/lines importing a library |
GET /reports/overview |
Aggregate stats for the Overview dashboard |
GET /reports/duplicates |
Conflicting versions across the org |
GET /reports/unused |
Declared-but-unused dependencies |
GET /reports/outdated?severity= |
Outdated dependencies, optionally by severity |
GET /scan/status · GET /scan/runs |
Scan state and run history |
POST /scan/refresh { entityRef? } |
On-demand rescan (one component, or all when omitted) |
Each package ships a standalone dev harness — no host app required.
yarn install
yarn tsc # emit declaration files into dist-types/
# in separate terminals:
yarn workspace backstage-plugin-library-tracker-backend start # dev backend on :7007 (mock catalog)
yarn workspace backstage-plugin-library-tracker start # dev frontendTrigger a scan and inspect the results:
curl -XPOST localhost:7007/api/library-tracker/scan/refresh
curl 'localhost:7007/api/library-tracker/dependencies' | jqQuality gates:
yarn tsc && yarn build # type-check + build all packages
yarn lint # lint all packages
CI=true yarn test # run all tests once (omit CI for watch mode)Unused-dependency detection is intentionally conservative — a dependency is flagged
unused only when confidence clears a threshold:
- npm / Python — the package name maps reliably to its import name (with a small alias table
for cases like
PyYAML → yaml), so both presence and absence of an import are trustworthy. - Maven / NuGet — a package id only loosely maps to the imported namespace, and reflection, dependency injection and code generation routinely hide real usage. These are reported as used when an import matches, but are never auto-flagged as unused (shown as unknown instead) to avoid false positives.
Scanning requires a readable source-location; file: and unreadable locations are skipped and
surfaced in Scan status.
| Package | Role |
|---|---|
backstage-plugin-library-tracker-common |
Isomorphic types and API DTOs |
backstage-plugin-library-tracker-backend |
Scanner, scheduler, store, REST API (new backend system) |
backstage-plugin-library-tracker |
Org-wide page + entity tab (frontend plugin) |
MIT