---
title: Terraform Composition
linktitle: Composition
description: Terraform Composition description for the Azure Verified Modules (AVM) program
weight: 3
---


{{% notice style="important" %}}
This guide **MUST** be used in conjunction with the [Terraform specifications]({{% siteparam base %}}/specs/tf/). **ALL AVM modules** (Resource and Pattern modules) **MUST meet the respective requirements described in these specifications**!
{{% /notice %}}

## Repositories

Each Terraform AVM module will have its own GitHub Repository in the [`Azure` GitHub Organization](https://github.com/Azure) as per [SNFR19]({{% siteparam base %}}/spec/SNFR19).

This repo will be created by the Module Owners and the AVM Core team collaboratively, including the configuration of permissions as per [SNFR9]({{% siteparam base %}}/spec/SNFR9)

## Directory and File Structure

Below is the directory and file structure expected for each AVM Terraform repository/module.
See template repo [here](https://github.com/Azure/terraform-azurerm-avm-template).

- `tests/` - (for unit tests and integration tests using Terraform test)
  - `unit/` - (optional .tftest.hcl files for unit testing with Terraform test)
  - `integration/` - (optional .tftest.hcl files for integration testing with Terraform test)
- `modules/` - (for sub-modules only if used)
- `examples/` - (all examples must deploy successfully without requiring input - these are customer facing)
  - `<at least one folder>` - (at least one example that uses the variable defaults minimum/required parameters/variables only)
  - `<other folders for examples as required>`
- `/...` - (Module files that live in the root of module directory)
  - `_header.md` - (required for documentation generation)
  - `_footer.md` - (required for documentation generation)
  - `main.tf`
  - `locals.tf`
  - `variables.tf`
  - `outputs.tf`
  - `terraform.tf`
  - `README.md` (autogenerated)
  - `main.resource1.tf` (If a larger module you may chose to use dot notation for each resource)
  - `locals.resource1.tf`

## Code Styling

This section points to conventions to be followed when developing a module.

### Casing

Use `snake_casing` as per [TFNFR3]({{% siteparam base %}}/spec/TFNFR3).

### Input Parameters and Variables

Make sure to review all specifications of `Category: Inputs/Outputs` within the [Terraform specification]({{% siteparam base %}}/specs/tf/) pages.

{{% notice style="tip" %}}
See examples in specifications [SNFR14]({{% siteparam base %}}/spec/SNFR14) and [TFFR14]({{% siteparam base %}}/spec/TFFR14).
{{% /notice %}}

### Resources

Resources are primarily leveraged by resource modules to declare the primary resource of the main resource type deployed by the AVM module.

Make sure to review all specifications covering resource properties and usage.

{{% notice style="tip" %}}
See examples in specifications [SFR1]({{% siteparam base %}}/spec/SFR1) and [RMFR1]({{% siteparam base %}}/spec/RMFR1).
{{% /notice %}}

### Outputs

Make sure to review all specifications of `Category: Inputs/Outputs` within the [Terraform specification]({{% siteparam base %}}/specs/tf/) pages.

{{% notice style="tip" %}}
See examples in specification [RMFR7]({{% siteparam base %}}/spec/RMFR7) and [TFFR2]({{% siteparam base %}}/spec/TFFR2).
{{% /notice %}}

## Interfaces

{{% notice style="note" %}}

This section is only relevant for contributions to resource modules.

{{% /notice %}}

To meet [RMFR4]({{% siteparam base %}}/spec/RMFR4) and [RMFR5]({{% siteparam base %}}/spec/RMFR5) AVM resource modules must leverage consistent interfaces for all the optional features/extension resources supported by the AVM module primary resource.

Please refer to the [Terraform Interfaces]({{% siteparam base %}}/specs/tf/interfaces/) page.

## Required AzAPI patterns

Every Terraform AVM resource module **MUST** implement the following AzAPI patterns. The cross-references point at the normative specs — this section only summarises them so that nothing here is missed during scaffolding.

| Spec                                                       | One-liner                                                                                                                                                                                                                                       |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [TFRMFR1]({{% siteparam base %}}/spec/TFRMFR1)             | Expose the parent scope as a single required `parent_id` string variable. **Do not** expose `resource_group_name` or any other scope-specific input. Validate with `provider::azapi::parse_resource_id` against the expected parent type.       |
| [TFRMNFR1]({{% siteparam base %}}/spec/TFRMNFR1)           | Implement every ARM subresource as a Terraform submodule under `modules/<subresource-singular-name>/`. Parent modules **MUST** reference submodules, and submodules **MUST** be independently consumable. Keep submodule primary resources single-instance only (no `count` / `for_each` on `azapi_resource.this`); cardinality belongs at the module call site. |
| [TFRMNFR2]({{% siteparam base %}}/spec/TFRMNFR2)           | Name the primary `azapi_resource` `this`. Satellite resources **MUST** be named after what they represent (e.g. `azapi_resource.lock`, `azapi_resource.role_assignment`, `azapi_resource.diagnostic_setting`, `azapi_resource.private_endpoint`), not `this`. |
| [TFFR4]({{% siteparam base %}}/spec/TFFR4)                 | Always set `response_export_values` on every AzAPI resource (use `[]` when nothing needs exporting). Include any read-only properties the module's outputs or downstream resources depend on.                                                  |
| [TFFR5]({{% siteparam base %}}/spec/TFFR5)                 | Always set `replace_triggers_refs` on every AzAPI resource. List the body paths that **MUST** force replacement when they change; `name` and `location` are already triggers, so don't repeat them.                                            |
| [TFFR6]({{% siteparam base %}}/spec/TFFR6)                 | Source the `type` argument of every AzAPI resource from a single `resource_types` object variable instead of hard-coding type strings. Use one optional key per resource, defaulted to the tested API version, and cascade the relevant subset to each submodule. |
| [TFFR7]({{% siteparam base %}}/spec/TFFR7)                 | Expose `retry` and `timeouts` variables, apply them to every `azapi_resource`, and cascade them to every submodule.                                                                                                                              |
| [TFNFR38]({{% siteparam base %}}/spec/TFNFR38)             | Validate every variable (or nested attribute) that holds an Azure ARM resource ID using `can(provider::azapi::parse_resource_id("Microsoft.X/y", value))`. Hand-rolled regex / `startswith` / `length` checks **MUST NOT** be used.              |
| [TFNFR39]({{% siteparam base %}}/spec/TFNFR39)             | Use the standard file layout (`terraform.tf`, `variables.tf`, `outputs.tf`, `main.tf`, `locals.tf`). Larger modules **MAY** split `main.tf` into `main.<topic>.tf` files.                                                                          |

The interface schema files under `static/includes/interfaces/tf/` are the canonical, copy-pasteable templates for the variables described by these specs. Treat them as authoritative.

## Telemetry

To meet the requirements of [SFR3]({{% siteparam base %}}/spec/SFR3) & [SFR4]({{% siteparam base %}}/spec/SFR4), we use the [modtm](https://registry.terraform.io/providers/Azure/modtm/latest) telemetry provider. This lightweight telemetry provider sends telemetry data to Azure Application Insights via a HTTP POST front end service.

The `modtm` telemetry provider is included in all Terraform modules and is enabled by default through the [main.telemetry.tf](https://github.com/Azure/terraform-azurerm-avm-template/blob/main/main.telemetry.tf) file being automatically distributed from the template repo. You do not need to change this configuration.

Make sure that the `modtm` provider is listed under the `required_providers` section in the module's `terraform.tf` file using the following entry. This is also validated by the linter.

```terraform
terraform {
  required_providers {
    # .. other required providers as needed
    modtm = {
      source = "Azure/modtm"
      version = "~> 0.3"
    }
  }
}
```
