Azure Bicep Updates – November 2025 Edition

Learn about the new features and changes added in v0.38.3 and v0.38.5

Azure Bicep Updates – November 2025 Edition

The Bicep team aims for (and usually hits) a monthly release cadence. This month, two releases were made in two days: v0.38.3, followed by a minor update, v0.38.5. The theme of both releases:

  • General availability (GA) on onlyIfNotExists decorator.
  • Three experimental features were added.
  • New function loadDirectoryFileInfo.
  • Library to interact with Bicep's CLI.
  • Other updates.

In this blog post, we'll break down each feature or change and explain what it enables you to do differently today.

GA @onlyIfNotExists decorator

If you manage shared platform resources, such as Azure Key Vault, Log Analytics, or Azure Container Instances, you face existence checks and struggle with splitting templates.

With the @onlyIfNotExists decorator now being GA, it allows you to declare: "Create once, then leave it alone". The result:

  • Fewer noisy diffs in CIs.
  • Reduced failed redeploy chatter.
  • Safer shared infrastructure rollout.

So, why does this matter? Before this, you had to guard the creations with parameters. You split "seed" vs "update" templates, or even risk overwriting rotated secrets and placeholders. The @onlyIfNotExists guarantees decorator encodes the real intent. It guarantees existence once, allowing operations to own the value rotation while the platform code remains stable.

Why it matters: before this, you guarded creations with parameters, split “seed” vs “update” templates, or risked overwriting rotated secrets and placeholders. @onlyIfNotExists encodes the real intent—guarantee existence once, so operations can own value rotation while platform code stays stable and quiet. Here's a real-world use case reported by a community member, including Bicep code:

@description('Key Vault module to create a Key Vault')
module keyvault 'br/public:avm/res/key-vault/vault:0.13.0' = {
  params: {
    name: 'keyvaultName'               // Target vault name
  }
}

@description('Reference the existing KeyVault as a resource to be parent')
resource keyvaultExisting 'Microsoft.KeyVault/vaults@2024-11-01' existing = {
  name: 'keyVaultName'                 // Same physical vault the module creates (module ensures existence)
}

@secure()
@description('Generate a new GUID for the first deployment - not reused after rotation')
param secretValue string = newGuid()

@description('The Key Vault secret name to be created')
param secretName string = 'myKeyVaultSecret'

@onlyIfNotExists()
@description('Create the secret only if it does not already exist')
resource secret 'Microsoft.KeyVault/vaults/secrets@2024-11-01' = {
  parent: keyvaultExisting
  name: secretName
  dependsOn: [
    keyvault                          // Ensure vault provisioning completes before initial seed
  ]
  properties: {
    value: secretValue                // Used ONLY on first creation. Ignored on subsequent deployments.
  }
}

The operation flow that happens when you deploy:

  1. First deployment: a secret is created with a generated GUID.
  2. Operations rotates the value directly in Key Vault as needed.
  3. Subsequent template deployments: decorator suppresses mutation.
💡
Use caution with this decorator if the resource's properties should always match the correct intent.

Interactive REPL and deploy commands

In a previous post, I touched upon the deploythem commands already. But now, the commands are directly available in the release, rather than requiring installation from a nightly build. You still have to enable a feature flag in the bicepconfig.json:

{
  "experimentalFeaturesEnabled": {
    "deployCommands": true
  }
}

The Bicep team created the following documentation links:

To use the new bicep console command to test Bicep functions and expressions live, open up your terminal, poke out a bunch of expressions or functions, and learn fast. The example below illustrates the point.

The @validate() decorator

Bicep decorators are simple to use. You could check the length. You could check allowed values being passed in on parameters, and that was it.

But real Azure resources demand more.

Take the adminPassword property on a VM (reported by Jordan Ellis Coppard). Azure requires:

  • Three of four complexity rules: lowercase, uppercase, digit, and a special character.
  • Specific weak values are banned, like "P@ssw0rd".

In Bicep, all you could write was this:

@minLength(12)
@maxLength(123)
param adminPassword string

That isn't enough. You find out the password fails only after deployment. With the @validate() decorator, it runs checks before you actually deploy. It can enforce logic, not just length or lists. Now we can express the rule set:

