Bicep Instructions

June 9, 2026 ยท View on GitHub

These instructions define conventions for Bicep Infrastructure as Code (IaC) development in this codebase. Bicep files deploy Azure resources declaratively through ARM templates.

Note

These instructions target Bicep 0.36+ and include generally available features through January 2026. The providers keyword is deprecated; use extension instead.

MCP Tools

Bicep MCP tools provide schema information and best practices:

ToolPurposeParameters
mcp_bicep_experim_get_az_resource_type_schemaRetrieves the schema for a specific Azure resource type and API versionazResourceType, apiVersion (both required)
mcp_bicep_experim_list_az_resource_types_for_providerLists all available resource types for a provider namespaceproviderNamespace (required)
mcp_bicep_experim_get_bicep_best_practicesReturns current Bicep authoring best practicesNone

Project Structure

Organize Bicep files in a dedicated folder (e.g., infra/, deploy/, or environment-specific names):

main.bicep                    # Main orchestration
main.bicepparam               # Parameter values
types.bicep                   # Shared type definitions
README.md                     # Documentation
modules/                      # Reusable sub-modules
  networking.bicep
  storage.bicep
  compute.bicep

File organization:

  • main.bicep - Primary resource definitions and orchestration
  • types.bicep - Shared type definitions and default values
  • modules/ - Reusable sub-modules for logical grouping

Coding Standards

File and Naming

  • File and folder names: kebab-case
  • Parameters: camelCase
  • Types: PascalCase
  • Metadata information appears at the top of each file
  • Hardcoded values for resource names, locations, or other configurable items are not permitted

Documentation and Comments

Every parameter and type includes a @description() decorator:

  • Descriptions are short sentences ending with a period
  • Non-obvious behaviors are explained: 'The description. (Updates a something not obvious when set)'

Section headers use /* */ comment blocks with whitespace for visual separation.

Parameters and Types

Parameter conventions:

  • Define related parameter types in types.bicep
  • Use ?? (null coalescing) and .? (safe dereference) instead of ternary operators with null checks
  • Organize parameters by functional grouping, then alphabetically within groups

Functional groupings organize parameters by their purpose:

GroupDescriptionExamples
IdentityAuthentication and authorizationManaged identity names, RBAC assignments
NetworkingNetwork connectivity and securityVNet names, subnet configurations, private endpoints
StorageData persistenceStorage account settings, container names
MonitoringObservability and diagnosticsLog Analytics workspace, diagnostic settings
ComputeProcessing resourcesVM sizes, instance counts, scaling rules
SecurityEncryption and secretsKey Vault names, encryption settings
  • Boolean parameters start with should or is
  • Required parameters have no defaults
  • Empty string defaults are not permitted; use null instead
  • Sensitive parameters include @secure()

For existing resources, prefer name parameters over resource IDs:

param identityName string?
resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(identityName)) {
  name: identityName!
}

Resource Naming

Resource names follow Azure naming conventions:

PatternExample
Hyphens allowed{abbrev}-${common.resourcePrefix}-{optional}-${common.environment}-${common.instance}
No hyphens{abbrev}${common.resourcePrefix}{optional}${common.environment}${common.instance}
Length restricted'{abbrev}${uniqueString(common.resourcePrefix, {optional}, common.environment, common.instance)}'

Outputs

  • Every output includes a meaningful @description()
  • Conditional resources require conditional output expressions
  • Nullable outputs use the ? type modifier: output id string? = condition ? resource.id : null

Resource Scoping

  • Default targetScope is 'resourceGroup'; use 'subscription' or 'managementGroup' for cross-resource-group or policy deployments
  • Use symbolic references for scope: (not ID strings): scope: resourceGroup('networking-rg')
  • Existing resources use existing = syntax
  • Cross-resource-group deployments use sub-modules with scope: property

Module Conventions

AspectMain ModuleSub-Module
Locationbicep/main.bicepbicep/modules/{name}.bicep
ParametersInclude defaults when sensibleNo defaults (parent provides all values)
ResourcesDefined in main.bicepScoped to specific functionality
ReferencesOrchestrates sub-modulesCannot reference other sub-modules directly
LookupsReceive resource names for existing lookups (not IDs)Inherit scope from parent

Type System

Shared Types

Types define configuration with @export() for reuse across modules:

@export()
@description('Common deployment configuration.')
type DeploymentConfig = {
  @description('Resource name prefix.')
  prefix: string

  @description('Azure region for resources.')
  location: string

  @description('Environment: dev, test, or prod.')
  environment: 'dev' | 'test' | 'prod'
}

@export()
var deploymentDefaults = {
  prefix: 'myapp'
  location: 'eastus2'
  environment: 'dev'
}

