A Guide to Compile-Time, Template, and Runtime Expressions in Azure Pipelines
Azure Pipelines support two types of expressions to control pipeline behavior: compile-time expressions (evaluated at pipeline creation) and runtime expressions (evaluated during pipeline execution). Template expressions, a subcategory of compile-time, help you create reusable pipeline components.
The key takeaway: Use compile-time expressions with parameters for fixed values needed at pipeline creation, and runtime expressions with variables for dynamic behavior based on execution state.
Want to dive deeper into all this terminology? Read along to learn all the details.
Table of Contents
- Pipeline Parameters - Static values set before pipeline runs
- Variables - Dynamic values that can change during execution
- Compile-time Expressions - For build configuration and static logic
- Template Expressions - For reusable pipeline components
- Runtime Expressions - For dynamic decision-making
- Common Pitfalls - Best practices and issues to avoid
Let’s start with the building blocks: parameters and variables.
Pipeline parameters
Parameters are compile-time values referenced using ${{ parameters.name }} syntax:
parameters:
- name: shouldBuildProject
type: boolean
default: true
This parameter appears as a checkbox in the Azure Pipelines UI:

Variables
Unlike parameters, variables can be modified dynamically during pipeline execution. Variables can be defined at different scopes, each with its own visibility and lifetime:
- Pipeline scope: Global visibility (all stages, jobs, steps). Used for configuration values needed throughout the pipeline.
- Stage scope: Limited to a specific stage and its jobs. Used for stage-specific settings.
- Job scope: Limited to a specific job and its steps. Used for job-specific configurations.
All three scopes are shown in this example:
# Pipeline-level variables
variables:
globalConfig: 'production'
stages:
- stage: Build
variables:
buildConfiguration: 'Release' # Stage-level variable
jobs:
- job: Compile
variables:
compilerFlags: '--optimize' # Job-level variable
steps:
- script: |
echo "Global config: $(globalConfig)"
echo "Build config: $(buildConfiguration)"
echo "Compiler flags: $(compilerFlags)"
Variables can be referenced using three different syntaxes, each with its specific use case:
$(variableName): The macro syntax. Used in task inputs and scripts${{ variables.variableName }}: The template expression syntax. Used in templates and compile-time expressions$[variables.variableName]: The runtime expression syntax. Used with the condition-keyword and runtime expressions
Compile-time expressions
The syntax for compile-time expressions looks like ${{ <expression> }} and these expressions can be used with pipeline parameters or statically defined variables.
Compile-time expressions are evaluated during pipeline compilation (when the pipeline is getting created) and cannot react to changes that occur during pipeline execution.
Example of a job with compile-time conditions:
jobs:
- job: Build
condition: ${{ eq(parameters.shouldBuildProject, true) }}
steps:
- script: echo "Building project..."
condition: ${{ and(eq(parameters.configuration, 'Release'), eq(parameters.enableTests, true)) }}
Template Expressions
Template expressions create reusable pipeline components with parameterized behavior:
# build-template.yml
parameters:
solution: ''
buildConfiguration: 'Release'
runTests: true
steps:
- script: dotnet build ${{ parameters.solution }} --configuration ${{ parameters.buildConfiguration }}
displayName: 'Build Solution'
# Conditional inclusion of steps based on parameter
- $${{ if eq(parameters.runTests, true) }}:
- script: dotnet test
displayName: 'Run Tests'
Here is how to use the template in your pipeline:
# azure-pipelines.yml
trigger:
- main
jobs:
- job: Build
steps:
- template: build-template.yml
parameters:
solution: '**/*.sln'
buildConfiguration: 'Debug'
runTests: false
Runtime Expressions
Runtime expressions ($(<expression>) or $[<expression>]) use variables to make decisions during pipeline execution:
Here are examples of runtime expressions inside conditions:
# Using array syntax (preferred for runtime conditions)
condition: eq(variables['hasUnitTests'], 'true')
# Using $[] syntax (alternative syntax)
condition: $[eq(variables['hasUnitTests'], 'true')]
# Complex condition with multiple checks
condition: $[and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/main'), eq(variables['Build.Reason'], 'PullRequest')))]
Note: In conditions, the $[] wrapper is optional when using functions like eq(), and(), etc. However, when directly referencing variables in other contexts, you must use either $(name) or $[variables.name].
Another way of dynamically setting a runtime variable is by using the ##vso prefix:
- script: echo "##vso[task.setvariable variable=hasUnitTests]true"
This is a special logging command syntax that Azure Pipelines uses to perform various operations during pipeline execution. These commands follow the format: ##vso[area.command]value
The isOutput property
When isOutput=true is specified in a task, the variable becomes accessible across dependent jobs or tasks. These variables are referenced then using the dependencies.<TaskName>.outputVariables['<VariableName>'] syntax.
Example:
- script: echo "##vso[task.setvariable variable=hasUnitTests;isOutput=true]true"
name: MyTask
- script: echo "This is a dependent task"
condition: eq(dependencies.MyTask.outputVariables['hasUnitTests'], 'true')
Common Pitfalls and Best Practices
-
Mixing Expression Types
- Don’t use runtime variables (
$(var)) in compile-time expressions ($) - Parameters can only be used with compile-time expressions (
${{ parameters.name }}) - Template expressions cannot access runtime values
- Don’t use runtime variables (
- Variable Scope Issues
- Variables defined in one job are not automatically available in other jobs
- Use
isOutput=trueand dependencies syntax for cross-job communication - Stage variables are not accessible from other stages
- Environment variables set in scripts need
task.setvariablecommand
- Parameter vs. Variable Choice
- Use parameters for:
- Template customization
- Build configuration selection
- Feature flags that must be set before runtime
- Use variables for:
- Values that change during execution
- Task outputs that need to be shared
- Environment-specific settings
- Use parameters for:
Feel free to share this article or leave a comment.