Fixing "The Language Expression Property Doesn't Exist" in Bicep
By ResourcePulse Team · · 6 min read
Your editor autocompletes the property. IntelliSense shows it in the dropdown. The build passes. Then the deployment fails with The language expression property 'identity' doesn't exist, available properties are 'apiVersion', 'name', 'type', ....
This is one of the most confusing errors in Bicep because the tooling tells you the property is valid right up until Azure tells you it isn't. This post shows why that happens and how to fix each version of it.
Reproducing the error
Here is a web app that assigns a managed identity downstream, so you output the principal ID:
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
name: appName
location: location
properties: {
serverFarmId: planId
}
}
output principalId string = webApp.identity.principalId
az bicep build is clean. The editor even autocompletes .identity.principalId. The deployment fails:
The language expression property 'identity' doesn't exist,
available properties are 'apiVersion', 'name', 'type', 'location', 'properties'.
The resource has no identity block, so at deploy time the object simply does not carry that property. Accessing a property that isn't there is a runtime error.
Why the editor suggests a property that fails at deploy
Bicep validates against the resource type. The type for Microsoft.Web/sites says an identity property is allowed, so the editor offers it and the build accepts it.
But "allowed by the type" is not the same as "present on this instance." identity only appears on the deployed object if you actually declare an identity block. The type system describes what a resource can have; the deployment engine works with what it does have. The error lives in that gap, which is why it only surfaces at deploy time.
Where Bicep can know the shape of your data up front, giving it a type closes the gap: a bad property gets caught at compile time instead of deploy. That is the point of typing an array of objects before you pass it to a module. The cases below are the ones typing can't reach, where the property is valid on the type but missing on the object Azure actually built.
You will hit the same gap with:
identity.principalIdwhen no identity was assignedproperties.outputs.xon a nested deployment that produced no outputs- A module output that is only set inside a conditional branch
- Any optional property a resource populates only under certain settings
Fix 1: declare the property you are reading
If you genuinely need identity.principalId, the resource has to have an identity. Declare it:
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
name: appName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: planId
}
}
output principalId string = webApp.identity.principalId
Now the deployed object carries identity, and the output resolves. Most "doesn't exist" errors on identity, diagnostics, or other optional blocks come down to this: the property you are reading was never set.
Fix 2: the safe-dereference operator (Bicep 0.21+)
When a property is legitimately optional (sometimes present, sometimes not), guard the access with the safe-dereference operator .?. Instead of throwing, it returns null when the property is absent:
output principalId string = webApp.identity.?principalId ?? ''
The .? stops the error, and ?? supplies a fallback so the output is always a valid string. Check your version with az bicep version; safe-dereference shipped in 0.21, which is the Azure CLI default as of early 2026.
This is the right fix when null is an acceptable answer: an optional tag, a diagnostic setting, an identity that may or may not exist.
Fix 3: guard a conditional resource with its own condition
Conditional resources are the other common source. Here a workspace deploys only when monitoring is on, but the output reads its ID unconditionally:
resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2023-09-01' = if (enableMonitoring) {
name: workspaceName
location: location
}
output workspaceId string = logAnalytics.id
Bicep flags this at build time as BCP318, a warning that the resource may be null. Deploy with enableMonitoring set to false and you get the runtime "doesn't exist" error.
Reuse the same condition that controls the resource:
output workspaceId string = enableMonitoring ? logAnalytics.id : ''
The output now matches the resource's own logic: a real ID when the workspace exists, an empty string when it doesn't. This is clearer than logAnalytics.?id here because the condition is something you control, not something you are guessing at.
Fix 4: Bicep before 0.21
Without the safe-dereference operator, guard with contains() when you are reading an optional field off an object parameter:
param config object
output region string = contains(config, 'region') ? config.region : 'westeurope'
For resource properties, lean on a condition you control, like the same if flag or a parameter, rather than probing the resource object. Upgrading Bicep is the cleaner long-term answer; .? exists precisely because this pattern was so common.
A property that no operator will fix
One case looks like the others but isn't. A dynamically allocated public IP has no address until it is associated with a resource:
resource pip 'Microsoft.Network/publicIPAddresses@2023-11-01' = {
name: pipName
location: location
properties: {
publicIPAllocationMethod: 'Dynamic'
}
}
output ipAddress string = pip.properties.ipAddress
properties.ipAddress does not exist yet at deployment time, so safe-dereference would just hand you an empty string. The fix is structural: use Static allocation if you need the address at deploy time, or read the IP in a later step once it is assigned. No operator conjures a value that Azure has not created.
Catching it before the deployment fails
This error wastes time because it survives the build and only shows up minutes into a deployment, often after several resources have already been created. The pattern is always the same: an expression reads a property that the live object does not carry. It is exactly the kind of thing a reviewer should catch in the diff rather than the pipeline.
If your team reviews Bicep changes on GitHub, ResourcePulse reads each pull request statically and flags risky changes alongside the estimated monthly cost of every resource added or modified. The Preview tier is free on one repository, with no Azure subscription access needed.