Featured image of post Azure Bicep Variables Explained: The Key to Clean, Reusable Templates

Azure Bicep Variables Explained: The Key to Clean, Reusable Templates

Learn how variables bring clarity and structure to your Azure Bicep templates. This hands-on guide explains when to use variables (and when not to), how they complement parameters, and how they help you design smarter, more maintainable infrastructure code

Introduction: Parameters, Variables, and the Hidden Logic of Templates

In the previous article, we explored how parameters allow you to customise your Bicep templates by passing in values at deployment time. They give your templates flexibility and make it easy to reuse the same code across multiple environments—like dev, test, and production.

But as you start to design more robust templates, you’ll quickly realise that not every value needs to come from the outside.

Some values are meant to be configured by the deployer—things like environment name or region. That’s what parameters are for.

But other values are part of the template’s internal logic—like a naming convention, a derived setting, or a computed flag that depends on other inputs. These are things the template author defines and controls. And this is exactly where variables come in.

Variables let you build internal logic inside your template—without pushing every decision onto the person deploying it.

At first glance, variables might feel similar to parameters. After all, they both hold values that you can reuse throughout the template. But the difference lies in ownership and intention:

  • Parameters represent choices that should be made from outside the template
  • Variables encapsulate logic that belongs inside the template

This separation isn’t just theoretical. It helps you:

  • Keep your parameter files clean and focused
  • Avoid exposing unnecessary details to the deployer
  • Define complex expressions without cluttering your interface

In this article, we’ll explore:

  • Why variables are a natural extension to parameters—not a replacement
  • How to declare and use them to simplify your templates
  • Common scenarios where variables improve clarity, safety, and consistency
  • Practical patterns, from combining strings to building conditional logic and loops

By the end, you’ll not only know how to use variables—you’ll understand why they exist, when to reach for them, and how they help you write cleaner, more reliable infrastructure code.

Why Variables?

In the introduction, we explored the idea that parameters and variables play two very different roles in a Bicep template. Parameters are for values that come from outside the template—things the deployer needs to specify. Variables, on the other hand, are tools for managing logic within the template itself.

Let’s look at a practical example.

Imagine you’re deploying a web app, a storage account, and a log workspace. You want all of them to follow a consistent naming pattern based on the current environment—something like myapp-dev, myapp-dev-storage, and myapp-dev-logs.

You could expose each of these names as individual parameters:

1
2
3
param webAppName string
param storageAccountName string
param logWorkspaceName string

But now you’ve created a problem. You’re asking the deployer to provide three tightly-related values—and trust that they follow the correct naming convention every single time. That’s repetitive, fragile, and puts the burden of consistency in the wrong place.

So what’s the alternative?

Instead, you expose only the values that should truly come from the deployer—like projectName and environment:

1
2
param projectName string
param environment string

Then you use variables inside your template to construct the rest:

1
2
3
var webAppName = '${projectName}-${environment}'
var storageAccountName = '${webAppName}-storage'
var logWorkspaceName = '${webAppName}-logs'

Now your template handles the naming logic automatically. The deployer only needs to provide a few key values, and your variables ensure everything else follows the right pattern—every time, without exception.

☕️ A Helpful Analogy

Think of writing a Bicep template like running a coffee shop.

  • The deployer is like the customer. They know what they want: the size, the type of milk, maybe an extra shot.
  • The template author is like the barista. They understand how to make the drink behind the scenes: how much coffee to use for each size, what temperature the milk should be, how long to pull the shot.

The barista doesn’t ask the customer to define brew ratios or water temperature—that would be overwhelming and unnecessary. Instead, the customer provides just the essentials, and the barista takes care of the rest.

In the same way, your template should only ask the deployer for the values that truly need to change between environments. The internal logic—like naming conventions, derived values, or calculated expressions—belongs inside the template, defined as variables.

That separation keeps your templates clean, clear, and easy to use—just like a well-run coffee bar.

What’s this ${...} Syntax?

If you’re wondering what ${projectName}-${environment} means, this is called string interpolation. It lets you dynamically insert values into a string.

In the example above, if projectName = 'myapp' and environment = 'dev', the result of:

1
var webAppName = '${projectName}-${environment}'

…would be 'myapp-dev'.

This pattern is common in many programming languages and Infrastructure as Code tools like Terraform or JavaScript. Bicep supports it natively, and it’s incredibly useful for constructing values that follow a predictable structure.

Why Not Just Use Parameters?

