Output iteration in ARM templates

This article shows you how to create more than one value for an output in your Azure Resource Manager template (ARM template). By adding copy loop to the outputs section of your template, you can dynamically return a number of items during deployment.

You can also use copy loop with resources, properties in a resource, and variables.

Tip

We recommend Bicep because it offers the same capabilities as ARM templates and the syntax is easier to use. To learn more, see loops.

Syntax

Add the copy element to the output section of your template to return a number of items. The copy element has the following general format:

"copy": {
  "count": <number-of-iterations>,
  "input": <values-for-the-output>
}

The count property specifies the number of iterations you want for the output value.

The input property specifies the properties that you want to repeat. You create an array of elements constructed from the value in the input property. It can be a single property (like a string), or an object with several properties.

Copy limits

The count can't exceed 800.

The count can't be a negative number. It can be zero if you deploy the template with a recent version of Azure CLI, PowerShell, or REST API. Specifically, you must use:

  • Azure PowerShell 2.6 or later
  • Azure CLI 2.0.74 or later
  • REST API version 2019-05-10 or later
  • Linked deployments must use API version 2019-05-10 or later for the deployment resource type

Earlier versions of PowerShell, CLI, and the REST API don't support zero for count.

Outputs iteration

The following example creates a variable number of storage accounts and returns an endpoint for each storage account:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageCount": {
      "type": "int",
      "defaultValue": 2
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    }
  },
  "variables": {
    "baseName": "[format('storage{0}', uniqueString(resourceGroup().id))]"
  },
  "resources": [
    {
      "copy": {
        "name": "storagecopy",
        "count": "[parameters('storageCount')]"
      },
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2022-09-01",
      "name": "[format('{0}{1}', copyIndex(), variables('baseName'))]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard_LRS"
      },
      "kind": "Storage",
      "properties": {}
    }
  ],
  "outputs": {
    "storageEndpoints": {
      "type": "array",
      "copy": {
        "count": "[parameters('storageCount')]",
        "input": "[reference(format('{0}{1}', copyIndex(), variables('baseName'))).primaryEndpoints.blob]"
      }
    }
  }
}

The preceding template returns an array with the following values:

[
  "https://0storagecfrbqnnmpeudi.blob.core.windows.net/",
  "https://1storagecfrbqnnmpeudi.blob.core.windows.net/"
]

The next example returns three properties from the new storage accounts.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageCount": {
      "type": "int",
      "defaultValue": 2
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    }
  },
  "variables": {
    "baseName": "[format('storage{0}', uniqueString(resourceGroup().id))]"
  },
  "resources": [
    {
      "copy": {
        "name": "storagecopy",
        "count": "[length(range(0, parameters('storageCount')))]"
      },
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2022-09-01",
      "name": "[format('{0}{1}', range(0, parameters('storageCount'))[copyIndex()], variables('baseName'))]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard_LRS"
      },
      "kind": "Storage",
      "properties": {}
    }
  ],
  "outputs": {
    "storageInfo": {
      "type": "array",
      "copy": {
        "count": "[length(range(0, parameters('storageCount')))]",
        "input": {
          "id": "[resourceId('Microsoft.Storage/storageAccounts', format('{0}{1}', copyIndex(), variables('baseName')))]",
          "blobEndpoint": "[reference(format('{0}{1}', copyIndex(), variables('baseName'))).primaryEndpoints.blob]",
          "status": "[reference(format('{0}{1}', copyIndex(), variables('baseName'))).statusOfPrimary]"
        }
      }
    }
  }
}

The preceding example returns an array with the following values:

[
  {
    "id": "/subscriptions/00000000/resourceGroups/demoGroup/providers/Microsoft.Storage/storageAccounts/0storagecfrbqnnmpeudi",
    "blobEndpoint": "https://0storagecfrbqnnmpeudi.blob.core.windows.net/",
    "status": "available"
  },
  {
    "id": "/subscriptions/00000000/resourceGroups/demoGroup/providers/Microsoft.Storage/storageAccounts/1storagecfrbqnnmpeudi",
    "blobEndpoint": "https://1storagecfrbqnnmpeudi.blob.core.windows.net/",
    "status": "available"
  }
]

Next steps