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

ConventionExampleWhen to Use
main.bicepmain.bicepThe entry-point file for any deployment
Resource-specific namestorage.bicep, network.bicepModule files for individual resources
Environment-prefixedprod-main.bicepWhen separate files exist per environment
Lowercase with hyphensapp-service-plan.bicepGeneral 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.

Leave a Comment