Type conventions:

  • All types and default values include @export() and @description()
  • Sensitive values include @secure()
  • Type literals (e.g., 'dev' | 'test' | 'prod') constrain parameters with known valid values
  • Use @sealed() to prevent extra properties on configuration types (strict enforcement)
  • Use @discriminator('propertyName') for type-safe unions with multiple variants (e.g., @discriminator('type') type pet = cat | dog)
  • Prefer resourceInput<> and resourceOutput<> over open object types for resource configurations

Resource-Derived Types

Resource-derived types provide compile-time validation for resource inputs and outputs:

@description('Storage account input configuration.')
type storageAccountInput = resourceInput<'Microsoft.Storage/storageAccounts@2023-05-01'>

@description('Storage account output properties.')
type storageAccountOutput = resourceOutput<'Microsoft.Storage/storageAccounts@2023-05-01'>

@description('Accepts any valid storage account configuration.')
param storageConfig storageAccountInput

Resource-derived types validate property names and types against the resource schema at compile time.

User-Defined Functions

@export()
@description('Generates a storage account name within the 3-24 character Azure limit.')
func getStorageAccountName(prefix string, environment string, instance string) string =>
  take('st${prefix}${environment}${instance}', 24)

Function conventions:

  • All exported functions include @export() and @description()
  • Place shared functions in a functions.bicep file within the module
  • Use lambda syntax (=>) for single-expression functions
  • Function names follow camelCase naming
  • Import shared functions using standard syntax: import { functionName } from 'shared/functions.bicep'

Built-in Functions

Bicep 0.36+ includes these additional built-in functions:

FunctionPurposeExample
parseUri(uri)Parses URI into components (scheme, host, port, path, query)parseUri('https://example.com/path?q=1').host
buildUri(scheme, host, path?, port?, query?)Constructs URI from componentsbuildUri('https', 'api.example.com', '/v1', 443)
loadDirectoryFileInfo(path)Gets file metadata from directory at compile timeloadDirectoryFileInfo('./configs/')
deployer().userPrincipalNameGets the deploying user's principaldeployer().userPrincipalName

Resource Decorators

Use @onlyIfNotExists() for idempotent deployments where existing resources must be preserved. The decorator creates resources only when they do not already exist.

File Organization

Every Bicep file includes metadata at the top:

metadata name = 'Module Name'
metadata description = 'Description of what this module deploys and how it works.'

Section order with /* */ comment headers:

  1. Metadata and imports
  2. Common parameters
  3. Module-specific parameters (grouped by functionality)
  4. Variables (when needed)
  5. Resources
  6. Modules
  7. Outputs

API Versioning

GuidelineDetails
Discover versionsUse mcp_bicep_experim_list_az_resource_types_for_provider and get_az_resource_type_schema
Version consistencyIdentical resource types within a file use the same API version
New resourcesUse the latest stable API version
Existing resourcesRetain API version unless significant changes warrant upgrade

Best Practices

Best practices retrieved via mcp_bicep_experim_get_bicep_best_practices:

CategoryPractice
ModulesOmit name field for module statements (auto-generated GUID prevents concurrency issues)
ParametersGroup logically related values into single param/output with user-defined types
Params FilesUse .bicepparam files with variables and expressions instead of .json
ResourcesUse parent property instead of / in child resource names
ResourcesAdd existing resources for parents when defining child resources without parent present
ResourcesDiagnostic codes BCP036, BCP037, BCP081 may indicate hallucinated types/properties
TypesAvoid open types (array, object); prefer user-defined types
TypesUse typed variables: var foo string = 'value'
SyntaxPrefer .? with ?? over ! or verbose ternary: a.?b ?? c

Parameters Files (.bicepparam) support variables and expressions:

using 'main.bicep'

var rgPrefix = 'myapp'
param resourceGroupName = '${rgPrefix}-rg'
param tags = { environment: 'prod', costCenter: 'engineering' }
param location = 'eastus2'

Experimental Features

Caution

Experimental features require explicit opt-in via bicepconfig.json and may change or be removed in future releases.

FeatureConfig KeySyntax Example
Testing FrameworktestFrameworktest storageTest 'tests/storage.tests.bicep' = { params: { location: 'eastus' } }
Assertionsassertionsassert locationValid = location != 'centralus'
Parameter ValidationuserDefinedConstraints@validate(length(value) >= 3 && length(value) <= 24) param storageName string

Enable features in bicepconfig.json: { "experimentalFeaturesEnabled": { "featureName": true } }

Validation

  • Search codebase for existing Bicep patterns before implementing
  • Use MCP tools or Microsoft docs (learn.microsoft.com/azure/templates/{provider}/{type}) for schema reference
  • Run az bicep build and address all diagnostic warnings and errors before committing