@minLength(12)
@maxLength(123)
@validate(
  x => toLower(x) != x && toUpper(x) != x && !contains(['abc@123', 'P@$$w0rd', 'P@ssw0rd', 'P@ssword123', 'Pa$$word', 'pass@word1', 'Password!', 'Password1', 'Password22', 'iloveyou!'], x),
  'Password must contain both uppercase and lowercase letters and not be a common password'
)
param adminPassword string

This catches weak or invalid passwords before deployment, saving you time and effort.

💡
Requires the userDefinedConstraints experimental feature flag.

Using loadDirectoryFileInfo in Bicep

This change came from the community. Gabriel created the pull request.

Before, you could only load a single file with functions like loadFileAsBase64 or loadJsonContent. If you had a folder of files, you had to reference each one manually.

Now, with this function, it's no longer needed. The new loadDirectoryFileInfo function returns an array of objects for every file in a directory. And guess what, it also supports wildcards.

Say you have a folder full of PowerShell scripts. You want them published as runbooks in an Azure Automation Account. With loadDirectoryFileInfo, you can do something like this:

@description('Name of the Automation Account')
param automationAccountName string

@description('Location of the Automation Account')
param location string = resourceGroup().location

// Load all PowerShell scripts from the Runbooks folder
var runbookFiles = loadDirectoryFileInfo('./Runbooks', '*.ps1')

resource automationAccount 'Microsoft.Automation/automationAccounts@2020-01-13-preview' = {
  name: automationAccountName
  location: location
  properties: {}
}

module runbooks 'Automation/automationRunbook.bicep' = [for file in runbookFiles: {
  name: '${automationAccountName}-${file.baseName}'
  scope: resourceGroup()
  params: {
    automationAccountName: automationAccount.name
    runbookName: file.baseName
    runbookFilePath: file.fullName
    runbookType: 'PowerShell'
  }
}]

Here, every .ps1 file in the Runbooks folder becomes a runbook. Add a file, and it gets deployed. Remove it, and it will be removed from the Automation Account.

Interact with Bicep CLI via JSONRPC

In version 0.38.5, a new library has been introduced: a JSON-RPC client for the Bicep CLI. It lets you call the Bicep CLI command programmatically. Compile, decompile, publish, etc – via a stable JSON-RPC interface.

This is an interesting addition, as it:

  • Increased performance and reusability
  • The client supports downloading or pinning specific Bicep versions
  • All existing JSONRPC methods exposed by the Bicep CLI are now accessible
  • Hooks into existing tools and APIs.

In short, this is a more robust and programmatic alternative to repeat bicep <subCommand> invocations. Here's a small example snippet on how to use the new client for .NET developers:

var clientFactory = new BicepClientFactory(new HttpClient());

using var client = await clientFactory.DownloadAndInitialize(new() {
    BicepVersion = "0.38.5"
}, cancellationToken);

// Compile a Bicep file
var result = await client.Compile(new("/path/to/main.bicep"));
if (!result.Success)
{
    Console.WriteLine("Errors:");
    foreach (var diag in result.Diagnostics)
    {
        Console.WriteLine(diag);
    }
}
else
{
    Console.WriteLine("Compiled JSON:");
    Console.WriteLine(result.Compiled.Text);
}

Other notable improvements

The following list contains other notable updates or improvements added to v0.38.3 and v0.38.5:

  • You can now use the any syntax when a type any is expected.
  • The moduleIdentity is no longer experimental.
  • A new linting rule experimentalFeatureWarning has been added.
  • Bicep to DSC decompile fixes (by myself).
  • The Bicep MCP server now includes the "List AVM MCP tool".

Conclusion

Both v0.38.3 and v0.38.5 releases introduced a lot of stable and experimental features. The onlyIfNotExists removes a long-standing pain point in managing shared resources. The experimental and stable additions, such as @validate() and loadDirectoryFileInfo shows the Bicep team is bringing in more stability for a v1.0.0 release.

With the new JSONRPC client, Bicep is no longer just a CLI tool. It's a service you can wire directly into your own tooling and automation

If you haven't upgraded your Bicep versions across your ecosystem, now's the time!

For the complete list of features and bug fixes, visit the v0.38.3 and v0.38.5 sections on GitHub.com.