hashicorp/terraform · error

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

Error message

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

What it means

A nested-block type name fails the validName regex `^[a-z0-9_]+$` (checked at line 57): it contains uppercase letters, hyphens, dots, or other characters. Terraform identifiers for block types must be lowercase snake_case. The identical check is commented out for attributes (see the FIXME at lines 141-145) but IS enforced for block types.

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 c9def3e214)

Solutions

  1. Rename the block type to lowercase snake_case: `myBlock` -> `my_block`.
  2. If generating schemas, add a normalization pass that lowercases and replaces separators with underscores.

Example fix

// before
BlockTypes: map[string]*configschema.NestedBlock{ "subnetConfig": {Nesting: configschema.NestingList} }

// after
BlockTypes: map[string]*configschema.NestedBlock{ "subnet_config": {Nesting: configschema.NestingList} }
Defensive patterns

Strategy: validation

Validate before calling

var validName = regexp.MustCompile(`^[a-z0-9_]+$`)
for name := range block.BlockTypes {
    if !validName.MatchString(name) { return fmt.Errorf("invalid block name %q", name) }
}

Prevention

When it happens

Trigger: Naming a nested block `myBlock`, `my-block`, or `MyBlock` in a schema literal; generated schemas emitting camelCase names from an API spec.

Common situations: Copying JSON/YAML field names into Go schema literals, SDK generation from a spec using camelCase, mixing naming conventions.

Related errors


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