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
- Modules MUST NOT use git references (e.g.,
git::https://xxx.yyy/xxx.git
or )
- 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 block to enforce provider versions
- SHOULD use pessimistic version constraint operator ()
Example:
hcl
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)
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 for conditional resource creation
- MUST use or as resource's collection
- The map's key or set's element MUST be static literals
Example:
hcl
resource "azurerm_subnet" "pair" {
for_each = var.subnet_map # map(string)
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):
Separate sections with blank lines.
Module Block Ordering
Severity: SHOULD | Requirement: TFNFR9
Order within module blocks:
-
Top meta-arguments:
-
Arguments (alphabetical):
- Required arguments
- Optional arguments
-
Bottom meta-arguments:
Lifecycle ignore_changes Syntax
Severity: MUST | Requirement: TFNFR10
The
attribute
MUST NOT be enclosed in double quotes.
Good:
hcl
lifecycle {
ignore_changes = [tags]
}
Bad:
hcl
lifecycle {
ignore_changes = ["tags"]
}
Null Comparison for Conditional Creation
Severity: SHOULD | Requirement: TFNFR11
For parameters requiring conditional resource creation, wrap with
type to avoid "known after apply" issues during plan stage.
Recommended:
hcl
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:
hcl
dynamic "identity" {
for_each = <condition> ? [<some_item>] : []
content {
# block content
}
}
Default Values with coalesce/try
Severity: SHOULD | Requirement: TFNFR13
Good:
hcl
coalesce(var.new_network_security_group_name, "${var.subnet_name}-nsg")
Bad:
hcl
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
- MUST NOT be declared in modules (except for )
- blocks in modules MUST only use
- 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
or
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
- Follow HashiCorp's naming rules
- Feature switches SHOULD use positive statements: instead of
Variables with Descriptions
Severity: SHOULD | Requirement: TFNFR17
- SHOULD precisely describe the parameter's purpose and expected data type
- Target audience is module users, not developers
- For types, use HEREDOC format
Variables with Types
Severity: MUST | Requirement: TFNFR18
- MUST be defined for every variable
- SHOULD be as precise as possible
- MAY only be used with adequate reasons
- Use instead of / for true/false values
- Use concrete instead of
Sensitive Data Variables
Severity: SHOULD | Requirement: TFNFR19
If a variable's type is
and contains sensitive fields, the entire variable
SHOULD be
, or extract sensitive fields into separate variables.
Non-Nullable Defaults for Collections
Severity: SHOULD | Requirement: TFNFR20
Nullable
SHOULD be set to
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
MUST be avoided unless there's a specific semantic need for null values.
Avoid sensitive = false
Severity: MUST | Requirement: TFNFR22
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
- Annotate with 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 )
- Use for sensitive attributes
- For resources deployed with , output computed attributes in a map structure
Examples:
hcl
# Single resource computed attribute
output "foo" {
description = "MyResource foo attribute"
value = azurerm_resource_myresource.foo
}
# for_each resources
output "childresource_foos" {
description = "MyResource children's foo attributes"
value = {
for key, value in azurerm_resource_mychildresource : key => value.foo
}
}
# Sensitive output
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
.
Handling Deprecated Outputs
Severity: MUST | Requirement: TFNFR30
- Move deprecated outputs to
- Define new outputs in
- Clean up during major version releases
Local Values Standards
locals.tf Organization
Severity: MAY | Requirement: TFNFR31
- SHOULD only contain blocks
- MAY declare blocks next to resources for advanced scenarios
Alphabetical Local Arrangement
Severity: MUST | Requirement: TFNFR32
Expressions in
blocks
MUST be arranged alphabetically.
Precise Local Types
Severity: SHOULD | Requirement: TFNFR33
Use precise types (e.g.,
for age, not
).
Terraform Configuration Requirements
Terraform Version Requirements
Severity: MUST | Requirement: TFNFR25
- MUST contain only one block
- First line MUST define
- MUST include minimum version constraint
- MUST include maximum major version constraint
- SHOULD use or format
Example:
hcl
terraform {
required_version = "~> 1.6"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
Providers in required_providers
Severity: MUST | Requirement: TFNFR26
- block MUST contain block
- Each provider MUST specify and
- Providers SHOULD be sorted alphabetically
- Only include directly required providers
- MUST be in format
- MUST include minimum and maximum major version constraints
- SHOULD use or format
Testing Requirements
Test Tooling
Severity: MUST | Requirement: TFNFR5
Required testing tools for AVM:
- Terraform (
terraform validate/fmt/test
)
- terrafmt
- Checkov
- tflint (with azurerm ruleset)
- Go (optional for custom tests)
Test Provider Configuration
Severity: SHOULD | Requirement: TFNFR36
For robust testing,
prevent_deletion_if_contains_resources
SHOULD be explicitly set to
in test provider configurations.
Documentation Requirements
Module Documentation Generation
Severity: MUST | Requirement: TFNFR2
- Documentation MUST be automatically generated via Terraform Docs
- A file MUST be present in the module root
Breaking Changes & Feature Management
Using Feature Toggles
Severity: MUST | Requirement: TFNFR34
New resources added in minor/patch versions MUST have a toggle variable to avoid creation by default:
hcl
variable "create_route_table" {
type = bool
default = false
nullable = false
}
resource "azurerm_route_table" "this" {
count = var.create_route_table ? 1 : 0
# ...
}
Reviewing Potential Breaking Changes
Severity: MUST | Requirement: TFNFR35
Breaking changes requiring caution:
Resource blocks:
- Adding new resource without conditional creation
- Adding arguments with non-default values
- Adding nested blocks without
- Renaming resources without blocks
- Changing to or vice versa
Variable/Output blocks:
- Deleting/renaming variables
- Changing variable
- Changing variable values
- Changing to false
- Changing from false to true
- Adding variables without
- Deleting outputs
- Changing output
- Changing output value
Contribution Standards
GitHub Repository Branch Protection
Severity: MUST | Requirement: TFNFR3
Module owners
MUST set branch protection policies on the default branch (typically
):
- Require Pull Request before merging
- Require approval of most recent reviewable push
- Dismiss stale PR approvals when new commits are pushed
- Require linear history
- Prevent force pushes
- Not allow deletions
- Require CODEOWNERS review
- No bypassing settings allowed
- Enforce for administrators
Compliance Checklist
Use this checklist when developing or reviewing Azure Verified Modules:
Module Structure
Code Style
Variables
Outputs
Terraform Configuration
Testing & Quality
Summary Statistics
- Functional Requirements: 3
- Non-Functional Requirements: 34
- Total Requirements: 37
By Severity
- MUST: 21 requirements
- SHOULD: 14 requirements
- MAY: 2 requirements
Based on: Azure Verified Modules - Terraform Requirements