---
title: "Mastering Bicep Documentation: Annotate Your Scripts for Seamless   Collaboration"
url: "https://www.dariuszparys.com/mastering-bicep-documentation-annotate-your-scripts-for-seamless-collaboration"
author: "Dariusz Parys"
published: 2024-05-24T13:32:00.000Z
updated: 2025-03-26T16:32:39.000Z
description: "Learn how to use decorators and metadata in Bicep to create clear,  informative documentation for your infrastructure-as-code scripts. Enhance collaboration and knowledge sharing."
tags:
  - "Bicep"
  - "Infrastructure as Code"
  - "Documentation"
---

# Mastering Bicep Documentation: Annotate Your Scripts for Seamless   Collaboration

Bicep allows you to add decorators to describe and enforce the correct usage of  
modules. In addition, it will enable you to add metadata to enhance the  
documentation of the infrastructure as code delivery. You can use both sources  
to create automatically Markdown documentation to have a handy reference for  
future users and developers of your scripts.

## How to Annotate a Bicep File

There are two ways to include annotations in Bicep

1. Using Decorators
2. Using Metadata

### Decorators

Decorators are used during execution to describe parameters and enforce the  
correctness of passed values.

A simple example will showcase this. Let's use this resource definition for a  
storage module.

```bicep
param storageAccountName string
param skuName string = 'Standard_LRS'
param location string = resourceGroup().location

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: skuName
  }
  kind: 'StorageV2'
}
```

_Simple Storage Module without Annotations_

Let's focus on the `skuName` parameter. t is a string and has a default value.  
If you pass in a value like `something` which is not valid at all, you will not  
spot the error until deployment. Even `az deployment group validate` and a  
`what-if` analysis will not spot the error.

```powershell
❯ az deployment group what-if --template-file .\base\storage.bicep --resource-group rg-demo --parameters skuName=something --parameters storageAccountName=289jijdj3ei

Resource and property changes are indicated with this symbol:
  + Create

The deployment will update the following scope:

Scope: /subscriptions/___/resourceGroups/rg-demo

  + Microsoft.Storage/storageAccounts/289jijdj3ei [2023-01-01]

      apiVersion: "2023-01-01"
      id:         "/subscriptions/___/resourceGroups/rg-demo/providers/Microsoft.Storage/storageAccounts/289jijdj3ei"
      kind:       "StorageV2"
      location:   "northeurope"
      name:       "289jijdj3ei"
      sku.name:   "something"
      type:       "Microsoft.Storage/storageAccounts"

Resource changes: 1 to create.
```

_az deployment group what-if doesn't care about the wrong value_

If we modify the corresponding bicep file with decorators, we can enforce an  
error using the analysis tools. Here is a modified version of the same storage  
module, now using decorators to describe expected parameter behavior in addition to descriptions.

```bicep
@minLength(3)
@maxLength(24)
@description('Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only.')
param storageAccountName string

@allowed([
  'Standard_LRS'
  'Standard_GRS'
  'Standard_RAGRS'
  'Standard_ZRS'
  'Premium_LRS'
])
@description('Please talk with the subscription owner if a different SKU is needed')
param skuName string = 'Standard_LRS'

param location string = resourceGroup().location

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: skuName
  }
  kind: 'StorageV2'
}
```

_Storage module with decorators_

Now that the what-if analysis is being executed, the what-if analysis will  
correctly interrupt with the error message of the wrong value in the parameter  
`skuName`.

```powershell
❯ az deployment group what-if --template-file .\base\storage.bicep --resource-group rg-demo --parameters skuName=something --parameters storageAccountName=289jijdj3ei

InvalidTemplate - Deployment template validation failed: 'The provided value for the template parameter 'skuName' is not valid. The value 'something' is not part of the allowed value(s): 'Standard_LRS,Standard_GRS,Standard_RAGRS,Standard_ZRS,Premium_LRS'.'.
```

_Now the what-if analysis is catching the wrong value_

### Metadata

