Overview
In late May this year, an exciting but semi overlooked feature was released, and I absolutely love it - Typed Variables!
Prior to this release, variable types were inferred through the value, which is fine for most statically defined content within a template, but there are cases that I will go through in this post where typing your variables really does make a big difference.
Why Use Typed Variables
Before I get going, let me walk through why you should consider using typed variables:
- Fail-Fast Error Detection: Typed variables enable the Bicep compiler to validate assigned values against declared types during authoring and compilation. This helps catch mistakes early, reducing deployment failures.
- Clearer Intent: Declaring types communicates your intent directly in the code, making it obvious how each variable should be used and what kind of data it should hold.
- Enhanced IntelliSense Support: Editors like Visual Studio Code offer richer autocompletion and validation for typed variables, speeding up development and reducing errors.
- Safer Refactoring: When you change variable values or types, the compiler immediately flags mismatches, making refactoring safer and more predictable.
Setup
Defining a typed variable is as simple as the following:
var <variable-name> <data-type> = <variable-value>
⚠️ Note: Requires Bicep version
0.36.X
or later.
Typed variables support both the standard set of base data types - string, int, bool, object, array - or for the more advanced, User-defined data types and Resource-derived types.
My Top Two Use Cases
1. Clear Intent
The first major use case for typed variables is improving code clarity and maintainability, especially when working with functions that return complex objects. Without type declarations, it can be difficult to understand what a function returns or how to properly use the resulting variable.
Consider this example with a custom function:
Un-Typed Variable
// ** Functions **
// ***************
func myFunction(param1 string, param2 int) object => {
property1: param1
property2: param2
}
// ** Variables **
// ***************
// Un-typed - No IntelliSense on variable use or expectation of variable type.
var variable = myFunction('example', 42)
In this un-typed scenario, several issues arise:
- Unclear return type: The function returns a generic
object
, making it unclear what properties are available - No IntelliSense support: When using
variable.
, your editor can’t help you with autocompletion - Hidden intent: Other developers (or future you) must examine the function implementation to understand what it returns
- Error-prone usage: Typos in property names won’t be caught until deployment time
Now let’s see how typed variables solve these problems:
Typed Variable
// User Defined-Types
// *********************
@sealed()
@description('Defines the structure for myFunction output.')
type myFunctionOutputType = {
@description('The first property of the output.')
property1: string
@description('The second property of the output.')
property2: int
}
// ** Functions **
// ***************
func myFunction(param1 string, param2 int) myFunctionOutputType => {
property1: param1
property2: param2
}
// ** Variables **
// ***************
// Typed - Includes IntelliSense, code clarity, and refactor safety on variable use.
var typedVariable myFunctionOutputType = myFunction('example', 42)
The typed approach delivers immediate improvements:
- Crystal clear intent: The type definition explicitly documents what the function returns and what each property represents
- Enhanced developer experience: Full IntelliSense support when working with the variable, including property names and descriptions
- Compile-time safety: Any mismatch between the function’s actual return value and the declared type will be caught during authoring and compilation
- Better maintainability: Changes to the function’s return structure must be reflected in the type definition, ensuring consistency across the codebase
- Team collaboration: New team members can quickly understand the data structure without diving into function implementations
This pattern is especially valuable in larger Bicep templates where functions might be defined in one section and used much later in the file, or using imported functions as discussed earlier in the series.
2. File functions
One of the most powerful applications of typed variables is when working with Bicep’s file functions such as loadJsonContent()
and loadYamlContent()
. Without typed variables, these functions return as an Any object
, providing no compile-time validation for the loaded content.
Let’s look at a practical example where we’re loading configuration data from an external JSON file:
JSON config file
{
"sku": {
"name": "Standard",
"tier": "Standard"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true
}
}
Un-Typed Variable
// ** Variables **
// ***************
var resourceConfig = loadJsonContent('./Config/resource.config.json')
With the un-typed approach above, resourceConfig
is treated as a generic object
. This means:
- No compile-time validation of the JSON structure
- No documentation about what the configuration should contain
Now let’s see how typed variables transform this experience:
// User Defined-Types
// *********************
@sealed()
@description('Defines the structure for importing a JSON File resource with SKU, kind, and properties.')
type resourceImportType = {
@description('The SKU details for the resource.')
sku: {
@description('The name of the SKU.')
name: string
@description('The tier of the SKU (e.g., Standard, Premium).')
tier: string
}
@description('The kind of the resource.')
kind: string
@description('The properties of the resource.')
properties: {
@description('Indicates whether only HTTPS traffic is supported.')
supportsHttpsTrafficOnly: bool
}
}
// ** Variables **
// ***************
var typedResourceConfig resourceImportType = loadJsonContent('./Config/resource.config.json')
With the typed variable approach, we gain several significant benefits:
- Compile-time validation: If the JSON file doesn’t match the defined structure, Bicep will catch this during authoring and compilation
- Rich IntelliSense: When you type
typedResourceConfig.
, your editor will show you the available properties with their descriptions - Self-documenting code: The type definition serves as living documentation of the expected configuration structure
- Refactoring safety: If you change the type definition, all usages will be validated automatically
This approach is particularly valuable when working with complex configuration files or when multiple team members need to understand the expected data structure.
Summary
Typed variables are a game changer for Bicep development, offering compile-time validation, enhanced IntelliSense, and self-documenting code that makes your templates more robust and maintainable. Make sure to give them a try, and happy Bicep-ing!