hashicorp/terraform · error

: name may contain only lowercase letters, digits and…

Error message

%s%s: name may contain only lowercase letters, digits and underscores

What it means

During schema validation, a nested block type name does not match the required pattern ^[a-z0-9_]+$. Terraform requires block type names to contain only lowercase letters, digits, and underscores — no uppercase, hyphens, dots, or other characters. This ensures names map cleanly to HCL identifiers and provider SDK conventions.

Solutions

  1. Rename the nested block type to use only lowercase letters, digits, and underscores (e.g., 'SubnetGroup' → 'subnet_group').
  2. If the name must reflect a hyphenated API field, use underscores: 'load-balancer' → 'load_balancer'.
  3. If using terraform-plugin-framework, the schema definitions naturally enforce this; check for string literals that bypass the framework.
  4. Add a unit test calling InternalValidate() to catch naming violations before release.

Example fix

// before — invalid block name with hyphen
BlockTypes: map[string]*NestedBlock{
    "sub-net": {Nesting: ListNesting, ...},  // ← hyphen not allowed
}

// after — snake_case
BlockTypes: map[string]*NestedBlock{
    "sub_net": {Nesting: ListNesting, ...},
}
Defensive patterns

Strategy: validation

Validate before calling

// Provider developers: enforce naming convention on block types
import "regexp"

var validBlockName = regexp.MustCompile(`^[a-z0-9_]+$`)

func validateBlockTypeNames(b *configschema.Block) error {
    for name := range b.BlockTypes {
        if !validBlockName.MatchString(name) {
            return fmt.Errorf("block type name %q must be lowercase letters, digits, and underscores only", name)
        }
    }
    return b.InternalValidate()
}

// Unit test gate:
func TestBlockNamesConform(t *testing.T) {
    if err := myResourceSchema().InternalValidate(); err != nil {
        t.Fatal(err)
    }
}

Prevention

When it happens

Trigger: A key in Block.BlockTypes contains characters outside [a-z0-9_]. Triggered when a provider developer names a nested block with uppercase letters (e.g., 'Subnet'), hyphens (e.g., 'sub-net'), dots (e.g., 'sub.net'), or any other non-conforming character.

Common situations: Provider developer mirrors an upstream API field name verbatim that contains hyphens or camelCase (common in cloud provider APIs like AWS, GCP). Schema auto-generation from an API spec that doesn't sanitize names. Copy-paste from API documentation. Developer unfamiliar with Terraform naming conventions uses PascalCase or kebab-case.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/c10018c3f8fb558e. Report an issue: GitHub.

Appendix: source

Thrown at internal/configs/configschema/internal_validate.go:58

		}
		multiErr = errors.Join(multiErr, attrS.internalValidate(name, prefix))

		// all attributes within a computed block must also be computed
		if b.Computed && !attrS.Computed {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: all attributes within computed blocks must also be computed", prefix, name))
		}
	}

	for name, blockS := range b.BlockTypes {
		if blockS == nil {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: block schema is nil", prefix, name))
			continue
		}

		if _, isAttr := b.Attributes[name]; isAttr {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: name defined as both attribute and child block type", prefix, name))
		} else if !validName.MatchString(name) {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name))
		}
		if !blockS.Deprecated && blockS.DeprecationMessage != "" {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: DeprecationMessage must not be set when Deprecated is false", prefix, name))
		}

		if blockS.MinItems < 0 || blockS.MaxItems < 0 {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must both be greater than zero", prefix, name))
		}

		// any nested blocks within a computed block must also be computed
		if b.Computed && !blockS.Computed {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: all nested blocks within computed blocks must also be computed", prefix, name))
		}

		switch blockS.Nesting {
		case NestingSingle:
			switch {
			case blockS.MinItems != blockS.MaxItems:

View on GitHub (pinned to d32a084675)