In addition to decorators, metadata can enhance the bicep file you are working  
on. Metadata doesn't have any particular keywords. It can be anything. The  
syntax is simply `metadata <metadata-name> = ANY`.

Metadata also doesn't enforce any validations on the bicep file. It is pure  
annotations of the content. You could add more context to the file to explain  
your decisions and give developers and users of the script a better understanding.

Taking the above example, let's annotate this file with additional metadata  
information.

```bicep
metadata name = 'Simple Storage Module'
metadata summary = '''
This storage module is used for non critical business data.
'''

@minLength(3)
@maxLength(24)
@description('Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only.')
param storageAccountName string

@allowed([
  'Standard_LRS'
  'Standard_GRS'
  'Standard_RAGRS'
  'Standard_ZRS'
  'Premium_LRS'
])
@description('Please talk with the subscription owner if a different SKU is needed')
param skuName string = 'Standard_LRS'

param location string = resourceGroup().location

@description('''
Please specify here an object that includes the environment and the owner
of the corresponding resource. See the example
''')
@metadata({
    example: {
        env: 'dev'
        owner: 'bar'
    }
})
param tags object = {
        env: 'dev'
        owner: 'foo'
    }

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: skuName
  }
  kind: 'StorageV2'
  tags: tags
}

@description('Name of the storage account created. Can be used to reference the resource')
output name string = storageAccount.name
```

_A more comprehensive use of metadata_

Beneath the metadata fields' `name` and `summary`, there is also descriptions  
and a metadata decorator on top of the parameter `tags`.

## Generate Markdown Documentation

We now have everything in the Bicep to generate the markdown documentation. We need an additional component to install on the system to do this. This component is PSDocs for Azure.

::bookmark{url="https://azure.github.io/PSDocs.Azure/" title="PSDocs for Azure" description="Generate documentation from Azure infrastructure as code (IaC) artifacts." author="Microsoft" publisher="logo" icon="https://www.dariuszparys.com/content/images/icon/ms_icon.png" thumbnail="https://www.dariuszparys.com/content/images/thumbnail/548f4800-8afc-11eb-98b1-9fb4c897d1d4"}

### PSDocs for Azure

This tool takes an ARM template as input and uses it as a base for generating  
markdown documentation. It also leverages any metadata included in the ARM  
template.

The fun fact is that when working with Bicep, we don't have any ARM templates by default.

In order to use it we need a script that

1. Converts the bicep modules into ARM templates
2. Runs PSDocs.Azure to generate the documentation
3. Combine multiple docs into one documentation file

As a prerequisite, we need PSDocs.Azure installed on our Powershell environment. The [detailed instructions here](https://azure.github.io/PSDocs.Azure/install-instructions/).

### Conversion Script

I created a Powershell script that will generate based on the folder where the  
biceps store the corresponding documentation and collect them into one final  
document stored in the desired output folder.

::bookmark{url="https://gist.github.com/dariuszparys/c8ed13e70295340b90c1c46ebab4eea2" title="generate-bicep-documentation.ps1" description="GitHub Gist: instantly share code, notes, and snippets." author="262588213843476" publisher="Gist" icon="https://www.dariuszparys.com/content/images/icon/pinned-octocat-093da3e6fa40-2.svg" thumbnail="https://www.dariuszparys.com/content/images/thumbnail/gist-og-image-54fd7dc0713e.png"}

To use the script on the current project root and store it in the folder `docs`  
with the name `readme.md` you call it

```powershell
.\tools\generate-bicep-documentation.ps1 -BicepDirectory . -DestinationFile docs/readme.md
```

## The Result

The storage module documentation in our sample is created using the generation  
script before and contains all metadata and decorator annotations, which look  
like this.

::gist{src="https://gist.github.com/dariuszparys/c8ed13e70295340b90c1c46ebab4eea2.js?file=storage.md"}

In addition to calling such scripts manually, you can create a pipeline step in  
your CI/CD system to generate and update that documentation on each push so you don't have outdated documents when people look things up.
