Bicep File Structure and Syntax
Every Bicep file follows a consistent structure. Understanding this structure is the foundation for writing any Bicep template correctly. Think of the Bicep file like a legal contract — every section has a fixed purpose, and placing content in the wrong section causes errors.
The Five Building Blocks of a Bicep File
A Bicep file contains five types of statements. Each type serves a specific role, and they all work together to describe a complete infrastructure deployment.
+------------------------------------------------------+ | Bicep File Anatomy | +----------+-------------------------------------------+ | Section | Purpose | +----------+-------------------------------------------+ | targetScope | Defines WHERE resources deploy | | param | Accepts input values from outside | | var | Stores calculated values internally | | resource | Declares what Azure resource to | | | create or manage | | output | Returns values after deployment | +----------+-------------------------------------------+
None of these sections are mandatory except resource — a Bicep file with no resources does nothing useful. All other sections are optional but almost always present in real-world templates.
Section 1 – targetScope
The targetScope tells Bicep where the resources in the file should be created. Azure has four deployment levels, and targetScope must match the deployment command used.
Azure Hierarchy │ ├── Tenant ──► targetScope = 'tenant' │ │ │ ├── Management Group ──► targetScope = 'managementGroup' │ │ │ │ │ ├── Subscription ──► targetScope = 'subscription' │ │ │ │ │ │ │ └── Resource Group ──► targetScope = 'resourceGroup' (Default) │ │ │ │ │ │ │ └── Resources (VM, Storage, SQL, etc.)
When targetScope is not written in the file, Bicep assumes resourceGroup by default. This is the most common deployment target.
// Optional - only write when NOT deploying to a resource group targetScope = 'subscription'
Section 2 – Parameters (param)
Parameters accept values from outside the Bicep file at deployment time. They make the template reusable across different environments like Dev, Staging, and Production.
param location string = 'eastus' param appName string param instanceCount int = 2
Parameters are covered in full detail in the Bicep Parameters topic.
Section 3 – Variables (var)
Variables store values that are calculated or composed inside the Bicep file. Unlike parameters, variables cannot be changed from outside. They help avoid repeating the same expression multiple times.
var storageAccountName = 'storage${uniqueString(resourceGroup().id)}'
var fullAppName = '${appName}-prod'
Section 4 – Resources (resource)
The resource block is the heart of every Bicep file. It tells Azure what to create, what to name it, and how to configure it. Every Azure service — storage, virtual machine, app service, database — is declared in a resource block.
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: 'mystorageaccount'
location: 'eastus'
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}
Section 5 – Outputs (output)
Outputs return values from the deployment. After Azure creates a resource, outputs expose useful details like the resource ID, endpoint URL, or connection string for other systems to use.
output storageAccountId string = storageAccount.id output storageAccountEndpoint string = storageAccount.properties.primaryEndpoints.blob
The Recommended File Layout
While Bicep does not enforce a specific order for sections, following a consistent layout makes files easy to read and maintain. The recommended order is:
// 1. Target scope (if not resourceGroup)
targetScope = 'subscription'
// 2. Parameters – inputs from outside
param location string = 'eastus'
param environment string = 'dev'
// 3. Variables – internal calculated values
var resourceGroupName = 'rg-${environment}'
var tags = {
environment: environment
project: 'MyApp'
}
// 4. Resources – what to create
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
name: 'asp-myapp-${environment}'
location: location
tags: tags
sku: {
name: 'B1'
}
}
// 5. Outputs – values to expose after deployment
output appServicePlanId string = appServicePlan.id
Bicep Syntax Rules
Rule 1 – No Semicolons
Bicep does not use semicolons at the end of lines. Each statement ends with a line break.
// Wrong (ARM/JSON style) "name": "mystorage"; // Correct (Bicep style) name: 'mystorage'
Rule 2 – Single Quotes for Strings
All string values in Bicep use single quotes, not double quotes.
// Wrong name: "mystorage" // Correct name: 'mystorage'
Rule 3 – Colon for Property Assignment
Inside resource blocks, properties use a colon to assign values (not an equals sign).
resource myStorage 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: 'mystorage' // colon used inside the block
location: 'eastus'
}
Rule 4 – Equals Sign for Top-Level Declarations
Top-level statements like param, var, resource, and output use an equals sign.
param location string = 'eastus' // equals sign at top level var prefix = 'prod' // equals sign at top level
Rule 5 – Comments
Bicep supports both single-line and multi-line comments.
// This is a single-line comment /* This is a multi-line comment */
Rule 6 – String Interpolation
Combine strings and expressions using ${} inside single quotes.
var appName = 'webapp'
var environment = 'dev'
// Combine two values into one string
var fullName = '${appName}-${environment}' // Result: 'webapp-dev'
Resource Type and API Version
Every resource declaration in Bicep requires a resource type string in this exact format:
'ResourceProvider/ResourceType@ApiVersion'
Breaking it down with an example:
'Microsoft.Storage/storageAccounts@2023-01-01' │ │ │ │ │ └── API Version (date format) │ └────────────────── Resource Type └─────────────────────────────────── Resource Provider (company/service)
The API version is a date that tells Azure which version of the resource API to use. Always use the latest stable API version when writing new templates. The Bicep VS Code extension suggests available versions automatically when typing.
A Complete Bicep File – Step by Step
The example below creates an Azure App Service Plan. Read through each section to see how the five building blocks work together.
// SECTION 1: No targetScope needed – defaults to resourceGroup
// SECTION 2: Parameters
param location string = 'eastus'
param planName string = 'my-app-plan'
param skuName string = 'B1'
// SECTION 3: Variables
var tags = {
createdBy: 'Bicep'
environment: 'Production'
}
// SECTION 4: Resource
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
name: planName
location: location
tags: tags
sku: {
name: skuName
}
kind: 'linux'
properties: {
reserved: true
}
}
// SECTION 5: Output
output planId string = appServicePlan.id
output planName string = appServicePlan.name
File Naming Conventions
| Convention | Example | When to Use |
|---|---|---|
| main.bicep | main.bicep | The entry-point file for any deployment |
| Resource-specific name | storage.bicep, network.bicep | Module files for individual resources |
| Environment-prefixed | prod-main.bicep | When separate files exist per environment |
| Lowercase with hyphens | app-service-plan.bicep | General best practice |
Decompiling ARM Templates to Bicep
Existing ARM JSON templates can convert to Bicep automatically using this command:
az bicep decompile --file existing-template.json
This produces a .bicep file from the JSON. The output is not always perfect — some manual cleanup is needed — but it is a fast starting point for migrating from ARM to Bicep.
Summary
A Bicep file has five sections: targetScope, param, var, resource, and output. Bicep syntax uses single quotes, colons for properties, and equals signs for declarations. No semicolons are needed. Every resource requires a type string with provider, type, and API version. Understanding this structure makes reading and writing any Bicep template straightforward.
