Azure Bicep Updates — March 2026 Edition

Azure Bicep Updates — March 2026 Edition

It’s been a while since I last published an Azure Bicep updates edition. But both the Bicep team and community certainly haven't been standing still. In this edition, we're going to catch up with two releases (v0.41.2 and v0.40.2) by looking at some of their highlights:

  • The bicep snapshot command
  • New experimental decorators and functions
  • Added namespace functions
  • Multi-line interpolated strings
  • And more..

Hold on to this one. This blog post is going to be a big one!

Bicep snapshot command

The bicep snapshot command started to surface in v0.36.2 as an experimental feature. Now, in v0.41.2, the command reached GA status. In short, this command allows you to generate a "normalized" snapshot of the resource defined in a Bicep file. The snapshot can then be stored in a file and used to visually compare changes between deployments.

This makes it easier to review how your infrastructure evolves over time, but also to better understand how expressions are evaluated in more complex deployments. Let's take a look at how this works in practice. Imagine you have the following Bicep file:

param location string

module storageAccount 'br/public:avm/res/storage/storage-account:0.32.0' = {
  params: {
    name: 'snapshotcommanddemo001'
    location: location
  }
}

Pretty simple. From this point, you create a snapshot: bicep snapshot main.bicepparam.

Of course, over time, this is going to be expanded with additional properties. Maybe for a security requirement, it's good to disable the publicNetworkAccess property:

param location string

module storageAccount 'br/public:avm/res/storage/storage-account:0.32.0' = {
  params: {
    name: 'snapshotcommanddemo001'
    location: location
    publicNetworkAccess: 'Disabled'
  }
}

Now, running it once again, but this time with the --mode validate added, we can see the property being changed:

Image 1: Property change

Whilst this is visual, you can capture the snapshot by --mode overwrite and included it in your pull request for easier readability, as it is JSON. To have an even better explanation and demo, check out the YouTube video created by Anthony Martin.

Null if not found (experimental)

The new ARM infrastructure backend changes now allow resources to define a nullIfNotFound decorator. This is literally stated in the decorator: if a resource is not found, return null. The feature is behind an experimental feature flag, and to enable it, set the following in your bicepconfig.json:

{
    "experimentalFeaturesEnabled": {"existingNullIfNotFound": true}
}

A perfect example to use such a decorator is whenever you have a governance template. The governance templates configure diagnostic settings. But diagnostic settings require a Log Analytics workspace. It can happen that not all subscriptions have the workspace (yet), and adding the @nullIfNotFound() decorator to the template skips the diagnostic settings if so. Here's an example:

param location string = resourceGroup().location

@nullIfNotFound()
resource centralWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' existing = {
  name: 'law-central-governance'
}

var workspaceResourceId = centralWorkspace.?id

module storageAccount 'br/public:avm/res/storage/storage-account:0.32.0' = {
  name: 'storageAccountDeployment'
  params: {
    name: 'mygovernancestorage001'
    location: location
    // diagnosticSettings is omitted entirely when the workspace isn't present,
    // and populated with the workspace ID when it is – no branching templates needed.
    diagnosticSettings: workspaceResourceId != null
      ? [
          {
            workspaceResourceId: workspaceResourceId!
          }
        ]
      : null
  }
}

output diagnosticsEnabled bool = workspaceResourceId != null

Support for extension namespace functions

In v0.41.2, another feature was added to support extension namespaces for functions. Basically, for extension authors, this provides a cleaner and type-safe function that .bicepparam files can call at deploy time. Depending on the extension, it can fetch secrets and configurations from external systems.

There are no hardcoded values needed anymore, nor do you have to use the externalInput() function. Let's break it down in more detail what this means.

First of all, a Bicep extension is a package that someone builds and then pushes to a registry. When you load one in a .bicepparam file using the extension keyword, it gives you access to a named namespace. Through this namespace, you can call functions that the extension author has defined for you.

Before this feature actually existed, fetching a value from an external config store required writing a raw externalInput() call where you had to know the exact internal expression format. There wasn't really any type checking or IntelliSense available.

param environment = externalInput('configStore.expression', '$config(deployment/environment)')

Here's the difference. Extension authors define proper typed functions with named parameters and descriptions. Then, as a user, you just call them through the extension's namespace:

using 'main.bicep'

extension 'br:myregistry.azurecr.io/bicep/extensions/configstore:1.0.0' as configStore