This might raise a fair question:
Couldn’t we just define webAppName, storageAccountName, and logWorkspaceName as parameters too?

Technically, yes. But that sends the wrong signal.

By exposing those values as parameters, you’re telling the deployer: “These are things you’re responsible for.” Even though, in reality, they’re just outcomes of a naming rule that never changes.

And there’s another limitation: in Bicep, parameters can’t depend on each other. You can’t define one parameter based on the value of another. That kind of composition must be handled inside the template—and that’s exactly what variables are built for.

So it’s not just about convenience. It’s about owning the right logic in the right place, and designing your template in a way that’s:

  • Easier to use
  • Harder to misuse
  • More maintainable in the long run

In the next section, we’ll explore how to declare variables in Bicep, look at the different types of values they can hold, and see why even the simplest variables can bring clarity and structure to your templates.

Declaring Variables: The Basics

Now that we understand why variables exist, let’s take a closer look at how to define them in Bicep—and what they can actually hold.

At their core, variables in Bicep are incredibly simple:
They’re just named values that you define once and reuse throughout your template.

No need for string interpolation. No need for anything fancy. Sometimes, the best use of a variable is just a clean, static value:

1
var location = 'australiaeast'

This line defines a variable called location and gives it the value 'australiaeast'. You can now refer to location in as many places as you need—across resources, conditions, outputs, or anywhere else in your Bicep file.

Why Use a Simple Variable Like This?

You might be thinking, “Why not just hardcode 'australiaeast' directly wherever it’s needed?”

It’s a fair question—but it’s also the same reason a barista doesn’t measure each coffee shot manually for every order.

Let’s say you’re building multiple resources, and you want all of them deployed to the same region. If you hardcode 'australiaeast' into five different places and then later decide to switch to 'westus2', now you’ve got to hunt and update every one of those occurrences.

With a variable, you define the value once—at the top of the template—and then reuse it everywhere. Change it in one spot, and the whole deployment adjusts accordingly.

This is part of the template author’s role: to manage consistency, reduce duplication, and structure logic in a way that’s easy to reason about and evolve over time.

What Can a Variable Hold?

Bicep variables can store a wide variety of values—not just strings. You can use:

  • Strings:

    1
    
    var appName = 'myApp'
    
  • Integers:

    1
    
    var instanceCount = 3
    
  • Booleans:

    1
    
    var isProduction = false
    
  • Arrays:

    1
    
    var regions = ['eastus', 'australiaeast', 'westeurope']
    
  • Objects:

    1
    2
    3
    4
    
    var tags = {
      environment: 'dev'
      owner: 'platform-team'
    }
    

You can also use expressions or reference parameters inside your variable values. For example:

1
2
param environment string
var resourceGroupName = 'web-app-${environment}-rg'

Here, resourceGroupName is built using a parameter, but it’s computed and owned by the template itself—not passed in from outside.

Naming Conventions and Readability

By convention, Bicep uses camelCase for variable names—like resourceGroupName, instanceCount, or appTags. The goal is clarity.

Try to choose names that describe what the variable actually represents. Avoid cryptic names like x or val1. The more descriptive your variables, the more self-explanatory your template becomes.

Where Do Variables Belong?

Just like a barista preps ingredients and tools before the customer places an order, variables are typically declared near the top of your Bicep file—right after your parameters (if you have any). This gives your template a clear structure: inputs, internal logic, then resource definitions.

In the next section, we’ll see how these variables are used in real resource declarations—how to reference them, combine them, and apply logic through conditionals or loops.

You’ll also see why even simple variables, when used well, can significantly clean up your template and reduce the burden on your deployers.

Using Variables in Practice

Now that we know how to declare variables, let’s see how they’re actually used in a real Bicep template.

Variables can show up anywhere a value is expected—whether you’re naming a resource, setting a property, configuring tags, or building a loop.

Let’s walk through a few common patterns.

Referencing a Variable Inside a Resource

Here’s a simple example: we’ve defined a variable called location, and we use it to set the region for a resource group:

1
2
3
4
5
6
var location = 'australiaeast'

resource myRg 'Microsoft.Resources/resourceGroups@2022-09-01' = {
  name: 'myApp-dev-rg'
  location: location
}

Instead of hardcoding 'australiaeast' directly, we use the variable. If we need to deploy to a different region, we update the variable once—and the change applies everywhere.

This is especially helpful when multiple resources share the same location. Rather than repeating the region name, we reference the variable—which keeps things consistent and easier to update later.

