Azure Pipelines is the CI CD service in Azure DevOps. It builds, tests, and deploys your code. It works with Azure Repos and other git providers. It runs on Microsoft hosted agents or on your own machines.
The Azure Pipelines Backstage plugin brings that build signal into your catalog. It shows the latest builds for a repo or a named pipeline. You can scan status at a glance. You can jump from a service page to the build details you care about. The same plugin can surface pull requests, git tags, and the repo readme so the context stays in one place.
Common use cases are simple. Give teams one screen to check build health across many services. Triage a failing run without hunting for links. Keep docs and code history next to the pipeline view. The plugin supports mono repos and multi org Azure DevOps setups. It can work with Backstage permissions so you keep CI data scoped to owners.
Installation Instructions
These instructions apply to self-hosted Backstage only.
Install the frontend package
-
Add the package to your app
yarn --cwd packages/app add @backstage-community/plugin-azure-devops
Show Azure Pipelines in the entity page
If your entities use dev.azure.com/project-repo
-
Update the entity page
// packages/app/src/components/catalog/EntityPage.tsx import React from 'react'; import { EntitySwitch } from '@backstage/plugin-catalog'; import { EntityAzurePipelinesContent, isAzureDevOpsAvailable, } from '@backstage-community/plugin-azure-devops'; // For example in your CI CD section const cicdContent = ( <EntitySwitch> <EntitySwitch.Case if={isAzureDevOpsAvailable}> <EntityAzurePipelinesContent defaultLimit={25} /> </EntitySwitch.Case> </EntitySwitch> );
If your entities use dev.azure.com/project and dev.azure.com/build-definitionz
-
Update the entity page
// packages/app/src/components/catalog/EntityPage.tsx import React from 'react'; import { EntitySwitch } from '@backstage/plugin-catalog'; import { EntityAzurePipelinesContent, isAzurePipelinesAvailable, } from '@backstage-community/plugin-azure-devops'; // For example in your CI CD section const cicdContent = ( <EntitySwitch> <EntitySwitch.Case if={isAzurePipelinesAvailable}> <EntityAzurePipelinesContent defaultLimit={25} /> </EntitySwitch.Case> </EntitySwitch> );
Note
You can remove the if guard if you want the tab to always render
defaultLimit sets the max number of builds to show and defaults to ten
Configure Azure DevOps integration
-
Add Azure DevOps credentials in your app config
integrations: azure: - host: dev.azure.com credentials: - organizations: - my-org - my-other-org clientId: ${AZURE_CLIENT_ID} clientSecret: ${AZURE_CLIENT_SECRET} tenantId: ${AZURE_TENANT_ID} - organizations: - another-org clientId: ${AZURE_CLIENT_ID} - host: server.company.com credentials: - organizations: - yet-another-org personalAccessToken: ${PERSONAL_ACCESS_TOKEN}
Add entity annotations
Use one of these setups
Repo with pipelines in the same project
# catalog-info.yaml
metadata:
annotations:
dev.azure.com/project-repo: my-project/my-repo
Monorepo with pipelines per entity
metadata:
annotations:
dev.azure.com/project-repo: my-project/my-repo
dev.azure.com/build-definition: my-build-definition
If you want a readme for each entity in a monorepo
metadata:
annotations:
dev.azure.com/readme-path: /services/my-service/README.md
Note
The readme path must be absolute in the repo
Relative paths are not supported by the Azure DevOps API
Pipeline in a different project than the repo
metadata:
annotations:
dev.azure.com/project-repo: project-with-source-code/my-repo
dev.azure.com/build-definition: my-build-definition
dev.azure.com/project: project-with-build-code
Pipelines only with another SCM
metadata:
annotations:
dev.azure.com/project: my-project
dev.azure.com/build-definition: "My Build With Spaces"
You can list more than one build definition separated by a comma
Multiple organizations
If you have more than one organization set in integrations azure add this
metadata:
annotations:
dev.azure.com/host-org: dev.azure.com/my-other-org
If you use a different host value in integrations azure match that host
metadata:
annotations:
dev.azure.com/host-org: server.company.com/yet-another-org
Names with spaces
Spaces in project and repo names are supported
metadata:
annotations:
dev.azure.com/project-repo: Has Spaces/With Space
Quotes are fine too
metadata:
annotations:
dev.azure.com/project: 'Has Spaces'
Install the backend plugin classic backend system
The backend plugin is required. Install it and mount its router.
-
Add the backend package
yarn --cwd packages/backend add @backstage-community/plugin-azure-devops-backend
-
Create the plugin router
// packages/backend/src/plugins/azureDevOps.ts import { Router } from 'express'; import { createRouter } from '@backstage-community/plugin-azure-devops-backend'; import { PluginEnvironment } from '../types'; export default async function createPlugin( env: PluginEnvironment, ): Promise<Router> { return await createRouter({ logger: env.logger, config: env.config, discovery: env.discovery, tokenManager: env.tokenManager, permissions: env.permissions, identity: env.identity, }); }
-
Mount the router
// packages/backend/src/index.ts import azureDevOps from './plugins/azureDevOps'; const azureDevOpsEnv = useHotMemoize(module, () => createEnv('azure-devops')); async function main() { // ... const apiRouter = Router(); // ... apiRouter.use( '/azure-devops', await azureDevOps(azureDevOpsEnv), ); // ... await startStandaloneServer({ logger, router: apiRouter }); } main().catch(error => { logger.error(error); process.exit(1); });
Note
The backend reads credentials from integrations azure in your config
Use the same config shown earlier
New backend system status
At this time the Azure DevOps backend plugin is provided as a classic router
Use the classic setup above in a new backend instance through the legacy router adapter if your app uses the new backend system
Example using the legacy adapter
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
import { createRouter } from '@backstage-community/plugin-azure-devops-backend';
const backend = createBackend();
backend.addLegacyRouter(async env => {
return await createRouter({
logger: env.logger,
config: env.config,
discovery: env.discovery,
tokenManager: env.tokenManager,
permissions: env.permissions,
identity: env.identity,
});
}, '/azure-devops');
backend.start();
If your installation uses a different bootstrap helper adapt the same idea
Mount the legacy router under a path such as /azure-devops
Optional catalog annotator processor
This processor can add dev.azure.com/host-org
and dev.azure.com/project-repo
for you
-
Add the package
yarn --cwd packages/backend add @backstage-community/plugin-catalog-backend-module-azure-devops-annotator-processor
-
Register the processor
// packages/backend/src/plugins/catalog.ts import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; import { AzureDevOpsAnnotatorProcessor } from '@backstage-community/plugin-catalog-backend-module-azure-devops-annotator-processor'; export default async function createPlugin(env: PluginEnvironment) { const builder = await CatalogBuilder.create(env); builder.addProcessor(new AzureDevOpsAnnotatorProcessor(env.config)); const { processingEngine, router } = await builder.build(); await processingEngine.start(); return router; }
Permission setup
The plugin supports the permission framework for pipelines and other features
-
Add the common package to your backend where your permission policy lives
yarn --cwd packages/backend add @backstage-community/plugin-azure-devops-common
-
Update your permission policy
// packages/backend/src/plugins/permission.ts or your policy file import { azureDevOpsPullRequestReadPermission, azureDevOpsPipelineReadPermission, azureDevOpsGitTagReadPermission, azureDevOpsReadmeReadPermission, azureDevOpsPullRequestDashboardReadPermission, } from '@backstage-community/plugin-azure-devops-common'; import { AuthorizeResult, PolicyDecision, isPermission, } from '@backstage/plugin-permission-common'; import { catalogConditions, createCatalogConditionalDecision, } from '@backstage/plugin-catalog-backend/alpha'; export class MyPermissionPolicy /* implements PermissionPolicy */ { async handle(request: PolicyQuery, user?: BackstageIdentityResponse): Promise<PolicyDecision> { if ( isPermission(request.permission, azureDevOpsPullRequestReadPermission) || isPermission(request.permission, azureDevOpsPipelineReadPermission) || isPermission(request.permission, azureDevOpsGitTagReadPermission) || isPermission(request.permission, azureDevOpsReadmeReadPermission) ) { return createCatalogConditionalDecision( request.permission, catalogConditions.isEntityOwner({ claims: user?.identity.ownershipEntityRefs ?? [], }), ); } if (isPermission(request.permission, azureDevOpsPullRequestDashboardReadPermission)) { return { result: AuthorizeResult.ALLOW }; } return { result: AuthorizeResult.ALLOW }; } }
New frontend system alpha support
If you use the new frontend system you can enable the plugin as a feature
-
Install the frontend package if you did not already
yarn --cwd packages/app add @backstage-community/plugin-azure-devops
-
Register the feature
// packages/app-next or packages/app src App.tsx import azureDevOpsPlugin from '@backstage-community/plugin-azure-devops'; export const app = createApp({ features: [ catalogPlugin, catalogImportPlugin, userSettingsPlugin, azureDevOpsPlugin, ], });
You can also enable feature discovery in app config
app:
packages: all
Changelog
This changelog is produced from commits made to the Azure Pipelines plugin since 7 months ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.
Features
- Add actions to clone and push Azure DevOps repos. Register the actions in the scaffolder. Include examples and tests PR 4711 merged 23 days ago
- Add action to create a pipeline from a YAML file PR 4229 merged 3 months ago
- Add action to permit a pipeline to use protected resources. This automates resource authorizations after first run PR 4303 merged 2 months ago
- Add option to wait for a pipeline to finish. Expose output variables when it completes PR 3651 merged 4 months ago
- Add build run logs. Include API endpoint and a drawer to view logs PR 2559 merged 6 months ago
- Add front end system filter for cards and tabs. Use availability helpers to show or hide UI PR 3489 merged 5 months ago
- Export getAnnotationValuesFromEntity for use in other plugins PR 3261 merged 6 months ago
Bug fixes
- Use strict filtering for build definitions when fetching builds. Prevent fuzzy matches PR 3829 merged 4 months ago
- Validate the readme path annotation. Reject relative paths with a clear error since the Azure DevOps API does not support them PR 4232 merged 3 months ago
Deprecations
- Deprecate getBuildDefinitions on the back end. It has no usages. It will be removed in a future release PR 4234 merged 3 months ago
- Deprecate getRepoBuilds on the front end and back end. It has no usages. It will be removed in a future release PR 4231 merged 3 months ago
- Move getAnnotationValuesFromEntity to the common package. The old location is deprecated and will be removed later PR 4236 merged 3 months ago
Maintenance
- Update createFrontendPlugin usage to pluginId. Replace permissionIntegrationRouter with coreServices permissionsRegistry PR 4269 merged 3 months ago
Documentation
- Add a note on how to set the build definition annotation and how to find the correct value in Azure DevOps PR 4233 merged 3 months ago
- Update README with details about spaces in project and repo names PR 4235 merged 3 months ago
- Update the permissions section in the README PR 4230 merged 3 months ago
- Fix README links to point to the community plugins repo PR 3931 merged 4 months ago
Set up Backstage in minutes with Roadie
Focus on using Backstage, rather than building and maintaining it.