Bicep User-Defined Types

A user-defined type is a custom label that describes the exact shape of a value. The built-in types, such as string and object, accept almost anything of their kind. A custom type narrows the choice and gives a complicated value a short, readable name.

Think of a paper form at a clinic. The form has a name line, an age box, and a yes-or-no tick box. Anyone who fills it out must follow that layout. A user-defined type works as the form, and each value works as a filled-out copy.

Form Diagram

  type appSettings
  ┌─────────────────────────────┐
  │ name       : text           │   required
  │ capacity   : whole number   │   required
  │ enableBackup : yes / no     │   optional
  └─────────────────────────────┘
            │ every value must match the form
            ▼
  { name: 'shop', capacity: 2 }   ✔ accepted
  { name: 'shop', capacity: 'two' } ✘ rejected

The type Statement

The type keyword creates a name for a shape. A question mark after a property type makes that property optional.

type appSettings = {
  name: string
  capacity: int
  enableBackup: bool?
}

param settings appSettings

A caller must pass an object with a name and a capacity. Passing a text value for capacity produces an error in the editor.

Limit a String to Approved Values

A union of text values works like an allowed list, and the name travels everywhere in the file.

type skuType = 'Standard_LRS' | 'Standard_GRS' | 'Premium_LRS'

param storageSku skuType = 'Standard_LRS'
param backupSku skuType = 'Standard_GRS'

Changing the approved list takes one edit, and every parameter that uses skuType follows.

Array Types

Square brackets after a type describe an array of that type.

type regionList = string[]

type subnetConfig = {
  name: string
  prefix: string
}

param regions regionList = ['eastus', 'westus']
param subnets subnetConfig[]

Decorators on Types

Decorators add limits to a type. Every parameter that uses the type receives the same rules.

@minLength(3)
@maxLength(24)
type storageName = string

param primaryStorage storageName
param archiveStorage storageName

Free-Form Objects

A dictionary-style type accepts any property name when all values share one type. The asterisk stands for any name.

type tagMap = {
  *: string
}

param tags tagMap = {
  owner: 'platform-team'
  project: 'shop'
}

Share Types Between Files

The @export() decorator makes a type available to other files. The import statement brings it in.

// types.bicep
@export()
type appSettings = {
  name: string
  capacity: int
}
// main.bicep
import { appSettings } from 'types.bicep'

param settings appSettings

A single shared file keeps every module and template aligned on the same definitions.

Benefits for Teams

Custom types document the expected input without extra comments. Editors suggest properties as soon as someone types a dot after the parameter name. Mistakes such as a misspelled property show up before deployment. Modules that accept a typed object as well stay short, since one parameter replaces a long list of loose values.

Summary

User-defined types name and constrain the shape of values. The type statement builds objects, unions, and arrays. Optional properties use a question mark, decorators add limits, and @export() with import shares definitions across files.