Is your feature request related to a problem? Please describe.
In a .bicepparam file, getSecret() works only when assigned to the root of a top-level
@secure() parameter. When the same call is placed inside a nested property of a structured
parameter value (an object, or an array of objects) it fails to compile — even when the target
leaf property is declared @secure() string.
The compiler raises:
Error BCP338: Failed to evaluate parameter "logins": Cannot emit unexpected expression of type ParameterKeyVaultReferenceExpression
This prevents using the idiomatic, type-safe approach of modeling deployment inputs as user-defined
types — e.g. an array of objects where each element carries its own secret (admin password,
connection string, API key). Authors are forced to flatten every secret into a separate top-level
@secure() parameter, which defeats the purpose of grouping related configuration into typed objects
and scales poorly (N secrets × M objects becomes N×M flat parameters).
Minimal reproduction
main.bicep — declares a typed parameter with a @secure() leaf:
@export()
type LoginObject = {
username: string
@secure()
password: string
}
@description('A list of logins, each carrying its own secret password.')
param logins LoginObject[]
main.bicepparam — supplies the secret via getSecret() in a nested property:
using './main.bicep'
param logins = [
{
username: 'admin'
password: getSecret('00000000-0000-0000-0000-000000000000', 'example-rg', 'example-kv', 'admin-password')
}
]
Compile:
bicep build-params main.bicepparam
Actual result:
main.bicepparam(3,16) : Error BCP338: Failed to evaluate parameter "logins": Cannot emit unexpected expression of type ParameterKeyVaultReferenceExpression [https://aka.ms/bicep/core-diagnostics#BCP338]
What works today (baseline for contrast)
The exact same getSecret() call succeeds when assigned to the root of a top-level secure string:
// main.bicep
@secure()
param password string
// main.bicepparam
using './main.bicep'
param password = getSecret('00000000-0000-0000-0000-000000000000', 'example-rg', 'example-kv', 'admin-password')
…and compiles to a Key Vault reference in the generated parameters JSON:
{
"parameters": {
"password": {
"reference": {
"keyVault": {
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-rg/providers/Microsoft.KeyVault/vaults/example-kv"
},
"secretName": "admin-password"
}
}
}
}
So the limitation is purely positional: a Key Vault reference is accepted at a parameter's root,
but rejected anywhere nested inside the parameter's value.
Describe the solution you'd like
Allow getSecret() (i.e. a ParameterKeyVaultReferenceExpression) to appear in nested positions
of a parameter value in .bicepparam, emitting nested Key Vault reference objects in the compiled
parameters JSON — for example:
{
"parameters": {
"logins": {
"value": [
{
"username": "admin",
"password": {
"reference": {
"keyVault": { "id": ".../vaults/example-kv" },
"secretName": "admin-password"
}
}
}
]
}
}
}
If full support requires changes to the ARM deployment engine (resolving Key Vault references nested
within structured parameter values), then as an interim improvement please make the diagnostic
actionable: BCP338's current text (Cannot emit unexpected expression of type ParameterKeyVaultReferenceExpression) is opaque. A targeted message explaining that getSecret()
may only be used at the root of a parameter — and pointing to the supported alternatives — would save
considerable debugging time.
Describe alternatives you've considered
- Flatten secrets into separate top-level
@secure() string parameters and recombine them inside
the template. Works, but explodes the parameter surface and breaks the typed-object model.
- Resolve secrets at deploy time outside Bicep (e.g. a pipeline step pulls from Key Vault and
injects values). Works, but moves secret handling out of the declarative template and loses the
Key Vault reference indirection (the secret value transits the pipeline).
Additional context
- The leaf property is explicitly
@secure() string, so this is not a request to weaken secret
handling — it is a request to allow a secure Key Vault reference in a nested secure slot, exactly
as already allowed at the parameter root.
- Bicep CLI version:
0.41.2 (3e403ea7c1)
- OS: Windows 11
Is your feature request related to a problem? Please describe.
In a
.bicepparamfile,getSecret()works only when assigned to the root of a top-level@secure()parameter. When the same call is placed inside a nested property of a structuredparameter value (an object, or an array of objects) it fails to compile — even when the target
leaf property is declared
@secure() string.The compiler raises:
This prevents using the idiomatic, type-safe approach of modeling deployment inputs as user-defined
types — e.g. an array of objects where each element carries its own secret (admin password,
connection string, API key). Authors are forced to flatten every secret into a separate top-level
@secure()parameter, which defeats the purpose of grouping related configuration into typed objectsand scales poorly (N secrets × M objects becomes N×M flat parameters).
Minimal reproduction
main.bicep— declares a typed parameter with a@secure()leaf:main.bicepparam— supplies the secret viagetSecret()in a nested property:Compile:
Actual result:
What works today (baseline for contrast)
The exact same
getSecret()call succeeds when assigned to the root of a top-level secure string:…and compiles to a Key Vault reference in the generated parameters JSON:
{ "parameters": { "password": { "reference": { "keyVault": { "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-rg/providers/Microsoft.KeyVault/vaults/example-kv" }, "secretName": "admin-password" } } } }So the limitation is purely positional: a Key Vault reference is accepted at a parameter's root,
but rejected anywhere nested inside the parameter's value.
Describe the solution you'd like
Allow
getSecret()(i.e. aParameterKeyVaultReferenceExpression) to appear in nested positionsof a parameter value in
.bicepparam, emitting nested Key Vaultreferenceobjects in the compiledparameters JSON — for example:
{ "parameters": { "logins": { "value": [ { "username": "admin", "password": { "reference": { "keyVault": { "id": ".../vaults/example-kv" }, "secretName": "admin-password" } } } ] } } }If full support requires changes to the ARM deployment engine (resolving Key Vault references nested
within structured parameter values), then as an interim improvement please make the diagnostic
actionable: BCP338's current text (
Cannot emit unexpected expression of type ParameterKeyVaultReferenceExpression) is opaque. A targeted message explaining thatgetSecret()may only be used at the root of a parameter — and pointing to the supported alternatives — would save
considerable debugging time.
Describe alternatives you've considered
@secure()string parameters and recombine them insidethe template. Works, but explodes the parameter surface and breaks the typed-object model.
injects values). Works, but moves secret handling out of the declarative template and loses the
Key Vault reference indirection (the secret value transits the pipeline).
Additional context
@secure() string, so this is not a request to weaken secrethandling — it is a request to allow a secure Key Vault reference in a nested secure slot, exactly
as already allowed at the parameter root.
0.41.2 (3e403ea7c1)