Terraform Unused Declaration Linter

Find what is declared and never used, and what is used and never declared. One of these costs money on every plan, and one of them is a breaking change to delete.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Check. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

An unused variable and an unused data source

The data source is the expensive one: an API call on every plan for nothing

variable "unused" {
  type = string
}

data "aws_ami" "ubuntu" {
  most_recent = true
}

resource "aws_s3_bucket" "logs" {
  bucket = "my-logs"
}

A reference with no declaration

Fails at plan, and in a real module the declaration may just be in another file

variable "declared" {
  type = string
}

resource "aws_s3_bucket" "a" {
  bucket = var.declared
  tags   = var.missing
}

A local left behind by a refactor

Nothing consumes it, and unlike a variable there is no interface to preserve

locals {
  used   = "a"
  unused = "b"
}

output "o" {
  value = local.used
}

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Deleting an unused variable from a shared module

    It is part of the module's published interface. Every caller setting it breaks on the next upgrade, with an error about an undeclared variable in a module they did not change.

    Instead:Deprecate it in the description, and remove it on a major version.

  2. Leaving a data source in place after the thing using it went away

    It is still read on every plan and apply, still counts against the provider's rate limit, and still fails the plan if it cannot be read.

    Instead:Delete it, or move it inside the check block that needs it if it is there to probe something.

  3. Running a linter like this against one file of a module

    Terraform treats every .tf file in the directory as one module, so a variable used elsewhere in the same module looks unused.

    Instead:Concatenate the module's files before checking, or verify each finding against the whole directory.

An unused data source is not free

It is an API call on every plan and every apply, it counts against the provider's rate limit, and if it cannot be read the plan fails. Dead weight with a running cost.

Deleting an unused variable is not always safe

In a root module it is dead configuration that everybody setting values still has to reason about, so delete it. In a module other people call, that variable is part of the published interface: removing it breaks every caller that sets it, which makes it a major version rather than a tidy-up.

An unused local is simply dead

There is no interface to preserve. They accumulate when an expression is factored out of one place and the original is left behind, and they are the cheapest thing on this page to delete.

An unused data source has a real cost

Terraform reads it on every plan whether or not anything uses it. On an account near a rate limit that is a plan that fails intermittently, for a value nobody consumes. If it is there to probe something, a check block is where it belongs, because a failure there is a warning rather than a failed plan.

A reference with no declaration fails at plan

Terraform's message is clear enough on its own. It appears here because in a module split across files the declaration may be in a file you did not paste, which is the same reason this page cannot be trusted on a partial module.

Outputs are not checked, on purpose

An output exists to be consumed by something outside the module, so nothing inside it refers to one. "Unused" is not a thing that can be determined from the module, and reporting them all would be noise.

This only sees what you pasted

A module is every .tf file in its directory and Terraform does not care which file anything is in. So a variable used in a file you did not include looks unused here. Concatenate the directory before trusting a finding that looks wrong.