param environment  = configStore.config('deployment/environment')
param skuName      = configStore.config('appservice/sku-name')
param replicaCount = int(configStore.config('appservice/replica-count'))

You can see in the above example that the main.bicep stays clean (because we only defined the .bicepparam file). It only has plain parameters and doesn't have knowledge of where the values come from. The .bicepparam file is the one responsible for loading the extension and where the functions get called from.

When you have such an extension available, your VS Code editor can now suggest functions and their names. If you call a function with the wrong type or a missing argument, you get a proper error before the deployment even starts.

Do this exist functions?

The heading wasn't really a spelling mistake, but a play on words. Bicep v0.40.2 brought in two new functions in the this namespace.

Inside a resource body, you can now use two new functions. One returns a boolean telling whether a resource already exists in Azure at the moment of deployment (this.Exists()) and the other goes a little step further. This lets you read the live property values of that resource if it exists (this.existingResource()). The this part mainly focuses on the scoped locality of the resource body they're used in. So this always refers to the resource being declared, nothing more.

One of the reasons this is useful is that Bicep deployments often run more than once. Without this.exists(), you'd have to choose between always applying your desired state or using a separate existing declaration when you deploy it through a pipeline, for example. Take, for example, the following pattern:

// Requires bicepconfig.json: { "experimentalFeaturesEnabled": { "thisNamespace": true } }

param location string = resourceGroup().location
param environment string = 'prod'

resource storageAccount 'Microsoft.Storage/storageAccounts@2025-06-01' = {
  name: 'myappstorage${environment}001'
  location: location
  sku: { name: 'Standard_LRS' }
  kind: 'StorageV2'
  properties: {
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false

    networkAcls: {
      // First deploy: lock down by default (secure by default).
      // Re-deploy: carry forward whatever the team has already configured so a
      // pipeline re-run never accidentally changes the network policy.
      defaultAction: this.exists()
        ? this.existingResource().properties.networkAcls.defaultAction
        : 'Deny'

      // Same pattern for bypass: preserve existing value or set a safe default.
      bypass: this.exists()
        ? this.existingResource().properties.networkAcls.bypass
        : 'AzureServices'
    }
  }
}

The pattern above shows this because, on first deployment, it locks down public access. But when you re-deploy it, it carries forward the existing defaultAction and bypass values so the pipeline never accidentally undoes something the platform security team might have configured.

Multi-line interpolated string

Bicep already had multi-line strings for a while using the ''' triple-quote syntax, but v0.41.2 brought the multi-line interpolated strings to GA status. I mentioned the triple-quote syntax because they couldn't contain dynamic values. When you used this syntax, you'd have to break out of the string and concatenate everything together.

Now with interpolated syntax inside a multi-line string, it becomes way easier, for example, when you have an embedded script. A deployment script resource is a perfect candidate to look at. Now you can write out something like this:

param location string = resourceGroup().location
param storageAccountName string
param containerName string

resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
  name: 'createBlobContainer'
  location: location
  kind: 'AzurePowerShell'
  properties: {
    azPowerShellVersion: '12.0'
    retentionInterval: 'PT1H'
    scriptContent: $'''
      $context = New-AzStorageContext -StorageAccountName '${storageAccountName}' -UseConnectedAccount
      $exists = Get-AzStorageContainer -Name '${containerName}' -Context $context -ErrorAction SilentlyContinue
      if (-not $exists) {
        New-AzStorageContainer -Name '${containerName}' -Context $context -Permission Off
        Write-Output "Container '${containerName}' created."
      } else {
        Write-Output "Container '${containerName}' already exists, skipping."
      }
    '''
  }
}

Here you can see that additional $''' added at the beginning, indicating an interpolated multi-line string.

Other notable improvements

Each release also brings other improvements, and the following bullet points are my list of notable ones:

  • New MCP Server tools have been added (decompile_arm_parameters_file, decompile_arm_template_file, format_bicep_file, get_bicep_file_diagnostics, get_file_references, and get_deployment_snapshot).
  • Combine the new MCP Server tools with the Bicep MCP server as a NuGet package.
  • Visualizer V2 designer is coming.
  • Extendable parameter improvements and fixes
  • Disable diagnostic for a whole file (#disable-diagnostics <bicepNumber>)

Conclusion

That's a wrap for this March 2026 edition of Azure Bicep updates. As you can see, a lot of new features and improvements across both versions. It's good to see that several features came out of experimental state and became stable (GA).

It's great again to be writing something around Bicep. Keep an eye out for the next edition, which might come sooner than you think!