Combining Variables and Parameters

Variables often work hand-in-hand with parameters.

Let’s say we define two parameters—projectName and environment—and use them to construct a consistent naming pattern:

1
2
3
4
5
6
7
8
9
param projectName string
param environment string

var baseName = '${projectName}-${environment}'

resource myRg 'Microsoft.Resources/resourceGroups@2022-09-01' = {
  name: '${baseName}-rg'
  location: 'australiaeast'
}

The variable baseName becomes a reusable building block you can use across multiple resources:

  • webAppName = '${baseName}-webapp'
  • storageAccountName = '${baseName}storage'
  • logWorkspaceName = '${baseName}-logs'

For example, if projectName is set to 'demoapp' and environment is 'test', then:

  • baseName becomes 'demoapp-test'
  • resourceGroupName becomes 'demoapp-test-rg'
  • storageAccountName becomes 'demoapp-teststorage'

This makes your template much easier to maintain—you’re enforcing structure without burdening the deployer with all the details.

Variables Can Reference Other Variables

Variables in Bicep can also reference other variables:

1
2
var baseName = '${projectName}-${environment}'
var storageAccountName = '${baseName}storage'

In this example, storageAccountName is built using baseName, which itself depends on parameters. This layered approach lets you compose values in a clean, predictable way.

Just keep in mind: Bicep variables are evaluated in the order they’re declared.

That means baseName must be defined before storageAccountName—otherwise, Bicep won’t know what baseName is yet, and you’ll get a compilation error.

This ordering requirement helps the Bicep engine resolve values step by step—just like following a recipe. You can use earlier ingredients in later steps, but not the other way around.

Conditional Variables

Variables can also use conditional logic to change values based on inputs:

1
2
3
4
param environment string

var isProd = environment == 'prod'
var sku = isProd ? 'Premium' : 'Standard'

Let’s unpack this.

  • The first line compares the value of environment to 'prod' using the == operator. This checks for equality and returns either true or false.
  • That result is stored in the variable isProd.
  • Then, the variable sku uses a ternary expression to set its value:
    • If isProd is true, sku becomes 'Premium'
    • Otherwise, it becomes 'Standard'

This condition ? trueValue : falseValue pattern is called a ternary operator. It’s a concise way to express “if this, then that; otherwise, something else.”

For example, if environment = 'prod', then:

  • isProd = true
  • sku = 'Premium'

But if environment = 'dev', then:

  • isProd = false
  • sku = 'Standard'

This pattern is powerful when you need to adapt configuration based on the environment—without requiring the deployer to explicitly provide every single setting.

Using Variables in Loops

Variables can also drive loops, which are especially useful for provisioning multiple similar resources.

1
2
3
4
5
6
var locations = ['eastus', 'westus', 'australiaeast']

resource regionalRgs 'Microsoft.Resources/resourceGroups@2022-09-01' = [for loc in locations: {
  name: 'demo-${loc}-rg'
  location: loc
}]

Let’s break this down:

  • locations is a variable holding an array of region names.
  • The [for loc in locations: {...}] syntax tells Bicep: “For each value in locations, assign it to loc and repeat the block inside.”
  • Inside the resource block:
    • loc represents one of the region names (like 'eastus')
    • We build a resource group name using string interpolation: 'demo-eastus-rg', 'demo-westus-rg', etc.

This loop will create three resource groups, one per region:

  • demo-eastus-rg
  • demo-westus-rg
  • demo-australiaeast-rg

This approach is perfect when deploying infrastructure to multiple regions or repeating a resource across different scopes.

Summary

In practice, variables are the glue that hold your logic together:

  • They help you avoid repetition
  • They allow you to build dynamic, consistent structures
  • They reduce the burden on the deployer
  • And they help your template adapt to real-world scenarios without becoming bloated

In the next section, we’ll look at best practices for working with variables—how to name them clearly, organise them logically, and avoid common pitfalls.

Best Practices for Using Variables in Bicep

At this point, you’ve seen how variables can make your Bicep templates more structured, dynamic, and easier to manage. But as with any powerful tool, how you use them matters just as much as why.

Let’s go over some best practices that will help you write clean, reliable, and maintainable Bicep code using variables.

1. Use Descriptive, Purposeful Names

A variable’s name should clearly describe what it holds. Avoid cryptic abbreviations or overly generic names.

1
2
3
4
5
// ✅ Good
var resourceGroupName = '${projectName}-${environment}-rg'

