Terraform validated your configuration against the schema the provider or module declares, and the name is not in it. Three common reasons.
1. A provider major version changed it
The most frequent cause. Arguments are removed and renamed across major versions.
terraform version
terraform providers
provider registry.terraform.io/hashicorp/aws v5.70.0
Ask the provider directly rather than reading documentation for the latest version:
terraform providers schema -json \
| python3 -c "import sys,json; s=json.load(sys.stdin); \
r=s['provider_schemas']['registry.terraform.io/hashicorp/aws']['resource_schemas']['aws_security_group']; \
print('\n'.join(sorted(r['block']['attributes'])))"
That is authoritative for the version you actually have.
Some AWS provider examples that catch people:
| Removed | Replacement |
|---|---|
aws_s3_bucket inline versioning block | aws_s3_bucket_versioning resource |
aws_s3_bucket inline lifecycle_rule | aws_s3_bucket_lifecycle_configuration |
aws_s3_bucket inline acl | aws_s3_bucket_acl |
aws_instance vpc | subnet_id |
aws_security_group inline ingress | aws_vpc_security_group_ingress_rule |
The v4 split of aws_s3_bucket into a dozen separate resources caused a very large number of these errors. Each inline block became its own resource, and each needs importing rather than simply rewriting, because the state addresses changed.
Pin the version so an upgrade is deliberate:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.70"
}
}
}
2. A block written as an argument, or vice versa
Error: Unsupported block type
Blocks of type "ingress" are not expected here.
HCL distinguishes the two:
tags = { Name = "web" } # argument: name, equals, value
ingress { # block: name, braces
from_port = 80
}
Getting it the wrong way round gives one of these two errors. The schema output above tells you which a given name is: attributes are arguments, block_types are blocks.
terraform providers schema -json \
| python3 -c "import sys,json; s=json.load(sys.stdin); \
r=s['provider_schemas']['registry.terraform.io/hashicorp/aws']['resource_schemas']['aws_security_group']; \
print('blocks:', ', '.join(sorted(r['block'].get('block_types',{}))))"
3. A module input was renamed
Error: Unsupported argument
An argument named "enable_nat" is not expected here.
on main.tf line 12, in module "vpc":
The error names the module block. Read what that version declares:
grep -rh "^variable" .terraform/modules/vpc/*.tf | sort
.terraform/modules/ holds the version actually downloaded, which is what matters. Module registries keep documentation per version, and reading the wrong version's docs is the usual way into this.
Pin modules too:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.13"
}
A source with no version takes the newest on every init -upgrade, which makes this error appear without any change on your part.
"Missing required argument"
The inverse, and often the other half of the same rename:
Error: Missing required argument
The argument "name" is required, but no definition was found.
terraform providers schema -json \
| python3 -c "import sys,json; s=json.load(sys.stdin); \
r=s['provider_schemas']['registry.terraform.io/hashicorp/aws']['resource_schemas']['aws_security_group']; \
print('\n'.join(k for k,v in r['block']['attributes'].items() if v.get('required')))"
Upgrading deliberately
terraform init -upgrade
terraform plan
Read the provider's upgrade guide before a major bump. The AWS provider publishes one per major version listing every removed argument and its replacement, which turns a long debugging session into a checklist.
Where a change also moves state addresses, as with the aws_s3_bucket split, terraform plan proposes destroying and recreating. Use moved blocks or import rather than applying that:
import {
to = aws_s3_bucket_versioning.assets
id = "acme-assets"
}
Validate before applying
terraform validate
validate checks configuration against the provider schema without touching state or the network, so it catches every one of these in a second. It belongs in CI ahead of plan:
script:
- terraform init -lockfile=readonly
- terraform fmt -check
- terraform validate
- terraform plan -out=tfplan
A checklist
terraform versionandterraform providers.terraform providers schema -jsonfor the authoritative argument list.- Check the provider's upgrade guide for that major version.
- Argument versus block:
=and a value, or a name and braces. - Module error → read
variabledeclarations under.terraform/modules/. - Pin both provider and module versions so upgrades are deliberate.
- State addresses changed →
movedorimport, not destroy and recreate. - Run
terraform validatein CI beforeplan.
Frequently Asked Questions
Why did my configuration stop working without me changing it?
Almost certainly a provider or module version moved. A provider constraint such as ~> 5.0 allows any 5.x release, and a module source with no version takes the newest available, so a terraform init -upgrade, or a fresh CI run with no committed lock file, can pick up a release that renamed or removed the argument. Pin versions and commit .terraform.lock.hcl so version changes only happen when someone makes them.
How do I find out which arguments a resource actually supports?
terraform providers schema -json returns the complete schema for the provider versions you have installed, which is authoritative in a way that documentation is not, since docs show the latest version. The attributes map lists arguments and the block_types map lists nested blocks, which also resolves "Unsupported block type" errors. Filtering for entries marked required gives you the list for "Missing required argument".
What is the difference between Unsupported argument and Unsupported block type?
HCL has two syntaxes: an argument is name = value and a block is name { ... }. Each schema entry is one or the other, so writing ingress = {...} where a block is expected, or tags { ... } where an argument is expected, produces the corresponding error. The provider schema tells you which form each name takes. Confusingly, some providers accept both forms for certain nested structures, which is why the distinction is not always obvious from examples.
Why did the AWS provider v4 upgrade break so many configurations?
Because it split aws_s3_bucket into around a dozen separate resources, so inline versioning, lifecycle_rule, acl, logging and other blocks all became standalone resources such as aws_s3_bucket_versioning. Rewriting the configuration is only half the work: the state addresses changed too, so a plain plan proposes destroying and recreating the bucket. The correct migration uses import blocks or terraform import to bring each new resource into state pointing at the existing bucket.
Can I catch these before running a plan?
Yes. terraform validate checks the configuration against the installed provider schemas without touching state or making network calls, so it finds every unsupported argument, unsupported block and missing required argument in about a second. Put it in CI between init and plan, alongside terraform fmt -check. It will not catch anything requiring state, such as an unknown count, but it eliminates this entire class of error before a plan is attempted.