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:
|
|
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
:
|
|
Then you use variables inside your template to construct the rest:
|
|
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:
|
|
…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:
|
|
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:
|
|
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:
|
|
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:
|
|
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'
andenvironment
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:
|
|
In this example,
storageAccountName
is built usingbaseName
, 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 beforestorageAccountName
—otherwise, Bicep won’t know whatbaseName
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:
|
|
Let’s unpack this.
- The first line compares the value of
environment
to'prod'
using the==
operator. This checks for equality and returns eithertrue
orfalse
. - That result is stored in the variable
isProd
. - Then, the variable
sku
uses a ternary expression to set its value:- If
isProd
istrue
,sku
becomes'Premium'
- Otherwise, it becomes
'Standard'
- If
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.
|
|
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 inlocations
, assign it toloc
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.
|
|
When a future you—or someone else—reads the template weeks or months later, these names will make a world of difference.
2. Group Related Variables Together
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
…then use them in conditions or property toggles:
|
|
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. 💪