// ❌ Avoid
var x = '${projectName}-${environment}-rg'

When a future you—or someone else—reads the template weeks or months later, these names will make a world of difference.

Variables are usually declared near the top of the template, after parameters and before resources. If your template includes multiple logical sections (e.g. networking, storage, monitoring), try to group variables by function.

1
2
3
4
5
6
7
8
9
// Tags
var commonTags = {
  environment: environment
  project: projectName
}

// Naming
var baseName = '${projectName}-${environment}'
var storageAccountName = '${baseName}storage'

This structure improves readability and makes it easier to locate variables when you need to update or debug them.

3. Reuse Variables Instead of Repeating Logic

If you’re using the same expression more than once—especially across multiple resources—turn it into a variable.

1
2
3
4
5
6
7
8
9
// Instead of repeating this:
name: '${projectName}-${environment}-logs'
...
name: '${projectName}-${environment}-storage'

// Define it once:
var baseName = '${projectName}-${environment}'
var logWorkspaceName = '${baseName}-logs'
var storageAccountName = '${baseName}-storage'

This reduces duplication and keeps your logic in one place—so changes are easier, safer, and less error-prone.

4. Don’t Expose Internal Logic as Parameters

This is one of the most important design principles: don’t make deployers configure things they shouldn’t have to.

If a value is meant to follow a fixed rule—like a naming convention or a calculated setting—define it as a variable.

1
2
3
4
5
6
// ✅ Preferred
param environment string
var logWorkspaceName = 'logs-${environment}'

// ❌ Avoid
param logWorkspaceName string

Exposing this as a parameter suggests that the deployer has to decide or manage it, which increases complexity and risk.

Remember our coffee shop analogy:

The customer (deployer) should choose the drink; the barista (template author) handles how it’s made.

5. Avoid Overusing Variables

Not every value needs to be wrapped in a variable.

Sometimes it’s more readable to inline a short value—especially if it’s only used once and doesn’t involve any logic.

1
2
3
4
5
6
// ✅ Clear
location: 'australiaeast'

// ❌ Over-engineered
var location = 'australiaeast'
location: location

Use variables for clarity, reuse, or encapsulating logic—not just out of habit.

6. Use Variables to Test and Prototype

Variables are great for quickly adjusting values while experimenting with your template logic.

You can use them like temporary settings while prototyping:

1
var isEnabled = true

…then use them in conditions or property toggles:

1
condition: isEnabled

Once you’ve settled on a structure, you can decide whether to leave the value as a variable or promote it to a parameter (if it needs to be configurable).

Summary

Here’s a quick checklist to keep in mind when using variables in Bicep:

  • Name variables clearly and meaningfully
  • Group related variables to improve structure
  • Reuse logic with variables instead of repeating expressions
  • Don’t expose internal logic as parameters
  • Use variables where they add clarity—not everywhere
  • Leverage variables for quick experimentation and iteration

Variables are not just a technical feature—they’re a design tool. When used thoughtfully, they help you craft cleaner, smarter, and more reliable templates that respect the boundary between what the deployer configures and what the template takes care of.

In the next (and final) section, we’ll wrap up what we’ve learned and point you toward the next step on your Bicep journey.

Conclusion: Designing with Confidence

Variables may seem like a simple concept—but as we’ve seen, they’re much more than a convenience feature. They help define clear boundaries between what a deployer needs to configure and what the template should manage internally.

In this article, we explored:

  • The why behind variables—what problems they solve and how they complement parameters
  • How to declare variables for both static values and computed logic
  • How to use them in practice—from simple resource naming to loops and conditionals
  • Best practices that keep your templates clean, reusable, and intuitive

Whether you’re designing templates for a single team or for large-scale reuse across environments, variables give you a powerful way to encapsulate logic, enforce consistency, and reduce friction for everyone using your templates.

Think of variables as the thoughtful design decisions you make on behalf of the deployer. You’re not just building infrastructure—you’re shaping the developer experience around it.

What’s Next?

Now that you’ve mastered parameters and variables, you’re ready for the next piece of the puzzle: Bicep modules.

Modules let you break your templates into smaller, reusable building blocks—perfect for large projects or shared infrastructure patterns. And as you might expect, variables play a key role there too, helping you organise internal logic within each module.

We’ll explore all of that in the next article in the series.

Until then—great work, and keep writing great templates. You’ve just added another powerful tool to your Bicep toolkit. 💪