Bicep quietly shipped a way to ask "do you already exist?" from inside a resource block. It should retire a workaround I’ve been carrying around for years.
This one turned up on a routine scan for what had shipped in Bicep recently, and the syntax read like the kind of thing that looks clean in the docs and then fights you for an afternoon. It didn’t, not at the resource level anyway: on a current CLI it worked first try, no flag, no ceremony. Where the afternoon actually went was trying to wire it into an AVM module the way I reach for almost everything else in Bicep these days, which turned out to be the more interesting story than the happy path.
Every idempotent Bicep template I’ve written has the same shape: try to find out if a resource already exists, and if it does, don’t clobber its settings with defaults on redeploy. The annoying part was never wanting that behaviour. It was that Bicep gave you no clean way to ask the question from inside the resource that needed the answer.
The workaround I’ve been living with
The existing keyword looks like the obvious tool for the job, until you actually reach for it:
resource stg 'Microsoft.Storage/storageAccounts@2025-06-01' existing = {
name: 'mystorageaccount'
}
Fine, if the resource is definitely there. Reference one that isn’t deployed yet and you get a hard NotFound error, deployment stopped dead. That’s no good for a "deploy if missing, preserve if present" pattern, because on a first-ever run the resource obviously doesn’t exist yet.
So the workaround (mine, and every write-up of this problem I’ve ever read) moves the question outside Bicep entirely:
- a pre-flight step in the pipeline (
az resource show, or adeploymentScriptresource) checks whether the thing exists - that result gets passed in as a plain
boolparameter - the Bicep file branches on the parameter with an
if()on the resource, and duplicates the "what property do I keep vs default" logic using whatever the pre-flight script fetched
It works. It’s also an extra script to maintain, an extra round-trip before the real deployment starts, and logic that exists in two places (the checking script and the template) that can drift apart from each other without anything failing loudly.
What this.exists() actually does
Bicep’s new this namespace answers the existence question from inside the resource declaration itself, no pre-flight step required:
resource stg 'Microsoft.Storage/storageAccounts@2026-04-01' = {
name: 'mystorageaccount'
location: 'eastus'
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
accessTier: this.exists() ? this.existingResource()!.properties.accessTier : 'Cold'
}
}
this.exists() returns a plain bool: does this resource currently exist in Azure, yes or no (however it got there, this template or otherwise). this.existingResource() pairs with it and hands back the actual resource object if there is one, null if there isn’t, so you can pull a property straight out of it with the null-forgiving operator (!) once you already know it exists, or the safe-navigation operator (.?) if you’d rather not check first:
accessTier: this.existingResource().?properties.accessTier ?? 'Cold'
Same outcome, no separate existing declaration, no pipeline pre-check, no second copy of the merge logic living outside the template. The whole "was this here before, and if so what did it have set" question collapses into one expression, right where the property gets assigned.
This one moved fast. It landed as an experimental feature in Bicep v0.40.2 back in January 2026, gated behind a thisNamespace flag in bicepconfig.json and covered by Microsoft’s blanket warning that Customer Support won’t assist with issues arising from experimental features. Then v0.44.1 promoted it to general availability in June 2026, no flag required. So on a current Bicep CLI this is a proper supported feature, not a preview toy; it’s just new enough that there’s barely any hands-on write-up of it in the wild, which is exactly the kind of gap worth filling. (The same release also took the @nullIfNotFound() decorator to GA, which softens the existing keyword’s hard failure into a null instead; that’s the other half of this story, but one post at a time.)
Trying it for real
Bicep CLI version: 0.45.15, one release past the v0.44.1 that took the feature GA in June 2026. Confirmed the version claim from the CLI’s own release notes rather than trusting a summary: v0.40.2 (January 2026) shipped it experimental behind the thisNamespace flag in bicepconfig.json, v0.44.1 (June 2026) promoted it to GA with no flag required. The resourceExistenceChecks flag name that floats around a few write-ups online isn’t real; it never appears in the repo’s experimental-features list. On 0.45.15 it just works, zero config, in both the CLI and the VS Code Bicep extension:

