User-defined data types in Bicep
Learn how to use user-defined data types in Bicep. For system-defined data types, see Data types.
Bicep CLI version 0.12.X or higher is required to use this feature.
User-defined data type syntax
You can use the type
statement to define user-defined data types. In addition, you can also use type expressions in some places to define custom types.
type <user-defined-data-type-name> = <type-expression>
Note
The @allowed
decorator is only permitted on param
statements. To declare that a property must be one of a set of predefined values in a type
or output
statement, use union type syntax. Union type syntax may also be used in param
statements.
The valid type expressions include:
Symbolic references are identifiers that refer to an ambient type (like
string
orint
) or a user-defined type symbol declared in atype
statement:// Bicep data type reference type myStringType = string // user-defined type reference type myOtherStringType = myStringType
Primitive literals, including strings, integers, and booleans, are valid type expressions. For example:
// a string type with three allowed values. type myStringLiteralType = 'bicep' | 'arm' | 'azure' // an integer type with one allowed value type myIntLiteralType = 10 // an boolean type with one allowed value type myBoolLiteralType = true
Array types can be declared by suffixing
[]
to any valid type expression:// A string type array type myStrStringsType1 = string[] // A string type array with three allowed values type myStrStringsType2 = ('a' | 'b' | 'c')[] type myIntArrayOfArraysType = int[][] // A mixed-type array with four allowed values type myMixedTypeArrayType = ('fizz' | 42 | {an: 'object'} | null)[]
Object types contain zero or more properties between curly brackets:
type storageAccountConfigType = { name: string sku: string }
Each property in an object consists of key and value. The key and value are separated by a colon
:
. The key may be any string (values that wouldn't be a valid identifier must be enclosed in quotes), and the value may be any type syntax expression.Properties are required unless they have an optionality marker
?
after the property value. For example, thesku
property in the following example is optional:type storageAccountConfigType = { name: string sku: string? }
Decorators may be used on properties.
*
may be used to make all values require a constraint. Additional properties may still be defined when using*
. This example creates an object that requires a key of type int namedid
, and that all other entries in the object must be a string value at least 10 characters long.type obj = { @description('The object ID') id: int @description('Additional properties') @minLength(10) *: string }
The following sample shows how to use the union type syntax to list a set of predefined values:
type obj = { level: 'bronze' | 'silver' | 'gold' }
Recursion
Object types may use direct or indirect recursion so long as at least leg of the path to the recursion point is optional. For example, the
myObjectType
definition in the following example is valid because the directly recursiverecursiveProp
property is optional:type myObjectType = { stringProp: string recursiveProp: myObjectType? }
But the following type definition wouldn't be valid because none of
level1
,level2
,level3
,level4
, orlevel5
is optional.type invalidRecursiveObjectType = { level1: { level2: { level3: { level4: { level5: invalidRecursiveObjectType } } } } }
Bicep unary operators can be used with integer and boolean literals or references to integer or boolean literal-typed symbols:
type negativeIntLiteral = -10 type negatedIntReference = -negativeIntLiteral type negatedBoolLiteral = !true type negatedBoolReference = !negatedBoolLiteral
Unions may include any number of literal-typed expressions. Union types are translated into the allowed-value constraint in Bicep, so only literals are permitted as members.
type oneOfSeveralObjects = {foo: 'bar'} | {fizz: 'buzz'} | {snap: 'crackle'} type mixedTypeArray = ('fizz' | 42 | {an: 'object'} | null)[]
In addition to be used in the type
statement, type expressions can also be used in these places for creating user-defined data types:
As the type clause of a
param
statement. For example:param storageAccountConfig { name: string sku: string }
Following the
:
in an object type property. For example:param storageAccountConfig { name: string properties: { sku: string } } = { name: 'store$(uniqueString(resourceGroup().id)))' properties: { sku: 'Standard_LRS' } }
Preceding the
[]
in an array type expression. For example:param mixedTypeArray ('fizz' | 42 | {an: 'object'} | null)[]
A typical Bicep file to create a storage account looks like:
param location string = resourceGroup().location
param storageAccountName string
@allowed([
'Standard_LRS'
'Standard_GRS'
])
param storageAccountSKU string = 'Standard_LRS'
resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: storageAccountName
location: location
sku: {
name: storageAccountSKU
}
kind: 'StorageV2'
}
By using user-defined data types, it can look like:
param location string = resourceGroup().location
type storageAccountSkuType = 'Standard_LRS' | 'Standard_GRS'
type storageAccountConfigType = {
name: string
sku: storageAccountSkuType
}
param storageAccountConfig storageAccountConfigType
resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: storageAccountConfig.name
location: location
sku: {
name: storageAccountConfig.sku
}
kind: 'StorageV2'
}
Declare tagged union type
To declare a custom tagged union data type within a Bicep file, you can place a discriminator decorator above a user-defined type declaration. Bicep CLI version 0.21.X or higher is required to use this decorator. The syntax is:
@discriminator('<propertyName>')
The discriminator decorator takes a single parameter, which represents a shared property name among all union members. This property name must be a required string literal on all members and is case-sensitive. The values of the discriminated property on the union members must be unique in a case-insensitive manner.
The following example shows how to declare a tagged union type:
type FooConfig = {
type: 'foo'
value: int
}
type BarConfig = {
type: 'bar'
value: bool
}
@discriminator('type')
type ServiceConfig = FooConfig | BarConfig | { type: 'baz', *: string }
param serviceConfig ServiceConfig = { type: 'bar', value: true }
output config object = serviceConfig
The parameter value is validated based on the discriminated property value. In the preceding example, if the serviceConfig parameter value is of type foo, it undergoes validation using the FooConfigtype. Likewise, if the parameter value is of type bar, validation is performed using the BarConfig type, and this pattern continues for other types as well.
Import types between Bicep files
Bicep CLI version 0.21.X or higher is required to use this compile-time import feature. The experimental flag compileTimeImports
must be enabled from the Bicep config file.
Only user-defined data types that bear the @export()
decorator can be imported to other templates. Currently, this decorator can only be used on type
statements.
The following example enables you to import the two user-defined data types from other templates:
@export()
type myStringType = string
@export()
type myOtherStringType = myStringType
For more information, see Import user-defined data types.
Next steps
- For a list of the Bicep data types, see Data types.
Feedback
https://aka.ms/ContentUserFeedback.
Coming soon: Throughout 2024 we will be phasing out GitHub Issues as the feedback mechanism for content and replacing it with a new feedback system. For more information see:Submit and view feedback for