Azure Verified Modules (AVM) Requirements
This guide covers the mandatory requirements for Azure Verified Modules certification. These requirements ensure consistency, quality, and maintainability across Azure Terraform modules.
References:
Table of Contents
Module Cross-Referencing
Severity: MUST | Requirement: TFFR1
When building Resource or Pattern modules, module owners MAY cross-reference other modules. However:
- Modules MUST be referenced using HashiCorp Terraform registry reference to a pinned version
- Example:
source = "Azure/xxx/azurerm" with version = "1.2.3"
- Modules MUST NOT use git references (e.g.,
git::https://xxx.yyy/xxx.git or github.com/xxx/yyy)
- Modules MUST NOT contain references to non-AVM modules
Azure Provider Requirements
Severity: MUST | Requirement: TFFR3
Authors MUST only use the following Azure providers:
| Provider |
Min Version |
Max Version |
| azapi |
>= 2.0 |
< 3.0 |
| azurerm |
>= 4.0 |
< 5.0 |
Requirements:
- Authors MAY select either Azurerm, Azapi, or both providers
- MUST use
required_providers block to enforce provider versions
- SHOULD use pessimistic version constraint operator (
~>)
Example:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
azapi = {
source = "Azure/azapi"
version = "~> 2.0"
}
}
}
Code Style Standards
Lower snake_casing
Severity: MUST | Requirement: TFNFR4
MUST use lower snake_casing for:
- Locals
- Variables
- Outputs
- Resources (symbolic names)
- Modules (symbolic names)
Example: snake_casing_example
Resource & Data Source Ordering
Severity: SHOULD | Requirement: TFNFR6
- Resources that are depended on SHOULD come first
- Resources with dependencies SHOULD be defined close to each other
Count & for_each Usage
Severity: MUST | Requirement: TFNFR7
- Use
count for conditional resource creation
- MUST use
map(xxx) or set(xxx) as resource's for_each collection
- The map's key or set's element MUST be static literals
Example:
resource "azurerm_subnet" "pair" {
for_each = var.subnet_map
name = "${each.value}-pair"
resource_group_name = azurerm_resource_group.example.name
virtual_network_name = azurerm_virtual_network.example.name
address_prefixes = ["10.0.1.0/24"]
}
Resource & Data Block Internal Ordering
Severity: SHOULD | Requirement: TFNFR8
Order within resource/data blocks:
-
Meta-arguments (top):
-
Arguments/blocks (middle, alphabetical):
- Required arguments
- Optional arguments
- Required nested blocks
- Optional nested blocks
-
Meta-arguments (bottom):
depends_on
lifecycle (with sub-order: create_before_destroy, ignore_changes, prevent_destroy)
Separate sections with blank lines.
Module Block Ordering
Severity: SHOULD | Requirement: TFNFR9
Order within module blocks:
-
Top meta-arguments:
source
version
count
for_each
-
Arguments (alphabetical):
- Required arguments
- Optional arguments
-
Bottom meta-arguments:
Lifecycle ignore_changes Syntax
Severity: MUST | Requirement: TFNFR10
The ignore_changes attribute MUST NOT be enclosed in double quotes.
Good:
lifecycle {
ignore_changes = [tags]
}
Bad:
lifecycle {
ignore_changes = ["tags"]
}
Null Comparison for Conditional Creation
Severity: SHOULD | Requirement: TFNFR11
For parameters requiring conditional resource creation, wrap with object type to avoid "known after apply" issues during plan stage.
Recommended:
variable "security_group" {
type = object({
id = string
})
default = null
}
Dynamic Blocks for Optional Nested Objects
Severity: MUST | Requirement: TFNFR12
Nested blocks under conditions MUST use this pattern:
dynamic "identity" {
for_each = <condition> ? [<some_item>] : []
content {
}
}
Default Values with coalesce/try
Severity: SHOULD | Requirement: TFNFR13
Good:
coalesce(var.new_network_security_group_name, "${var.subnet_name}-nsg")
Bad:
var.new_network_security_group_name == null ? "${var.subnet_name}-nsg" : var.new_network_security_group_name
Provider Declarations in Modules
Severity: MUST | Requirement: TFNFR27
provider MUST NOT be declared in modules (except for configuration_aliases)
provider blocks in modules MUST only use alias
- Provider configurations SHOULD be passed in by module users
Variable Requirements
Not Allowed Variables
Severity: MUST | Requirement: TFNFR14
Module owners MUST NOT add variables like enabled or module_depends_on to control entire module operation. Boolean feature toggles for specific resources are acceptable.
Variable Definition Order
Severity: SHOULD | Requirement: TFNFR15
Variables SHOULD follow this order:
- All required fields (alphabetical)
- All optional fields (alphabetical)
Variable Naming Rules
Severity: SHOULD | Requirement: TFNFR16
Variables with Descriptions
Severity: SHOULD | Requirement: TFNFR17
description SHOULD precisely describe the parameter's purpose and expected data type
- Target audience is module users, not developers
- For
object types, use HEREDOC format
Variables with Types
Severity: MUST | Requirement: TFNFR18
type MUST be defined for every variable
type SHOULD be as precise as possible
any MAY only be used with adequate reasons
- Use
bool instead of string/number for true/false values
- Use concrete
object instead of map(any)
Sensitive Data Variables
Severity: SHOULD | Requirement: TFNFR19
If a variable's type is object and contains sensitive fields, the entire variable SHOULD be sensitive = true, or extract sensitive fields into separate variables.
Non-Nullable Defaults for Collections
Severity: SHOULD | Requirement: TFNFR20
Nullable SHOULD be set to false for collection values (sets, maps, lists) when using them in loops. For scalar values, null may have semantic meaning.
Discourage Nullability by Default
Severity: MUST | Requirement: TFNFR21
nullable = true MUST be avoided unless there's a specific semantic need for null values.
Avoid sensitive = false
Severity: MUST | Requirement: TFNFR22
sensitive = false MUST be avoided (this is the default).
Sensitive Default Value Conditions
Severity: MUST | Requirement: TFNFR23
A default value MUST NOT be set for sensitive inputs (e.g., default passwords).
Handling Deprecated Variables
Severity: MUST | Requirement: TFNFR24
- Move deprecated variables to
deprecated_variables.tf
- Annotate with
DEPRECATED at the beginning of description
- Declare the replacement's name
- Clean up during major version releases
Output Requirements
Additional Terraform Outputs
Severity: SHOULD | Requirement: TFFR2
Authors SHOULD NOT output entire resource objects as these may contain sensitive data and the schema can change with API or provider versions.
Best Practices:
- Output computed attributes of resources as discrete outputs (anti-corruption layer pattern)
- SHOULD NOT output values that are already inputs (except
name)
- Use
sensitive = true for sensitive attributes
- For resources deployed with
for_each, output computed attributes in a map structure
Examples:
output "foo" {
description = "MyResource foo attribute"
value = azurerm_resource_myresource.foo
}
output "childresource_foos" {
description = "MyResource children's foo attributes"
value = {
for key, value in azurerm_resource_mychildresource : key => value.foo
}
}
output "bar" {
description = "MyResource bar attribute"
value = azurerm_resource_myresource.bar
sensitive = true
}
Sensitive Data Outputs
Severity: MUST | Requirement: TFNFR29
Outputs containing confidential data MUST be declared with sensitive = true.
Handling Deprecated Outputs
Severity: MUST | Requirement: TFNFR30
- Move deprecated outputs to
deprecated_outputs.tf
- Define new outputs in
outputs.tf
- Clean up during major version releases
Local Values Standards
locals.tf Organization
Severity: MAY | Requirement: TFNFR31
locals.tf SHOULD only contain locals blocks
- MAY declare
locals blocks next to resources for advanced scenarios
Alphabetical Local Arrangement
Severity: MUST | Requirement: TFNFR32
Expressions in locals blocks MUST be arranged alphabetically.
Precise Local Types
Severity: SHOULD | Requirement: TFNFR33
Use precise types (e.g., number for age, not string).
Terraform Configuration Requirements
Terraform Version Requirements
Severity: MUST | Requirement: TFNFR25
terraform.tf requirements:
- MUST contain only one
terraform block
- First line MUST define
required_version
- MUST include minimum version constraint
- MUST include maximum major version constraint
- SHOULD use
~> #.# or >= #.#.#, < #.#.# format
Example:
terraform {
required_version = "~> 1.6"
required_providers {
azurerm = {