The AVM gotcha. My first move with any new resource property is to reach for the matching Azure Verified Module rather than write the raw resource by hand, so that’s what I tried first here too: wrapping this.exists() into a call to the public avm/res/storage/storage-account module.
module stg 'br/public:avm/res/storage/storage-account:0.29.0' = {
name: 'stgDeploy'
params: {
name: storageAccountName
location: location
skuName: 'Standard_LRS'
kind: 'StorageV2'
accessTier: this.exists() ? this.existingResource()!.properties.accessTier : 'Cold'
}
}
That fails to compile:
Error BCP057: The name "this" does not exist in the current context.
A side-by-side compile of the raw-resource version above, completely unchanged, builds clean, so it isn’t a version or environment problem: the this namespace only binds inside a resource declaration, never inside a module call. A module is a black box from the caller’s side, potentially wrapping several resources underneath, so there’s no single "existing resource" for this to refer to from outside it. I went looking for whether AVM had already worked around this and found nothing: no rule for or against it anywhere in the AVM Bicep specification, nothing in the FAQ, no AVM module anywhere in the Azure org using either function. It’s not that AVM said no, it’s that the feature only went GA the month before and nobody’s proposed it in yet. For now, if you want this behaviour on an AVM-backed resource, you’re either dropping to a raw resource declaration for that one property or waiting for someone to add it upstream.
The before and after, deployed for real against the same resource group: first-ever create, then a manual az storage account update --access-tier Hot outside of Bicep entirely to simulate drift, then a redeploy of the unchanged template with no pre-flight step run:
| 1st deploy | manual drift | 2nd deploy (unchanged template, no pre-flight) | |
|---|---|---|---|
this.exists() version |
Cold |
→ Hot |
Hot — preserved |
| old pre-flight-param version | Cold |
→ Hot |
Cold — stomped back to default |
That’s the whole pitch made concrete. The old pattern is only as safe as remembering to run the pre-flight step every single time, and the moment someone runs az deployment group create directly (a hotfix, a local test, forgetting the pipeline step exists), it silently resets whatever was there. this.exists() can’t forget, because there’s nothing left outside the template to forget to run.
The identity question the docs don’t answer. This lab runs in a test tenant where I generally just mess around as Owner, which makes me the wrong test subject for "what’s the minimum permission needed." So I built a throwaway custom role scoped to just enough to run a deployment plus Microsoft.Storage/storageAccounts/write, deliberately leaving out storageAccounts/read, and ran the same template through it via a scoped service principal. Even on a first-ever deploy of a resource that doesn’t exist yet, it failed outright:
AuthorizationFailed: The client '<service-principal-object-id>' does not have authorization to
perform action 'Microsoft.Storage/storageAccounts/read' over scope
'/subscriptions/<redacted>/resourcegroups/rg-bicep-lab/providers/Microsoft.Storage/storageAccounts/<name>'
Adding storageAccounts/read back to the same role and rerunning succeeded normally. So the answer the docs don’t spell out: this.exists() needs explicit read on the resource type regardless of whether the resource is actually there yet, because Bicep still has to make the call to find out. Built-in roles like Contributor or Storage Account Contributor already include read, so most people will never notice. Anyone building tightly-scoped custom roles for a deployment pipeline around "just enough to create X," though, will hit this the first time they adopt the pattern, and the error gives no hint that it’s an existence check causing it rather than the create itself.
Why This Matters
Idempotency in IaC shouldn’t need a second script watching the first one. this.exists() and this.existingResource() move the "have I seen you before" question to where it belongs, inside the template describing the resource, instead of bolted onto the pipeline around it. And now that it’s generally available, there isn’t even an experimental-feature excuse for keeping the old pattern around. For anyone who’s built the same pre-flight-check-and-pass-a-bool pattern more times than they’d like to admit, it’s worth a proper look now, before everyone else notices at once. Two catches worth carrying into any adoption plan: it doesn’t reach through an AVM module today, so raw-resource authoring is back on the table for the specific properties that need it, and it wants explicit read access on the resource type even when there’s nothing there yet, so audit pipeline identities before rolling it out somewhere locked down.



Leave a Reply