Skip to main content

Azure Pipelines - Node.js 16 and custom pipelines task extensions

· 4 min read
John Reilly
OSS Engineer - TypeScript, Azure, React, Node.js, .NET

Support for Node.js 16 for Azure Pipelines custom pipelines task extensions has arrived. From a TypeScript perspective, this post documents how to migrate from a Node.js 10 custom task to one that runs on Node 16 using azure-pipelines-task-lib.

title image reading "Azure Pipelines - Node.js 16 and custom pipelines task extensions" with Azure Pipelines, Node.js and TypeScript logos

The road to Node.js 16

Azure Pipelines custom pipelines task extensions have been around for a while. They're a great way to extend the functionality of Azure Pipelines. They're written in TypeScript and run on Node.js. You can learn how to write one here. However, until recently they were restricted to only be able to run on Node.js 6 or Node.js 10. This was a problem as support for Node 6 ended in 2018 and Node 10 ended in 2020.

A GitHub issue was opened to track support for different Node versions with custom tasks, but it remained unresolved for a long time. In October 2022 it was announced that Node.js 16 support was available.

Migrating a task to Node.js 16

There's an official migration guide to help you migrate your task from Node.js 6 or Node.js 10 to Node.js 16. It's available here. It gave me a couple of pointers but I wanted to document the process in a bit more detail. Also, I wanted to show how you can start to get some benefits from being on Node.js 16 with TypeScript.

The version of the azure-pipelines-task-lib being used in the package.json should be incremented to 4.0.0 or higher. If you haven't already, it's worth updating the @types/node version to 16.0.0 or higher. This will give you access to the types of the Node 16 APIs.

The migration guide suggests updating the task.json to have a Node16 property alongside the existing Node10 one:

"execution": {
"Node10": {
"target": "bash.js",
"argumentFormat": ""
+ "Node16": {
+ "target": "bash.js",
+ "argumentFormat": ""
+ }

I'm rather unclear as to the benefits of having a Node10 and a Node16 alongside each other; there's no useful reason to do so that I can come up with. I may be missing something.

Either way, in my own case I wanted to take advantage of the Node 16 environment and so I removed the Node10 property entirely. My task.json now looks like this:

  "execution": {
"Node16": {
"target": "index.js"

This was all I needed to do, to get to the point of having a Node 16 compatible task. But we want to go a little further.

Updating TypeScript to use Node 16

Now we have Node 16, we can now start using some of the APIs available there if we'd like, and we can stop transpiling to an older version of JavaScript. To do this we need to update our TypeScript configuration in our tsconfig.json file:

-    "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
- "lib": [] /* Specify library files to be included in the compilation. */,
+ "target": "es2021" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
+ "lib": ["ES2021"] /* Specify library files to be included in the compilation. */,

Here we're just changing the emitted JavaScript to be more modern. We're also updating the lib property to include the ES2021 library. This will give us access to the types of the Node 16 APIs.

How do we know we're using Node 16?

Great question! I was suspicious that the task was still running on Node 10. I wanted to know for sure. I ran a migrated task with system diagnostics enabled:

Screenshot of Azure Pipelines including the text "##[debug]Using node path: /home/vsts/agents/2.213.2/externals/node16/bin/node"

As we can see, we're using Node 16. This is great news!

##[debug]Using node path: /home/vsts/agents/2.213.2/externals/node16/bin/node


That's it, we're now writing modern custom pipelines task extensions using Node.js 16 and TypeScript. Microsoft have commented on the lack of alignment between Node task runners and the Node release cycle:

The original design of the Node task runner did not make Node version upgrades straightforward for task authors, and as a result has not kept up with the latest Node releases. We've heard feedback from customers on this, and are now making a number of changes to enable Azure Pipelines agents to keep installed Node versions in sync with the Node release cadence and support lifecycle while minimizing impacts on task and pipeline authors.

So, by the sounds of it, the problem is being taken seriously and will be addressed.