Creating an Azure Pipelines extension
Most of what an Azure Pipelines build or release should do can be accomplished by using the builtin tasks or tasks from the marketplace. Especially generic tasks like the Command Line task combined with the ability to install software on the build agents by using package managers (e.g. Chocolatey on Windows) provide a lot of creative freedom.
By implementing and publishing your own extension containing your custom tasks, you can consistently reuse functionality and make it easily accessible to other people inside and outside of your organization.
The extension used as an example in this blog post contains tasks to validate websites, namely a task that checks a website for broken links. Its source can be found in GitHub.
For detailed information about the files and folders of the sample extension, check out the Appendix.
Create an extension using the sample extension
TypeScript can be installed using npm by running
npm install -g typescript
- TFS Cross Platform Command Line Interface (tfx-cli) to package your extensions.
tfx-cli can be installed using npm by running
npm i -g tfx-cli
Initialize folders and files
- Clone or download the sample extension’s repository.
- Adapt file and folder names as well as contents of
- Change contents of
- Change the icons.
- In the console, from the root directory, execute
- In the console, from each subfolder of
Implement and build
- Implement some awesome functionality (in your
- To fill the
distfolder with all the files needed to build extension package, execute in the console, from the root folder
npm run-script build
- To build the extension package, execute in the console, from the root folder
npm run-script package
(no need to execute
npm run-script buildprior to that)
(Note: The definition of the
package commands mentioned above can be found in the root directory’s
Publish to the marketplace
To publish your extension to the marketplace, go to https://marketplace.visualstudio.com/azuredevops and click on Publish extension. You will need to sign in and create a Publisher.
Updating an extension that’s already published and in use
When an extension is updated in the marketplace, its instances that are installed in Azure DevOps accounts will not automatically get that update. This is good for the production scenario because nobody wants their build to fail because there’s a breaking change in an extension.
However, this means that you have to uninstall and reinstall the extension in the DevOps account you use for testing while you are developing and testing your extension.
If there are no breaking changes in the task definitions (e.g. new required input fields), you don’t have to revisit your build definitions containing the tasks. The existing task instances also don’t get removed from build definitions by uninstalling and reinstalling the extension so don’t worry.
To uninstall the extension go to Organization settings > Extensions > Manage (
https://dev.azure.com/someAccount/_settings/extensions). Hover over the extension and click More actions (the three dots).
Publish the extension using the
As an alternative to publishing or updating your extension with Microsoft’s web UI, you can also use the
tfx tool. Run
tfx extension publish --help in the command line to find out how.
CI/CD for the extension using Azure DevOps
Thanks to the awesome existing extension CI/CD Tools for VSTS Extensions you can also build and publish your extension using Azure DevOps itself. It can even update instances of your extension to facilitate your extension development and testing.
Sample Extension folder structure
build build scripts clean.js deletes the dist folder build.js copies files to the dist folder and calls npminstall.js npminstall.js calls npm install for each src/tasks/*/package.json files dist build output goes here node_modules node modules used during development solely src the source of the Azure DevOps extension tasks contains a subfolder for each build/release task brokenLinkChecker brokenLinkChecker task subfolder node_modules node modules used for the task brokenLinkCheckerTask.ts brokenLinkChecker task source code icon.png icon displayed in the UI where the task is configured package.json lists node modules used for the task task.json defines the task configuration UI and its startup file anotherTask ... extension-icon.png extension icon displayed in the market place (128*128) license.md overview.md content of the extension info page in the marketplace vss-extension.json extension manifest file listing tasks, icon, version package.json lists node modules used during development README.md documentation about the repository (NOT the extension) tsconfig.json TypeScript project file
Rationale behind the folder structure
dist folder) are used. Setting
tsconfig.json make sure the resulting folder structure in
dist is the same as in
src. This and programmatically copying all other files from
dist make for an easy solution to end up with all the extension files in the
dist folder from which the package can then be created.
(The actual - but also complicated and not that important - reason for having an
src and a
dist folder as opposed to have neither of them is the following: All the files that make up a task must be self contained in a single folder. This includes the files in
node_modules. Because the
node_modules folder might potentially contain more files for development than for production, separating
dist prevents bloating the extension package by including only the necessary modules.)
Additional information about some files
- Extension manifest
- Official Reference
versionattribute must be changed before the extension can be updated in the marketplace. As an alternative the version can be provided when generating the extension package with the
- Including a
node_modulesfolder in the package by referencing it in the
filesattribute with the intention to have the contained modules available to all tasks does not work. Make sure all task subfolders are totally self contained.
- Extension icon
- Best displayed when it is 128px * 128px
- Task manifest
- Official JSON schema
- The official documentation on tasks is either sparse or difficult to find. This extension makes use of input fields of types
radioso check out the task’s source code for hints about how to use fields of these types.
- Before the extension package is created, the task’s subfolder must contain all necessary files. This especially includes the
node_modules. Azure DevOps does not exectute npm install while the extension is published in the marketplace or when the task is used in a build pipeline.
- Task icon
- Must be named
- Must live in the same folder as the corresponding
- Best displayed when it is 64px * 64px
- Must be named