User-defined data types in Bicep

Learn how to create 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.

Define types

You can use the type statement to create user-defined data types. In addition, you can also use type expressions in some places to define custom types.

@<decorator>(<argument>)
type <user-defined-data-type-name> = <type-expression>

The @allowed decorator is only permitted on param statements. To declare a type with a set of predefined values in a type, use union type syntax.

The valid type expressions include:

  • Symbolic references are identifiers that refer to an ambient type (like string or int) or a user-defined type symbol declared in a type 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
    
  • You can declare array types by appending [] 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 a key and a value, separated by a colon :. The key can be any string, with nonidentifier values enclosed in quotes, and the value can be any type of expression.

    Properties are required unless they have an optionality marker ? after the property value. For example, the sku property in the following example is optional:

    type storageAccountConfigType = {
      name: string
      sku: string?
    }
    

    Decorators can be used on properties. * can be used to make all values require a constraint. Additional properties can still be defined when using *. This example creates an object that requires a key of type int named id, 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 directions = 'east' | 'south' | 'west' | 'north'
    
    type obj = {
      level: 'bronze' | 'silver' | 'gold'
    }
    

    Recursion

    Object types can 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 recursive recursiveProp 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, or level5 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 can 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@2023-04-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@2023-04-01' = {
  name: storageAccountConfig.name
  location: location
  sku: {
    name: storageAccountConfig.sku
  }
  kind: 'StorageV2'
}

Use decorators

Decorators are written in the format @expression and are placed above the declarations of the user-defined data type. The following table shows the available decorators for user-defined data types.

Decorator Apply to Argument Description
description all string Provide descriptions for the user-defined data type.
discriminator object string Use this decorator to ensure the correct subclass is identified and managed.
export all none Indicates that the user-defined data type is available for import by another Bicep file.
maxLength array, string int The maximum length for string and array data types. The value is inclusive.
maxValue int int The maximum value for the integer data types. This value is inclusive.
metadata all object Custom properties to apply to the data types. Can include a description property that is equivalent to the description decorator.
minLength array, string int The minimum length for string and array data types. The value is inclusive.
minValue int int The minimum value for the integer data types. This value is inclusive.
sealed object none Elevate BCP089 from a warning to an error when a property name of a use-define data type is likely a typo. For more information, see Elevate error level.
secure string, object none Marks the types as secure. The value for a secure type isn't saved to the deployment history and isn't logged. For more information, see Secure strings and objects.

Decorators are in the sys namespace. If you need to differentiate a decorator from another item with the same name, preface the decorator with sys. For example, if your Bicep file includes a variable named description, you must add the sys namespace when using the description decorator.

Discriminator

See Tagged union data type.

Description

Add a description to the user-defined data type. Decorators can be used on properties. For example:

@description('Define a new object type.')
type obj = {
  @description('The object ID')
  id: int

  @description('Additional properties')
  @minLength(10)
  *: string
}

Markdown-formatted text can be used for the description text.

Export

Use @export() to share the user-defined data type with other Bicep files. For more information, see Export variables, types, and functions.

Integer constraints

You can set minimum and maximum values for integer type. You can set one or both constraints.

@minValue(1)
@maxValue(12)
type month int

Length constraints

You can specify minimum and maximum lengths for string and array types. You can set one or both constraints. For strings, the length indicates the number of characters. For arrays, the length indicates the number of items in the array.

The following example declares two type. One type is for a storage account name that must have 3-24 characters. The other type is an array that must have from 1-5 items.

@minLength(3)
@maxLength(24)
type storageAccountName string

@minLength(1)
@maxLength(5)
type appNames array

Metadata

If you have custom properties that you want to apply to a user-defined data type, add a metadata decorator. Within the metadata, define an object with the custom names and values. The object you define for the metadata can contain properties of any name and type.

You might use this decorator to track information about the data type that doesn't make sense to add to the description.

@description('Configuration values that are applied when the application starts.')
@metadata({
  source: 'database'
  contact: 'Web team'
})
type settings object

When you provide a @metadata() decorator with a property that conflicts with another decorator, that decorator always takes precedence over anything in the @metadata() decorator. So, the conflicting property within the @metadata() value is redundant and will be replaced. For more information, see No conflicting metadata.

Sealed

See Elevate error level.

Secure types

You can mark string or object user-defined data type as secure. The value of a secure type isn't saved to the deployment history and isn't logged.

@secure()
type demoPassword string

@secure()
type demoSecretObject object

Elevate error level

By default, declaring an object type in Bicep allows it to accept additional properties of any type. For example, the following Bicep is valid but raises a warning of [BCP089] - The property "otionalProperty" is not allowed on objects of type "{ property: string, optionalProperty: null | string }". Did you mean "optionalProperty"?:

type anObject = {
  property: string
  optionalProperty: string?
}
 
param aParameter anObject = {
  property: 'value'
  otionalProperty: 'value'
}

The warning informs you that the anObject type doesn't include a property named otionalProperty. While no errors occur during deployment, the Bicep compiler assumes otionalProperty is a typo, that you intended to use optionalProperty but misspelled it, and alert you to the inconsistency.

To escalate these warnings to errors, apply the @sealed() decorator to the object type:

@sealed() 
type anObject = {
  property: string
  optionalProperty?: string
}

You get the same results by applying the @sealed() decorator to the param declaration:

type anObject = {
  property: string
  optionalProperty: string?
}
 
@sealed() 
param aParameter anObject = {
  property: 'value'
  otionalProperty: 'value'
}

The ARM deployment engine also checks sealed types for additional properties. Providing any extra properties for sealed parameters results in a validation error, causing the deployment to fail. For example:

@sealed()
type anObject = {
  property: string
}

param aParameter anObject = {
  property: 'value'
  optionalProperty: 'value'
}

Tagged union data 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 following example shows how to declare a tagged union data 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

For more information, see Custom tagged union data type.

Next steps

  • For a list of the Bicep data types, see Data types.